diff --git a/reviewboard/static/rb/css/pages/review-request.less b/reviewboard/static/rb/css/pages/review-request.less
index f8182c30f1ec2b9d598a960965f0d6324046e6b1..c0afbab687999cdc5ca66fc431ddf1c80f46ca9e 100644
--- a/reviewboard/static/rb/css/pages/review-request.less
+++ b/reviewboard/static/rb/css/pages/review-request.less
@@ -526,3 +526,19 @@ a.mobile-actions-menu-label {
     margin-left: 2em;
   }
 }
+
+
+
+/****************************************************************************
+ * Upload Preview
+ ****************************************************************************/
+ #paste-upload-preview {
+  //use classes for these
+  img {
+    max-width: 40vw;
+    max-height: 20vh;
+  }
+  pre {
+    max-width: 30vw;
+  }
+ }
diff --git a/reviewboard/static/rb/js/models/uploadPreviewDialogModel.es6.js b/reviewboard/static/rb/js/models/uploadPreviewDialogModel.es6.js
new file mode 100644
index 0000000000000000000000000000000000000000..648f506bdebf9924a391d063d7b5f88b7d9ac76b
--- /dev/null
+++ b/reviewboard/static/rb/js/models/uploadPreviewDialogModel.es6.js
@@ -0,0 +1,6 @@
+RB.UploadPreviewDialogModel = Backbone.Model.extend({
+    defaults: {
+        file: null,
+        fileContent: null,
+    },
+});
diff --git a/reviewboard/static/rb/js/views/reviewRequestEditorView.es6.js b/reviewboard/static/rb/js/views/reviewRequestEditorView.es6.js
index ebb8a8e257a8daa191b218aae4092df327181b36..cf32c373aead18bffe3df66b889f6da826e3fbfb 100644
--- a/reviewboard/static/rb/js/views/reviewRequestEditorView.es6.js
+++ b/reviewboard/static/rb/js/views/reviewRequestEditorView.es6.js
@@ -443,7 +443,8 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
                   '_onCloseDiscardedClicked', '_onCloseSubmittedClicked',
                   '_onDeleteReviewRequestClicked', '_onUpdateDiffClicked',
                   '_onArchiveClicked', '_onUnarchiveClicked',
-                  '_onMuteClicked', '_onUnmuteClicked', '_onUploadFileClicked');
+                  '_onMuteClicked', '_onUnmuteClicked', '_onUploadFileClicked',
+                  '_onPaste');
 
         this._fieldViews = {};
         this.rendered = false;
@@ -453,6 +454,49 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
         this._$main = null;
         this._$extra = null;
         this._blockResizeLayout = false;
+
+
+    },
+
+    /**
+     * Handle a paste event.
+     *
+     * Args:
+     *  e (jQuery.Event):
+     *    The event that triggered this handler.
+     *
+     */
+    _onPaste(e) {
+        const pasteEvent = e.originalEvent;
+        console.log(e.originalEvent);
+        if ($(pasteEvent.target).is('textarea, input') ||
+            pasteEvent.clipboardData.files.length === 0) {
+            return;
+        }
+
+        e.preventDefault();
+        e.stopPropagation();
+        const file = pasteEvent.clipboardData.files[0];
+
+        const reader  = new FileReader();
+        reader.onload = e => this._onFilePasted(e, file);
+
+        if (file.type.startsWith('image/')) {
+            reader.readAsDataURL(file);
+        } else {
+            reader.readAsArrayBuffer(file);
+        }
+    },
+
+    _onFilePasted(readEvent, file) {
+        const dialog = new RB.UploadPreviewDialogView({
+                model: new RB.UploadPreviewDialogModel({
+                    file: file,
+                    fileContent: readEvent.target.result,
+                    reviewRequest: this.model.get('reviewRequest'),
+                }),
+            });
+        dialog.show();
     },
 
     /**
@@ -594,10 +638,16 @@ RB.ReviewRequestEditorView = Backbone.View.extend({
         window.onbeforeunload = this._onBeforeUnload.bind(this);
 
         this.rendered = true;
+        $(window).on('paste', this._onPaste);
 
         return this;
     },
 
+    remove() {
+        Backbone.View.prototype.remove.call(this);
+        $(window).off('paste', this._onPaste);
+    },
+
     /**
      * Warn the user if they try to navigate away with unsaved comments.
      *
diff --git a/reviewboard/static/rb/js/views/uploadPreviewDialogView.es6.js b/reviewboard/static/rb/js/views/uploadPreviewDialogView.es6.js
new file mode 100644
index 0000000000000000000000000000000000000000..4118535bbd74eb9c241b5429a0fb2e2de6bc1433
--- /dev/null
+++ b/reviewboard/static/rb/js/views/uploadPreviewDialogView.es6.js
@@ -0,0 +1,108 @@
+(function() {
+
+
+const captionText = gettext('Caption:');
+
+const warningText = gettext('We could not generate a thumbnail for this file.');
+
+const imageTemplate = _.template(dedent`
+    <form encoding="multipart/form-data" enctype="multipart/form-data" id="attachment-upload-form">
+     <table>
+      <tr>
+       <td class="label">
+        <label for="paste_upload_caption">${captionText}</label>
+        </td>
+        <td>
+         <input type="text" name="caption" id="paste_upload_caption">
+        </td>
+      </tr>
+      <tr>
+      </tr>
+     <img src="<%- fileContent %>" title="<%- fileName %>">
+     </table>
+    </form>
+`);
+
+
+/**
+ * View for previewing pasted files.
+ */
+RB.UploadPreviewDialogView = RB.DialogView.extend({
+    id: 'paste-upload-preview',
+    /**
+     * Initialize the dialog box view.
+     * Args:
+     *     options (object):
+     *         Options for the dialog box.
+     */
+
+    initialize(options = {}) {
+        _.bindAll(this, '_onUploadClicked');
+
+        RB.DialogView.prototype.initialize.call(this, _.defaults({
+            title: gettext('Upload File'),
+            buttons: [
+                {
+                    label: gettext('Cancel'),
+                },
+                {
+                    label: gettext('Upload'),
+                    onClick: this._onUploadClicked,
+                    primary: true,
+                }
+            ],
+        }, options));
+    },
+
+    /**
+     * Handle a file being uploaded.
+     */
+    _onUploadClicked () {
+        const file = this.model.get('file');
+        const attachment = this.model
+            .get('reviewRequest')
+            .createFileAttachment();
+        attachment.set({
+            caption: this.$('#paste_upload_caption').val(),
+            filename: file.name,
+            file: file,
+        });
+        attachment.save({
+            success: () => document.location.reload(),
+            error:(model, xhr) => alert($.parseJSON(xhr.responseText)),
+        });
+    },
+
+
+    /**
+     * Render the Dialog.
+     *
+     * This will render the file attachment as an `image`. If it's not, then it
+     will give a warning
+     *
+     * Returns:
+     *     RB.uploadPreviewDialogView:
+     *     This object, for chaining.
+     */
+    render() {
+        const file = this.model.get('file');
+
+        if (file.type.startsWith('image/')) {
+            const uri = this.model.get('fileContent');
+            const image = new Image();
+
+            image.onload = () => this.$el.append($(imageTemplate({
+                fileContent: uri,
+                fileName: file.name,
+            })));
+
+            image.src = uri;
+        } else {
+            this.$el.append($(`<p>${warningText}</p>`));
+        }
+        return this;
+    }
+});
+
+
+})();
diff --git a/reviewboard/staticbundles.py b/reviewboard/staticbundles.py
index b1113ded0fa4371a6736a0c5006645709a74ad75..fd7be3152a2a49a880dcb762c0188bb6f6899caa 100644
--- a/reviewboard/staticbundles.py
+++ b/reviewboard/staticbundles.py
@@ -278,6 +278,8 @@ PIPELINE_JAVASCRIPT = dict({
             'rb/js/views/floatingBannerView.js',
             'rb/js/views/regionCommentBlockView.es6.js',
             'rb/js/views/reviewDialogView.es6.js',
+            'rb/js/models/uploadPreviewDialogModel.es6.js',
+            'rb/js/views/uploadPreviewDialogView.es6.js',
             'rb/js/views/reviewRequestFieldViews.es6.js',
             'rb/js/views/reviewRequestEditorView.es6.js',
             'rb/js/views/screenshotThumbnailView.js',
