Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

Let Users Paste Screenshots Straight into Your App

C
Carlo Fontanos
· 2 min read

The feature that gets the most "oh nice!" reactions in anything I build is also one of the smallest: paste a screenshot, it uploads. GitHub and Slack trained everyone to expect it; most internal tools still make people save-then-browse like it's 2009.

The whole mechanism is one event

document.addEventListener('paste', (e) => {
    const files = [...e.clipboardData.files]
        .filter(f => f.type.startsWith('image/'));

    if (!files.length) return;   // normal text paste - don't interfere

    e.preventDefault();
    for (const file of files) uploadImage(file);
});

When the clipboard holds an image (a screenshot, a copied image from a browser, a cropped photo), clipboardData.files contains genuine File objects. From there it's a normal upload:

async function uploadImage(file) {
    const body = new FormData();
    body.append('image', file, file.name || 'pasted.png');

    const res = await fetch('/upload', { method: 'POST', body });
    const { url } = await res.json();
    insertPreview(url);
}

Screenshots usually arrive as image/png named image.png - give them a timestamped name server-side. And validate the type on the server with real MIME detection (finfo in PHP), never the filename.

Nice touches that make it feel native

  • Instant local preview: URL.createObjectURL(file) shows the image immediately while the upload runs in the background. Revoke it when done.
  • Scope it sensibly: listening on document is fine for single-purpose pages; in bigger apps attach to the editor or dropzone so pasting into unrelated inputs stays untouched.
  • Pair with drag & drop: the drop event exposes files the same way (e.dataTransfer.files), so one upload function serves both.

The async Clipboard API is the other half

The paste event needs the user to press Ctrl+V. Reading the clipboard programmatically (a "paste image" button) uses the newer API:

const items = await navigator.clipboard.read();   // needs permission + HTTPS
for (const item of items) {
    const type = item.types.find(t => t.startsWith('image/'));
    if (type) uploadImage(new File([await item.getType(type)], 'clip.png', { type }));
}

This one requires a secure context, a user gesture, and shows a permission prompt in Chromium. My advice: build on the paste event (zero friction, works everywhere), add the button version only if users ask.

Total implementation: maybe twenty lines. It's the highest ratio of user delight to code I know.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading