diff --git a/package.json b/package.json
index 040867fa1f48679467a8b197babd29144d35935a..e526a3fb8afb16ea46bbac5eeaf0ae5b2f6a17c5 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
     "bowser": "1.9.4",
     "chromedriver": "2.43.1",
     "classnames": "2.2.6",
+    "computed-style-to-inline-style": "3.0.0",
     "copy-webpack-plugin": "^4.5.1",
     "core-js": "2.5.7",
     "css-loader": "^1.0.0",
diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx
index cca9eaab2a62e8f50f4dc6469db8fee9498d5c15..32c6fea0025f06bdb153ff61b889c31a9d04eb0f 100644
--- a/src/containers/backpack.jsx
+++ b/src/containers/backpack.jsx
@@ -98,14 +98,17 @@ class Backpack extends React.Component {
         }
         if (!payloader) return;
 
-        payloader(dragInfo.payload, this.props.vm)
-            .then(payload => saveBackpackObject({
-                host: this.props.host,
-                token: this.props.token,
-                username: this.props.username,
-                ...payload
-            }))
-            .then(this.refreshContents);
+        // Creating the payload is async, so set loading before starting
+        this.setState({loading: true}, () => {
+            payloader(dragInfo.payload, this.props.vm)
+                .then(payload => saveBackpackObject({
+                    host: this.props.host,
+                    token: this.props.token,
+                    username: this.props.username,
+                    ...payload
+                }))
+                .then(this.refreshContents);
+        });
     }
     handleDelete (id) {
         deleteBackpackObject({
@@ -150,11 +153,14 @@ class Backpack extends React.Component {
             blockDragOverBackpack: false
         });
     }
-    handleBlockDragEnd (blocks) {
+    handleBlockDragEnd (blocks, topBlockId) {
         if (this.state.blockDragOverBackpack) {
             this.handleDrop({
                 dragType: DragConstants.CODE,
-                payload: blocks
+                payload: {
+                    blockObjects: blocks,
+                    topBlockId: topBlockId
+                }
             });
         }
         this.setState({
diff --git a/src/lib/backpack/block-to-image.js b/src/lib/backpack/block-to-image.js
new file mode 100644
index 0000000000000000000000000000000000000000..f3016fdbf01ce6b6af46144066bd811b38321824
--- /dev/null
+++ b/src/lib/backpack/block-to-image.js
@@ -0,0 +1,50 @@
+import computedStyleToInlineStyle from 'computed-style-to-inline-style';
+import ScratchBlocks from 'scratch-blocks';
+
+/**
+ * Given a blockId, return a data-uri image that can be used to create a thumbnail.
+ * @param {string} blockId the ID of the block to imagify
+ * @return {Promise} resolves to a data-url of a picture of the blocks
+ */
+export default function (blockId) {
+    // Not sure any better way to access the scratch-blocks workspace than this...
+    const block = ScratchBlocks.getMainWorkspace().getBlockById(blockId);
+    const blockSvg = block.getSvgRoot().cloneNode(true /* deep */);
+
+    // Once we have the cloned SVG, do the rest in a setTimeout to prevent
+    // blocking the drag end from finishing promptly.
+    return new Promise(resolve => {
+        setTimeout(() => {
+            // Create an <svg> element to put the cloned blockSvg inside
+            const NS = 'http://www.w3.org/2000/svg';
+            const svg = document.createElementNS(NS, 'svg');
+            svg.appendChild(blockSvg);
+
+            // Needs to be on the DOM to get CSS properties and correct sizing
+            document.body.appendChild(svg);
+
+            const padding = 10;
+            const extraHatPadding = 16;
+            const topPadding = padding + (blockSvg.getAttribute('data-shapes') === 'hat' ? extraHatPadding : 0);
+            const leftPadding = padding;
+            blockSvg.setAttribute('transform', `translate(${leftPadding} ${topPadding})`);
+
+            const bounds = blockSvg.getBoundingClientRect();
+            svg.setAttribute('width', bounds.width + (2 * padding));
+            svg.setAttribute('height', bounds.height + (2 * padding));
+
+            // We need to inline the styles set by CSS rules because
+            // not all the styles are set directly on the SVG. This makes the
+            // image styled the same way the block actually appears.
+            // TODO this doesn't handle images that are xlink:href in the SVG
+            computedStyleToInlineStyle(svg, {recursive: true});
+
+            const svgString = (new XMLSerializer()).serializeToString(svg);
+
+            // Once we have the svg as a string, remove it from the DOM
+            svg.parentNode.removeChild(svg);
+
+            resolve(`data:image/svg+xml;utf-8,${encodeURIComponent(svgString)}`);
+        }, 10);
+    });
+}
diff --git a/src/lib/backpack/code-payload.js b/src/lib/backpack/code-payload.js
index 6c190ea08bba98d173ca26c70eb409984142006d..3ec2e85c25671079023513c1bcb93ddd2bccc662 100644
--- a/src/lib/backpack/code-payload.js
+++ b/src/lib/backpack/code-payload.js
@@ -1,16 +1,20 @@
-import codeThumbnail from './code-thumbnail';
+import blockToImage from './block-to-image';
+import jpegThumbnail from './jpeg-thumbnail';
 
-const codePayload = code => {
+const codePayload = ({blockObjects, topBlockId}) => {
     const payload = {
         type: 'script', // Needs to match backpack-server type name
         name: 'code', // All code currently gets the same name
         mime: 'application/json',
-        body: btoa(JSON.stringify(code)), // Base64 encode the json
-        thumbnail: codeThumbnail // TODO make code thumbnail dynamic
+        body: btoa(JSON.stringify(blockObjects)) // Base64 encode the json
     };
 
-    // Return a promise to make it consistent with other payload constructors like costume-payload
-    return new Promise(resolve => resolve(payload));
+    return blockToImage(topBlockId)
+        .then(jpegThumbnail)
+        .then(thumbnail => {
+            payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
+            return payload;
+        });
 };
 
 export default codePayload;
diff --git a/src/lib/backpack/code-thumbnail.js b/src/lib/backpack/code-thumbnail.js
deleted file mode 100644
index e657144bcb1c5c12a8fe99c6ffbb51a3234779df..0000000000000000000000000000000000000000
--- a/src/lib/backpack/code-thumbnail.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// image/jpeg base64 encoded code thumbnail image
-// eslint-disable-next-line max-len
-export default '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';