How to paste images
The modern way #
Using the Async Clipboard API #
To read images from the user's clipboard programmatically, that is, after a button click, you can use the read()
method of the Async Clipboard API. If permission to read from the clipboard have not been granted yet, the call to navigator.clipboard.read()
will request it upon the first call of the method.
const pasteButton = document.querySelector('#paste-button');
pasteButton.addEventListener('click', async () => {
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
const imageTypes = clipboardItem.types.find(type => type.startsWith('image/'))
for (const imageType of imageTypes) {
const blob = await clipboardItem.getType(imageType);
// Do something with the image blob.
}
}
} catch (err) {
console.error(err.name, err.message);
}
});
- Chrome 86, Supported 86
- Firefox 90, Behind a flag
- Edge 79, Supported 79
- Safari 13.1, Supported 13.1
The classic way #
Listen for the paste
event #
The classic way to paste images is to use the (synchronous) Clipboard API, which gives you access to the clipboardData
field inside the Document: paste
event. However, this method comes with limitations, for example because it's synchronous, pasting large amounts of data can block the page.
document.addEventListener('paste', async (e) => {
e.preventDefault();
for (const clipboardItem of e.clipboardData.files) {
if (clipboardItem.type.startsWith('image/')) {
// Do something with the image file.
}
}
});
- Chrome 66, Supported 66
- Firefox 63, Supported 63
- Edge 79, Supported 79
- Safari 13.1, Supported 13.1
Progressive enhancement #
For browsers that do not support the Async Clipboard API, it is impossible to access the user's clipboard programmatically (for example, on a button click). Thus for accessing a user's clipboard on a paste
event, you can use the Async Clipboard API and fall back to the (synchronous) Clipboard API.
The ClipboardItem
objects coming from navigator.clipboard.read
have a types
field which is an array, and File
objects coming from event.clipboardData.files
have a type
field which is a string. You can conditionally check each of the type
or types
fields for images in the clipboard:
document.addEventListener('paste', async (e) => {
e.preventDefault();
const clipboardItems = typeof navigator?.clipboard?.read === 'function' ? await navigator.clipboard.read() : e.clipboardData.files;
for (const clipboardItem of clipboardItems) {
let blob;
if (clipboardItem.type?.startsWith('image/')) {
// For files from `e.clipboardData.files`.
blob = clipboardItem
// Do something with the blob.
} else {
// For files from `navigator.clipboard.read()`.
const imageTypes = clipboardItem.types?.filter(type => type.startsWith('image/'))
for (const imageType of imageTypes) {
blob = await clipboardItem.getType(imageType);
// Do something with the blob.
}
}
}
});
Further reading #
- Clipboard API on MDN
- Unblocking clipboard access on web.dev
Demo #
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to paste images</title>
</head>
<body>
<h1>How to paste images</h1>
<p>Hit <kbd>⌘</kbd> + <kbd>v</kbd> (for macOS) or <kbd>ctrl</kbd> + <kbd>v</kbd>
(for other operating systems) to paste images anywhere in this page.
</p>
</body>
</html>
document.addEventListener('paste', async (e) => {
e.preventDefault();
const clipboardItems = typeof navigator?.clipboard?.read === 'function' ? await navigator.clipboard.read() : e.clipboardData.files;
for (const clipboardItem of clipboardItems) {
let blob;
if (clipboardItem.type?.startsWith('image/')) {
// For files from `e.clipboardData.files`.
blob = clipboardItem
// Do something with the blob.
appendImage(blob);
} else {
// For files from `navigator.clipboard.read()`.
const imageTypes = clipboardItem.types?.filter(type => type.startsWith('image/'))
for (const imageType of imageTypes) {
blob = await clipboardItem.getType(imageType);
// Do something with the blob.
appendImage(blob);
}
}
}
});
const appendImage = (blob) => {
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
document.body.append(img);
};
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
button {
display: block;
}