Let Users Paste Screenshots Straight into Your App
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.
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.