diff --git a/package.json b/package.json
index 439bf307995147592165e46d4004978f0ab17314..94658acec5d21837858cce186d8a21fc759ff7cc 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
     "scratch-render": "0.1.0-prerelease.20180502115145",
     "scratch-svg-renderer": "0.1.0-prerelease.20180423193917",
     "scratch-storage": "0.4.1",
-    "scratch-vm": "0.1.0-prerelease.1525459625",
+    "scratch-vm": "0.1.0-prerelease.1525460669",
     "selenium-webdriver": "3.6.0",
     "startaudiocontext": "1.2.1",
     "style-loader": "^0.20.0",
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index d3b2f9d4480de7260e6a15d2650077e1486dd0bd..21cef41e9a6c66b2072b2625d6ad3f1a828f7154 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -34,7 +34,7 @@ const messages = defineMessages({
     addSpriteFromFile: {
         id: 'gui.spriteSelector.addSpriteFromFile',
         description: 'Button to add a sprite in the target pane from file',
-        defaultMessage: 'Coming Soon'
+        defaultMessage: 'Upload'
     }
 });
 
@@ -51,12 +51,15 @@ const SpriteSelectorComponent = function (props) {
         onChangeSpriteY,
         onDeleteSprite,
         onDuplicateSprite,
+        onFileUploadClick,
         onNewSpriteClick,
-        onSurpriseSpriteClick,
         onPaintSpriteClick,
         onSelectSprite,
+        onSpriteUpload,
+        onSurpriseSpriteClick,
         raised,
         selectedId,
+        spriteFileInput,
         sprites,
         ...componentProps
     } = props;
@@ -121,7 +124,11 @@ const SpriteSelectorComponent = function (props) {
                 moreButtons={[
                     {
                         title: intl.formatMessage(messages.addSpriteFromFile),
-                        img: fileUploadIcon
+                        img: fileUploadIcon,
+                        onClick: onFileUploadClick,
+                        fileAccept: '.svg, .png, .jpg, .jpeg, .sprite2', // TODO add sprite 3
+                        fileChange: onSpriteUpload,
+                        fileInput: spriteFileInput
                     }, {
                         title: intl.formatMessage(messages.addSpriteFromSurprise),
                         img: surpriseIcon,
@@ -154,12 +161,15 @@ SpriteSelectorComponent.propTypes = {
     onChangeSpriteY: PropTypes.func,
     onDeleteSprite: PropTypes.func,
     onDuplicateSprite: PropTypes.func,
+    onFileUploadClick: PropTypes.func,
     onNewSpriteClick: PropTypes.func,
     onPaintSpriteClick: PropTypes.func,
     onSelectSprite: PropTypes.func,
+    onSpriteUpload: PropTypes.func,
     onSurpriseSpriteClick: PropTypes.func,
     raised: PropTypes.bool,
     selectedId: PropTypes.string,
+    spriteFileInput: PropTypes.func,
     sprites: PropTypes.shape({
         id: PropTypes.shape({
             costume: PropTypes.shape({
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index 4be489f6948e718c7fe8c2cecb90c44db388726a..fb08f3b413c8da8105a40309571c4c5a95cb383b 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -17,6 +17,7 @@ import styles from './target-pane.css';
  */
 const TargetPane = ({
     editingTarget,
+    fileInputRef,
     hoveredTarget,
     spriteLibraryVisible,
     onChangeSpriteDirection,
@@ -27,11 +28,13 @@ const TargetPane = ({
     onChangeSpriteY,
     onDeleteSprite,
     onDuplicateSprite,
+    onFileUploadClick,
     onNewSpriteClick,
-    onSurpriseSpriteClick,
     onPaintSpriteClick,
     onRequestCloseSpriteLibrary,
     onSelectSprite,
+    onSpriteUpload,
+    onSurpriseSpriteClick,
     raiseSprites,
     stage,
     sprites,
@@ -48,6 +51,7 @@ const TargetPane = ({
             hoveredTarget={hoveredTarget}
             raised={raiseSprites}
             selectedId={editingTarget}
+            spriteFileInput={fileInputRef}
             sprites={sprites}
             onChangeSpriteDirection={onChangeSpriteDirection}
             onChangeSpriteName={onChangeSpriteName}
@@ -57,9 +61,11 @@ const TargetPane = ({
             onChangeSpriteY={onChangeSpriteY}
             onDeleteSprite={onDeleteSprite}
             onDuplicateSprite={onDuplicateSprite}
+            onFileUploadClick={onFileUploadClick}
             onNewSpriteClick={onNewSpriteClick}
             onPaintSpriteClick={onPaintSpriteClick}
             onSelectSprite={onSelectSprite}
+            onSpriteUpload={onSpriteUpload}
             onSurpriseSpriteClick={onSurpriseSpriteClick}
         />
         <div className={styles.stageSelectorWrapper}>
@@ -108,6 +114,7 @@ const spriteShape = PropTypes.shape({
 TargetPane.propTypes = {
     editingTarget: PropTypes.string,
     extensionLibraryVisible: PropTypes.bool,
+    fileInputRef: PropTypes.func,
     hoveredTarget: PropTypes.shape({
         hoveredSprite: PropTypes.string,
         receivedBlocks: PropTypes.bool
@@ -120,11 +127,13 @@ TargetPane.propTypes = {
     onChangeSpriteY: PropTypes.func,
     onDeleteSprite: PropTypes.func,
     onDuplicateSprite: PropTypes.func,
+    onFileUploadClick: PropTypes.func,
     onNewSpriteClick: PropTypes.func,
     onPaintSpriteClick: PropTypes.func,
     onRequestCloseExtensionLibrary: PropTypes.func,
     onRequestCloseSpriteLibrary: PropTypes.func,
     onSelectSprite: PropTypes.func,
+    onSpriteUpload: PropTypes.func,
     onSurpriseSpriteClick: PropTypes.func,
     raiseSprites: PropTypes.bool,
     spriteLibraryVisible: PropTypes.bool,
diff --git a/src/containers/sprite-library.jsx b/src/containers/sprite-library.jsx
index 072e1714a81ca1683acb86e9d280bef47c322156..15d75970c1b9123373d37e7c93d3a4aa20fe4c4c 100644
--- a/src/containers/sprite-library.jsx
+++ b/src/containers/sprite-library.jsx
@@ -39,7 +39,7 @@ class SpriteLibrary extends React.PureComponent {
         clearInterval(this.intervalId);
     }
     handleItemSelect (item) {
-        this.props.vm.addSprite2(JSON.stringify(item.json));
+        this.props.vm.addSprite(JSON.stringify(item.json));
         analytics.event({
             category: 'library',
             action: 'Select Sprite',
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 97d6dffaa31e4c89e9f09a02d0e7acda5bb8fe76..1178ed64ec9d7fc10eb6d07afe1fcba16a535ffb 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -13,6 +13,7 @@ import {setReceivedBlocks} from '../reducers/hovered-target';
 
 import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
 import spriteLibraryContent from '../lib/libraries/sprites.json';
+import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js';
 
 class TargetPane extends React.Component {
     constructor (props) {
@@ -27,9 +28,13 @@ class TargetPane extends React.Component {
             'handleChangeSpriteY',
             'handleDeleteSprite',
             'handleDuplicateSprite',
+            'handleNewSprite',
             'handleSelectSprite',
             'handleSurpriseSpriteClick',
-            'handlePaintSpriteClick'
+            'handlePaintSpriteClick',
+            'handleFileUploadClick',
+            'handleSpriteUpload',
+            'setFileInput'
         ]);
     }
     componentDidMount () {
@@ -67,19 +72,34 @@ class TargetPane extends React.Component {
     }
     handleSurpriseSpriteClick () {
         const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
-        this.props.vm.addSprite2(JSON.stringify(item.json));
+        this.props.vm.addSprite(JSON.stringify(item.json));
     }
     handlePaintSpriteClick () {
         // @todo this is brittle, will need to be refactored for localized libraries
         const emptyItem = spriteLibraryContent.find(item => item.name === 'Empty');
         if (emptyItem) {
-            this.props.vm.addSprite2(JSON.stringify(emptyItem.json)).then(() => {
+            this.props.vm.addSprite(JSON.stringify(emptyItem.json)).then(() => {
                 setTimeout(() => { // Wait for targets update to propagate before tab switching
                     this.props.onActivateTab(COSTUMES_TAB_INDEX);
                 });
             });
         }
     }
+    handleNewSprite (spriteJSONString) {
+        this.props.vm.addSprite(spriteJSONString);
+    }
+    handleFileUploadClick () {
+        this.fileInput.click();
+    }
+    handleSpriteUpload (e) {
+        const storage = this.props.vm.runtime.storage;
+        handleFileUpload(e.target, (buffer, fileType, fileName) => {
+            spriteUpload(buffer, fileType, fileName, storage, this.handleNewSprite);
+        });
+    }
+    setFileInput (input) {
+        this.fileInput = input;
+    }
     handleBlockDragEnd (blocks) {
         if (this.props.hoveredTarget.sprite && this.props.hoveredTarget.sprite !== this.props.editingTarget) {
             this.props.vm.shareBlocksToTarget(blocks, this.props.hoveredTarget.sprite);
@@ -95,6 +115,7 @@ class TargetPane extends React.Component {
         return (
             <TargetPaneComponent
                 {...componentProps}
+                fileInputRef={this.setFileInput}
                 onChangeSpriteDirection={this.handleChangeSpriteDirection}
                 onChangeSpriteName={this.handleChangeSpriteName}
                 onChangeSpriteSize={this.handleChangeSpriteSize}
@@ -103,8 +124,10 @@ class TargetPane extends React.Component {
                 onChangeSpriteY={this.handleChangeSpriteY}
                 onDeleteSprite={this.handleDeleteSprite}
                 onDuplicateSprite={this.handleDuplicateSprite}
+                onFileUploadClick={this.handleFileUploadClick}
                 onPaintSpriteClick={this.handlePaintSpriteClick}
                 onSelectSprite={this.handleSelectSprite}
+                onSpriteUpload={this.handleSpriteUpload}
                 onSurpriseSpriteClick={this.handleSurpriseSpriteClick}
             />
         );
diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js
index ca4ce8773c113b92ef9fcfb460daeda609242482..d32f5cc1cdf4d055ba9d5078d7bc9589e2d8002b 100644
--- a/src/lib/file-uploader.js
+++ b/src/lib/file-uploader.js
@@ -47,6 +47,7 @@ const handleFileUpload = function (fileInput, onload) {
  * @property {string} dataFormat The data format of this asset, typically
  * the extension to be used for that particular asset, e.g. 'svg' for vector images
  * @property {string} md5 The md5 hash of the asset data, followed by '.'' and dataFormat
+ * @property {string} The md5 hash of the asset data // TODO remove duplication....
  */
 
 /**
@@ -71,7 +72,8 @@ const cacheAsset = function (storage, fileName, assetType, dataFormat, data) {
     return {
         name: fileName,
         dataFormat: dataFormat,
-        md5: `${md5}.${dataFormat}`
+        md5: `${md5}.${dataFormat}`,
+        assetId: md5
     };
 };
 
@@ -106,6 +108,7 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle
         break;
     }
     default:
+        log.warn(`Encountered unexpected file type: ${fileType}`);
         return;
     }
 
@@ -158,6 +161,7 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun
         break;
     }
     default:
+        log.warn(`Encountered unexpected file type: ${fileType}`);
         return;
     }
 
@@ -171,8 +175,49 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun
     handleSound(vmSound);
 };
 
+const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite) {
+    switch (fileType) {
+    case '':
+    case 'application/zip': { // We think this is a .sprite2 or .sprite3 file
+        handleSprite(new Uint8Array(fileData));
+        return;
+    }
+    case 'image/svg+xml':
+    case 'image/png':
+    case 'image/jpeg': {
+        // Make a sprite from an image by making it a costume first
+        costumeUpload(fileData, fileType, `${spriteName}-costume1`, storage, (vmCostume => {
+            const newSprite = {
+                name: spriteName,
+                isStage: false,
+                x: 0,
+                y: 0,
+                visible: true,
+                size: 100,
+                rotationStyle: 'all around',
+                direction: 90,
+                draggable: true,
+                currentCostume: 0,
+                blocks: {},
+                variables: {},
+                costumes: [vmCostume],
+                sounds: [] // TODO are all of these necessary?
+            };
+            // TODO probably just want sprite upload to handle this object directly
+            handleSprite(JSON.stringify(newSprite));
+        }));
+        return;
+    }
+    default: {
+        log.warn(`Encountered unexpected file type: ${fileType}`);
+        return;
+    }
+    }
+};
+
 export {
     handleFileUpload,
     costumeUpload,
-    soundUpload
+    soundUpload,
+    spriteUpload
 };