Which image and video formats your browser can decode leaks its engine, version and even GPU. How Accept, canPlayType and decodingInfo differ.
Browsers still disagree about which image and video formats they can decode. JPEG XL is the clearest example: Safari has decoded it since Safari 17, while Chromium and Firefox have gone back and forth on shipping it. Every time two engines disagree about what they can decode, "which formats can you handle?" becomes one more question whose answer identifies the asker. This post is about that surface: decode capability for images and media, read three different ways, one of which reaches down to your hardware.
Key Takeaways
- Format support is read at three layers: the HTTP
Acceptheader (no JavaScript), capability queries likecanPlayType()andisTypeSupported(), andMediaCapabilities.decodingInfo(), which addssmoothandpowerEfficient. - On its own it is coarse. Support tracks browser family and version so tightly that it mostly restates the User-Agent. Do not expect it to single you out.
- It earns its keep in two ways: as a contradiction check against a claimed UA, and through hardware-decode bits the UA never carries.
powerEfficientis the interesting field. It says whether a codec is hardware-decoded, which reflects your GPU, driver and OS — without WebGL and without a permission prompt.- It decays differently from other signals. Format support shifts on browser, OS and driver updates, not on the schedule of canvas or font changes.
Reading 1: The Accept Header
The first reading happens before any script runs. The request for a page or an image carries an Accept header that lists the image formats the browser will take. A recent Chromium build sends something like image/avif,image/webp,image/apng,image/svg+xml,image/*, while other engines send shorter or differently ordered lists. That is what the server sees on the very first request, and the HTTP-only fingerprinting post covers how the whole header set is read without JavaScript. The format list is a fast tell of engine and rough version, and nothing you install to block scripts touches it.
The reason it changes over time is simple: a browser adds a token when it ships a decoder. The MDN reference on image file types lists the formats and their MIME types, and Can I Use for AVIF and for JPEG XL show how unevenly support has landed. AVIF is now nearly universal; JPEG XL is still a split, which makes it a discriminating bit exactly because it has not converged.
Reading 2: Capability Queries in JavaScript
A script can ask directly instead of inferring from headers. HTMLMediaElement.canPlayType() takes a MIME type with an optional codec string and answers with "", "maybe" or "probably". MediaSource.isTypeSupported() does the same for streaming pipelines, and images can be probed by loading a tiny data-URI sample of each format and seeing whether it decodes.
const probes = [
'video/mp4; codecs="avc1.42E01E"',
'video/mp4; codecs="hev1.1.6.L93.B0"',
'video/webm; codecs="vp9"',
'video/mp4; codecs="av01.0.05M.08"',
'audio/ogg; codecs="opus"',
];
const v = document.createElement('video');
const vector = probes.map((p) => v.canPlayType(p) || 'no').join('|');
The result is a short string whose pattern varies with engine, version and operating system, because proprietary codecs such as HEVC often depend on the platform's media framework rather than the browser alone. Note that "maybe" versus "probably" is itself a difference between implementations, not just yes and no.
Reading 3: MediaCapabilities and the Hardware Bits
The third reading is the one that justifies a post of its own. navigator.mediaCapabilities.decodingInfo() takes a full configuration — codec, resolution, bitrate, frame rate — and resolves with three booleans: supported, smooth and powerEfficient.
const info = await navigator.mediaCapabilities.decodingInfo({
type: 'file',
video: {
contentType: 'video/mp4; codecs="hev1.1.6.L93.B0"',
width: 3840, height: 2160, bitrate: 20_000_000, framerate: 60,
},
});
// { supported: true, smooth: true, powerEfficient: true }
powerEfficient is effectively a statement about hardware decode. A machine with a GPU that decodes 4K HEVC or AV1 in silicon answers differently from one that falls back to software, and the answer depends on the GPU generation, the driver and the OS media stack. Probing a grid of codecs, sizes and frame rates produces a small hardware-flavored vector, which is information the User-Agent does not carry. It is the media-API sibling of the GPU signals covered in WebGPU fingerprinting and the cross-checks in fingerprint-hardware mismatch, and it needs no permission and no canvas.
How Much Entropy Is There, Honestly
Not much on its own. Two people running the same Chrome version on the same OS report nearly identical Accept lists and canPlayType vectors, so the software half of this surface mostly restates browser family and version. Say that plainly: format support is not a strong standalone identifier.
It becomes useful in two ways:
- Contradiction checks. A claimed Safari that cannot decode HEVC, or a claimed recent Chrome missing AV1, does not add up. Detection stacks use this alongside headless-browser detection, where missing codecs are a classic tell, and inside broader consistency checks.
- The hardware bits.
powerEfficientandsmoothacross a probe grid separate machines that share a browser build but not a GPU, which the UA cannot do.
One neighbouring surface is worth keeping distinct: WebRTC fingerprinting reads the codecs a browser advertises for real-time calls, whereas this post is about decode capability for stored and streamed media and images.
Stability: A Different Decay Curve
Canvas and font signals change when you install fonts or update a graphics stack. Format support moves when the browser ships or drops a decoder, when the OS updates its media framework, or when a driver update turns hardware decode on or off. That gives the signal a step-function pattern tied to update cycles. For someone trying to keep a stable identifier it is a mixed bag: reliable between updates, then abruptly different. For someone defending against tracking, an update changes this part of the profile, but because many users update at around the same time, it mostly moves you into a new crowd rather than making you stand out.
What You Can Do
There is no toggle for this. Format support is a functional feature, and lying about it breaks video and images. The practical options:
- Use a mainstream, up-to-date browser so your answers match a large crowd rather than a rare combination.
- Where available, prefer anti-fingerprinting modes that limit what media capability APIs reveal; they trade some capability for a smaller surface.
- Be suspicious of tools that spoof the UA without matching the decode profile — that mismatch is exactly what this surface catches.
To see what your own browser reports next to its other signals, run the fingerprint check.
FAQ
Is codec support a strong fingerprint? No. It is coarse, tracks browser version closely and mostly restates the User-Agent. Its value is in contradiction checks and the hardware-decode bits.
Does blocking JavaScript stop it?
Only the JavaScript readings. The Accept header is sent on the first request regardless.
Why is powerEfficient more revealing than supported?
supported follows the browser build, while powerEfficient follows your GPU, driver and OS.


