diff --git a/src/components/sprite-selector/sprite-list.jsx b/src/components/sprite-selector/sprite-list.jsx
index e8ab7f8d0cc583f25801617f53399ae0c78b10a1..a29912b79385183e8c49e0ac819ba5288ec3e761 100644
--- a/src/components/sprite-selector/sprite-list.jsx
+++ b/src/components/sprite-selector/sprite-list.jsx
@@ -71,6 +71,7 @@ const SpriteList = function (props) {
                                 [styles.raised]: isRaised,
                                 [styles.receivedBlocks]: receivedBlocks
                             })}
+                            dragPayload={sprite}
                             dragType={DragConstants.SPRITE}
                             id={sprite.id}
                             index={index}
diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx
index 35fc11bf1655f2b40a0453ce55f12b04d9d1ad7f..aedf2f35da1c84187386aae3e5132d7fbfad9372 100644
--- a/src/containers/backpack.jsx
+++ b/src/containers/backpack.jsx
@@ -7,12 +7,14 @@ import {
     saveBackpackObject,
     deleteBackpackObject,
     soundPayload,
-    costumePayload
+    costumePayload,
+    spritePayload
 } from '../lib/backpack-api';
 import DragConstants from '../lib/drag-constants';
 
 import {connect} from 'react-redux';
 import storage from '../lib/storage';
+import VM from 'scratch-vm';
 
 class Backpack extends React.Component {
     constructor (props) {
@@ -45,7 +47,7 @@ class Backpack extends React.Component {
         }
     }
     componentWillReceiveProps (newProps) {
-        const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND];
+        const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND, DragConstants.SPRITE];
         // If `dragging` becomes true, record the drop area rectangle
         if (newProps.dragInfo.dragging && !this.props.dragInfo.dragging) {
             this.dropAreaRect = this.ref && this.ref.getBoundingClientRect();
@@ -83,10 +85,13 @@ class Backpack extends React.Component {
         case DragConstants.SOUND:
             payloader = soundPayload;
             break;
+        case DragConstants.SPRITE:
+            payloader = spritePayload;
+            break;
         }
         if (!payloader) return;
 
-        payloader(dragInfo.payload)
+        payloader(dragInfo.payload, this.props.vm)
             .then(payload => saveBackpackObject({
                 host: this.props.host,
                 token: this.props.token,
@@ -152,7 +157,8 @@ Backpack.propTypes = {
     }),
     host: PropTypes.string,
     token: PropTypes.string,
-    username: PropTypes.string
+    username: PropTypes.string,
+    vm: PropTypes.instanceOf(VM)
 };
 
 const getTokenAndUsername = state => {
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index 1d637330acce4a4410f1f6ecd840089c86febfc6..55f1a23eda355dde4ebf76d6c6d87c6d9bbe3d58 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -135,6 +135,12 @@ class TargetPane extends React.Component {
         if (dragInfo.dragType === DragConstants.SPRITE) {
             // Add one to both new and target index because we are not counting/moving the stage
             this.props.vm.reorderTarget(dragInfo.index + 1, dragInfo.newIndex + 1);
+        } else if (dragInfo.dragType === DragConstants.BACKPACK_SPRITE) {
+            // TODO storage does not have a way of loading zips right now, and may never need it.
+            // So for now just grab the zip manually.
+            fetch(dragInfo.payload.bodyUrl)
+                .then(response => response.arrayBuffer())
+                .then(zip => this.props.vm.addSprite(zip));
         } else if (targetId) {
             // Something is being dragged over one of the sprite tiles or the backdrop.
             // Dropping assets like sounds and costumes duplicate the asset on the
diff --git a/src/lib/backpack-api.js b/src/lib/backpack-api.js
index 9a2b19e95979a57329cbcd87dac60b895fdb18bf..f0c51f14040ced7823c226521dd792b4cf639f65 100644
--- a/src/lib/backpack-api.js
+++ b/src/lib/backpack-api.js
@@ -1,6 +1,7 @@
 import xhr from 'xhr';
 import costumePayload from './backpack/costume-payload';
 import soundPayload from './backpack/sound-payload';
+import spritePayload from './backpack/sprite-payload';
 
 const getBackpackContents = ({
     host,
@@ -19,9 +20,13 @@ const getBackpackContents = ({
             return reject();
         }
         // Add a new property for the full thumbnail url, which includes the host.
+        // Also include a full body url for loading sprite zips
         // TODO retreiving the images through storage would allow us to remove this.
         return resolve(response.body.map(item => (
-            Object.assign({}, item, {thumbnailUrl: `${host}/${item.thumbnail}`})
+            Object.assign({}, item, {
+                thumbnailUrl: `${host}/${item.thumbnail}`,
+                bodyUrl: `${host}/${item.body}`
+            })
         )));
     });
 });
@@ -72,5 +77,6 @@ export {
     saveBackpackObject,
     deleteBackpackObject,
     costumePayload,
-    soundPayload
+    soundPayload,
+    spritePayload
 };
diff --git a/src/lib/backpack/sprite-payload.js b/src/lib/backpack/sprite-payload.js
new file mode 100644
index 0000000000000000000000000000000000000000..c9dea7f22ab4cf9933463540903dada2c4a6206c
--- /dev/null
+++ b/src/lib/backpack/sprite-payload.js
@@ -0,0 +1,24 @@
+import jpegThumbnail from './jpeg-thumbnail';
+import storage from '../storage';
+
+const spritePayload = (sprite, vm) => vm.exportSprite(
+    sprite.id,
+    'base64'
+).then(zippedSprite => {
+    const payload = {
+        type: 'sprite',
+        name: sprite.name,
+        mime: 'application/zip',
+        body: zippedSprite,
+        // Filled in below
+        thumbnail: ''
+    };
+
+    const costumeDataUrl = storage.get(sprite.costume.assetId).encodeDataURI();
+    return jpegThumbnail(costumeDataUrl).then(thumbnail => {
+        payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
+        return payload;
+    });
+});
+
+export default spritePayload;