Every time you press Ctrl+C, your operating system does something you probably never think about: it stores several copies of the same content in parallel. A single copy from a web page will typically leave text/plain, text/html, and occasionally text/uri-list on the clipboard at the same time. A screenshot will show up as image/png. A selection in Google Docs will place half a dozen application/* formats alongside the plain text.
If you are building a paste handler, a rich-text editor, a file uploader, or anything that accepts clipboard data, understanding these MIME types is the difference between a feature that "works on my machine" and one that survives a user pasting from Outlook. This article is a field guide: what each common MIME type means, where it comes from, and when you should prefer one over another.
What is a clipboard MIME type?
MIME (Multipurpose Internet Mail Extensions) types were originally designed to label email attachments, but the same labels are now used anywhere a program needs to describe what kind of bytes it is about to hand over. The clipboard uses MIME types as keys in a dictionary: each key points to a different representation of the same logical content. When your paste handler asks the clipboard for text/html, it receives the HTML version of what the user copied; when it asks for text/plain, it receives the plain-text version.
In JavaScript, you see this dictionary through event.clipboardData.types (a list of strings) and event.clipboardData.getData(type) (the value for a given type). The async navigator.clipboard.read() API exposes the same structure through ClipboardItem.types and ClipboardItem.getType(type).
text/plain
The lowest common denominator. Every copy operation that produces text of any kind puts a text/plain representation on the clipboard. It is unformatted, unstyled, and encoded as UTF-8 in every modern browser. If your feature only needs the string a user intended to copy, text/plain is the right source of truth.
A common mistake is to ignore text/plain and parse text/html instead when both are available, reasoning that HTML carries more information. It usually does — but the HTML representation of a copied paragraph will include wrappers, inline styles, and tags from the source application. If your handler then converts that HTML back into text, you often end up with more or less content than the user actually copied.
text/html
Present whenever the source application wants to preserve formatting. A copy from a web page, a Word document, Google Docs, Slack, an email client, or anywhere rich text is rendered produces an HTML representation. The shape of this HTML varies wildly:
- Browsers wrap the HTML in a
<meta>tag that declares the source charset and a comment block identifying the source. - Microsoft Office produces HTML embedded inside a "StartFragment"/"EndFragment" comment scaffold, alongside massive
mso-*inline styles and VML metadata. - Google Docs places highly specific inline styles on every element and adds custom attributes for its own import paths.
- Code editors often produce
<pre>-wrapped HTML with inline syntax-highlighting colours.
Rich-text editors that accept HTML have to sanitise it aggressively before inserting it into the document. A naive innerHTML = html pastes raw styles that override the host document's typography, leaks XSS vectors, and fills the DOM with empty wrapper tags. Libraries such as ProseMirror, Slate, and TipTap each ship their own HTML-to-schema pipelines for this reason.
text/uri-list
A newline-separated list of URIs. Produced when the user copies a link, a file from a file-manager, or a tab from the browser's address bar. The format is defined by RFC 2483: URIs are separated by \r\n, and lines starting with # are comments. Most clipboards produce a single-line value (one URI), but you should parse defensively.
When both text/uri-list and text/plain are present, the plain-text version usually contains the same URL. If your feature is a URL-importer, prefer text/uri-list because it disambiguates an intentionally copied link from a text block that happens to contain a URL.
image/png, image/jpeg, image/gif, image/webp
Any image on the clipboard will be exposed through an image/* MIME type. The most common is image/png, produced by screenshot tools on every major OS. image/jpeg appears when copying photos from a browser image element that was itself JPEG-encoded. Copying from Safari sometimes produces image/tiff; copying from a modern design tool can produce image/webp.
In the synchronous paste-event API, image data appears as a File object in event.clipboardData.items. In the async Clipboard API, it appears as a Blob you can fetch with clipboardItem.getType('image/png'). Never assume the image will be PNG — always consume the MIME type the clipboard reports.
application/* (custom types)
These are the "fingerprints" that make an application's copied data lossless when pasted back into the same application. Google Docs places application/x-vnd.google-docs-document-slice-clip+wrapped on the clipboard; Office uses application/x-officedraw; Figma uses application/vnd.figma; design tools sometimes use custom JSON formats. Web apps can register their own: the Clipboard API's ClipboardItem constructor accepts arbitrary MIME keys.
Most paste handlers can safely ignore these unless they want to offer rich import from a specific source. If you do want to consume them, the async Clipboard API is the only reliable path — the synchronous paste event on many browsers strips custom types as a security measure.
Files on the clipboard
A user copying a file from Finder or Explorer produces something subtler: the clipboard contains a file handle, not the file's bytes. In the paste event, this appears as entries in event.clipboardData.files — each one a File object with a name, size, type, and an async arrayBuffer() accessor.
Treat file-handles carefully. The actual bytes are only read when you invoke arrayBuffer(), which is asynchronous and can fail if the source file was moved or deleted between the copy and the paste. If your feature needs to upload the file, read it into a blob first, then upload the blob — do not keep the File reference for longer than a single event tick.
Picking the right type in practice
A simple rule of thumb:
- Plain-text imports or search inputs: use
text/plain, fall back to nothing. - URL imports: use
text/uri-list, fall back totext/plainafter validating as a URL. - Rich text editors: use
text/html, fall back totext/plainwrapped in a paragraph. Always sanitise HTML before insertion. - File or image uploaders: check
clipboardData.filesfirst, then anyimage/*entry initems, then fall back to URL-based imports fromtext/uri-list. - Import from a specific source app: check for the app's
application/*MIME first, then fall back to more generic types.
Inspecting what's actually there
The fastest way to see which MIME types a given copy produces is to open Clipboard Inspector and press Ctrl+V. Every type is listed with its decoded value, and you can copy individual entries back out for testing. The paste-event debugging guide walks through a practical workflow for chasing down a paste bug end to end.