Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
Active Storage

How to Send Pasted Screenshots from Paste.js to Rails (Active Storage)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Paste.js only reads the clipboard; Active Storage performs the Rails upload and attachment. To connect them, handle the paste event in the browser, convert the image payload to a File or Blob, then send it either as multipart form data to Rails or through Active Storage’s direct-upload flow. Rails does not provide a built-in Paste.js adapter, so the bridge is application code.

This guide shows a complete server-mediated implementation first, then explains direct uploads, cleanup, progress, security, and failure handling. Paste.js APIs differ between releases; confirm the event and payload names in the version installed from the project’s current README and release history (Paste.js on GitHub).

How the pieces fit together

There are three separate operations:

  1. Clipboard capture: Paste.js listens on a focused browser element and exposes pasted text or image data.
  2. Upload: your JavaScript turns that data into a browser File and submits bytes by multipart HTTP or Active Storage direct upload.
  3. Attachment: Rails attaches the resulting upload to an Active Record model, then serves previews or transformed images through Active Storage.

Capturing an image does not attach anything by itself. The model, endpoint, validation, authorization, and abandoned-upload cleanup remain your application’s responsibility. Active Storage’s attachment API is described in the Rails API reference and its workflow in the Active Storage Overview.

Choose an upload path

Path Byte destination Advantages Costs and requirements
Rails-mediated multipart Your Rails controller first Smallest conceptual jump from a custom Paste.js handler; ordinary authentication and validation are easy to apply. Rails receives and forwards every byte, so application servers handle upload traffic and temporary files.
Active Storage direct upload Configured storage service from the browser Rails is bypassed for file bytes, reducing web-server transfer work; Rails receives a signed blob reference. More JavaScript and blob-association code; third-party storage requires correct CORS configuration. Rails documents this flow for file inputs, not Paste.js specifically.

Use the first route when you want the simplest custom endpoint. Use direct upload when upload traffic should avoid Rails and your storage and CORS setup are ready. In both cases, enforce authorization and content validation on the server.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites and model setup

  1. Install and configure Active Storage with bin/rails active_storage:install, then run the migration.
  2. Configure a storage service in config/storage.yml and select it per environment.
  3. Include the Active Storage JavaScript package if you use direct uploads; Rails’ guide documents the required import and file-input setup.
  4. Define an attachment on the record that should own the screenshot.

For example, a support ticket can accept one screenshot:

class Ticket < ApplicationRecord
  has_one_attached :screenshot
end

For multiple pasted images, use has_many_attached :screenshots and adjust the controller and client state accordingly.

Server-mediated implementation

1. Add a route and controller action

# config/routes.rb
resources :tickets, only: [] do
  post :screenshot, on: :member
end

# app/controllers/tickets_controller.rb
class TicketsController < ApplicationController
  before_action :set_ticket

  def screenshot
    upload = params.require(:screenshot)

    unless upload.content_type.in?(%w[image/png image/jpeg image/webp])
      return render json: { error: "Unsupported image type" }, status: :unprocessable_entity
    end

    if upload.size > 10.megabytes
      return render json: { error: "Image is too large" }, status: :unprocessable_entity
    end

    @ticket.screenshot.attach(upload)
    render json: {
      id: @ticket.screenshot.blob.id,
      filename: @ticket.screenshot.filename.to_s,
      url: url_for(@ticket.screenshot)
    }, status: :created
  end

  private

  def set_ticket
    @ticket = current_user.tickets.find(params[:id])
  end
end

The size and MIME checks are examples, not a substitute for your application’s policy. Treat client-supplied filenames and content types as untrusted. For stronger validation, inspect the uploaded file with your normal image-processing and malware-scanning controls.

2. Add a paste target

<div id="screenshot-paste-target"
     contenteditable="true"
     role="textbox"
     aria-label="Paste a screenshot here">
  Click here, then press Ctrl-V (Windows/Linux) or Command-V (macOS).
</div>
<progress id="upload-progress" max="100" value="0" hidden></progress>
<div id="paste-status" role="status" aria-live="polite"></div>
<img id="screenshot-preview" alt="Pasted screenshot preview" hidden>

A focused, editable target gives users a predictable place to paste. You can use a textarea, contenteditable region, or another element supported by the Paste.js version you installed. Do not assume a global paste listener will receive clipboard data when the browser or operating system blocks it.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Adapt Paste.js image data to a File

Paste.js examples expose separate image and text handlers, but the exact callback signature is version-dependent. Keep the library-specific part tiny and normalize its result to a browser File. The following adapter accepts a Blob, a File, or a data URL; replace the marked handler wiring with the API used by your installed release.

function asImageFile(value, filename = "pasted-screenshot.png") {
  if (value instanceof File) return value;
  if (value instanceof Blob) {
    return new File([value], filename, { type: value.type || "image/png" });
  }
  if (typeof value === "string" && value.startsWith("data:")) {
    const [meta, encoded] = value.split(",");
    const mime = (meta.match(/data:([^;]+)/) || [])[1] || "image/png";
    const bytes = Uint8Array.from(atob(encoded), c => c.charCodeAt(0));
    return new File([bytes], filename, { type: mime });
  }
  throw new TypeError("Paste.js did not provide an image payload");
}

async function sendScreenshot(file, ticketId) {
  const form = new FormData();
  form.append("screenshot", file, file.name);

  const response = await fetch(`/tickets/${ticketId}/screenshot`, {
    method: "POST",
    body: form,
    headers: {
      "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").content,
      "Accept": "application/json"
    },
    credentials: "same-origin"
  });

  const payload = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(payload.error || `Upload failed (${response.status})`);
  return payload;
}

In your Paste.js setup, call asImageFile(imagePayload) from the image-paste callback, preview it with URL.createObjectURL(file), and pass it to sendScreenshot. Revoke the object URL after replacing the preview to avoid retaining browser memory.

4. Add progress, duplicate handling, and errors

let uploadInProgress = false;
const target = document.querySelector("#screenshot-paste-target");
const progress = document.querySelector("#upload-progress");
const status = document.querySelector("#paste-status");
const preview = document.querySelector("#screenshot-preview");

async function handlePastedImage(imagePayload) {
  if (uploadInProgress) {
    status.textContent = "An upload is already in progress.";
    return;
  }

  const file = asImageFile(imagePayload);
  const objectUrl = URL.createObjectURL(file);
  preview.src = objectUrl;
  preview.hidden = false;
  uploadInProgress = true;
  progress.hidden = false;
  progress.value = 0;
  status.textContent = "Uploading screenshot…";

  try {
    const result = await sendScreenshot(file, target.dataset.ticketId);
    status.textContent = `Uploaded ${result.filename}`;
  } catch (error) {
    status.textContent = error.message;
  } finally {
    uploadInProgress = false;
    progress.hidden = true;
    URL.revokeObjectURL(objectUrl);
  }
}

// Wire this function to Paste.js's image callback for your installed version.
// Example shape only: pasteHandler.onImage(handlePastedImage);

fetch does not expose upload-progress events. If a visible percentage is required for the server-mediated route, use XMLHttpRequest.upload.onprogress or a supported upload library. Active Storage’s documented direct-upload lifecycle includes progress and error events, but custom Paste.js wiring still has to subscribe and update your UI.

Direct uploads with Active Storage

Rails direct upload creates a blob record, sends bytes from the browser to the configured storage service, and returns a signed blob reference. The Rails guide documents this sequence for inputs marked direct_upload: true; a pasted file must be inserted into the same JavaScript machinery rather than selected through an input.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Import Active Storage’s JavaScript package in your application bundle.
  2. Use the configured direct-upload endpoint to create a blob from the pasted File.
  3. Upload the file to the service using the signed data returned by Rails.
  4. Keep the returned signed ID in your form or send it to a controller that calls record.screenshot.attach(signed_id).

The exact client calls depend on your Rails version and bundler. Follow the current Active Storage direct-upload documentation and adapt its file-input example to the normalized File from Paste.js. Configure storage-service CORS for your application origin; without it, the browser can reject an otherwise valid signed upload.

Associating the blob with a record

def update
  @ticket = current_user.tickets.find(params[:id])
  if @ticket.update(ticket_params)
    redirect_to @ticket
  else
    render :edit, status: :unprocessable_entity
  end
end

def ticket_params
  params.require(:ticket).permit(:title, :screenshot)
end

When posting a direct-upload signed ID, permit that field and attach it through the normal Active Storage association. If you upload before the parent record is saved, retain the signed ID in the form state and remove unused blobs when a user abandons the form. Rails does not decide your abandonment policy for you.

Rendering and transforming the attachment

After attachment, render through Active Storage helpers so URLs and representations follow your configured service:

<%= image_tag @ticket.screenshot.variant(resize_to_limit: [1200, 1200]) if @ticket.screenshot.attached? %>

Image transformations require the image-processing dependencies and configuration described in the Rails guide. Generate variants only after checking that an attachment exists.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Security and validation checklist

  • Authorize the ticket or record before accepting an upload; never trust an ID supplied by the browser.
  • Restrict permitted media types and size on the server, not only in JavaScript.
  • Decide whether SVG, animated images, and very large dimensions are allowed.
  • Use CSRF protection for same-origin Rails requests and HTTPS in production.
  • Do not expose private attachment URLs without the authorization policy your application requires.
  • Rate-limit repeated paste attempts and consider scanning untrusted uploads.
  • For direct uploads, limit CORS origins, methods, and headers to what the storage service needs.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting

Nothing happens when the user pastes

Focus the designated target, verify that the browser clipboard permission is available, and check that the Paste.js callback is actually registered for the installed version. Test with an ordinary image copied from the operating system and inspect the browser console.

The callback receives text but no image

Clipboard contents can contain multiple representations. Ensure you are using Paste.js’s image event rather than its text event, and handle the case where the source application places only a file item or unsupported format on the clipboard.

Rails returns 422

Log the received parameter name, MIME type, and size. A mismatch between form.append("screenshot", ...) and the controller’s permitted parameter, a rejected content type, or an oversized file commonly causes this response.

Direct upload fails with a CORS error

Check the storage service’s CORS rule for the exact application origin and required methods and headers. Rails’ direct-upload setup assumes this cross-origin configuration when storage is hosted separately.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The image uploaded but disappears after leaving the form

You probably created a blob before saving or attaching the parent record. Store the signed ID until save succeeds and schedule cleanup for unattached blobs according to your retention policy.

Duplicate screenshots are created

Disable the paste target while an upload is active, debounce repeated callbacks, and decide whether a second paste replaces the existing attachment or creates another one. The choice belongs in your product behavior, not in Paste.js.

Performance, reliability, and cost decisions

  • Server load: multipart uploads consume Rails request capacity; direct uploads move file bytes directly to storage.
  • Latency: show an immediate local preview, then report upload and attachment states separately so users do not mistake a preview for a saved file.
  • Retries: retry transient network failures carefully; do not blindly retry a request that may already have attached the blob. Use an idempotency strategy if duplicate attachments matter.
  • Large images: resize in the browser only when your quality and privacy requirements permit it; otherwise preserve the original and generate Active Storage variants for display.
  • Abandoned blobs: direct uploads can finish before the form is submitted, so monitor and clean unattached records.

Or skip the browser setup

If your goal is to obtain a clean screenshot of a web page rather than accept a user’s clipboard image, ScreenshotNeo provides a one-request screenshot API and MCP server. It removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. AI agents can use its MCP tools take_screenshot, get_page_info, and capture_pdf.

cURL (see the ScreenshotNeo documentation):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Every response identifies its page verdict and billing status in X-Page-Verdict and X-Billed headers. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000. Sign up for the free ScreenshotNeo plan.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does Paste.js upload directly to Active Storage?

No. Paste.js exposes clipboard data; your JavaScript must convert it to a File or Blob and then use a Rails endpoint or Active Storage direct-upload flow.

Can I paste into a normal file input?

Browsers do not let application JavaScript assign arbitrary clipboard bytes to a file input for security reasons. Handle the paste event and feed the resulting File into your upload path instead.

Which route is better for private screenshots?

Choose the route that matches your authorization and storage policy. Both require server-side authorization; direct uploads additionally require tightly scoped storage CORS and signed requests.

Why does Rails documentation show file inputs instead of Paste.js?

The official guide documents the supported Active Storage direct-upload mechanism, while Paste.js is a separate clipboard layer. The integration between them is custom application code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.