I was about to add an "Export as WebP" button to a small asset-export page I maintain for our editors. Before shipping it I wanted one boring fact: which formats can canvas.toBlob actually produce? The answer turned out to depend on the engine, and the failure mode is quiet. Ask for a format the engine can't encode and you get a PNG back. No exception, no null, no console warning.
How I checked
I ran the three engines that ship with Playwright 1.61.1: Chromium 149, WebKit 26.5 and Firefox 151, all headless on a Mac. A 64×48 canvas, 11 MIME types, and each one through toBlob, toDataURL and OffscreenCanvas.convertToBlob. I ignored whatever the file "should" be and read the first 16 bytes for the real magic number. For the WebP comparison I used ImgIng's format converter on the same engines, with a synthetic 2000×1500 test image I generated myself.
What each engine can encode
- Chromium 149: PNG, JPEG, WebP. That's it.
- Firefox 151: PNG, JPEG, WebP, plus BMP.
- WebKit 26.5: PNG, JPEG, AVIF, BMP, GIF, HEIC. Not WebP.
A few more from the list. image/jxl and image/svg+xml fell back to PNG on all three. So did image/jpg, the typo almost everyone makes once. Case doesn't matter, IMAGE/JPEG works fine. One caveat I want to be clear about: this is Playwright's WebKit build. I have not tested Safari itself or iOS, so I'm not claiming anything about them.
The only honest signal is blob.type
When the fallback happens, blob.type says image/png and the data URL prefix changes to data:image/png. That's the whole warning. If your code saves the result as photo.webp, the name is yours and the bytes are PNG. My first test only checked whether toBlob returned something, and all 11 types "passed". Reading the header is what exposed Chromium's AVIF, GIF and BMP and WebKit's WebP.
The one case where I saw toBlob return null was a 0×0 canvas, on all three engines. The three APIs agreed on every type, so switching to OffscreenCanvas doesn't help.
The fallback also costs bytes. Asking WebKit 26.5 for WebP gave me a 7.99 MB PNG, 19.8× the size of Chromium's default WebP of the same image.
Probing instead of sniffing the UA
ImgIng was a useful reference because it handles exactly this. With a few counting hooks installed before page load, I watched it call toBlob once per format (PNG, JPEG, WebP, AVIF) on a 4×4 canvas at startup and pick encoders from the real results. On Chromium and Firefox its WebP output was byte-identical to my own toBlob at 0.8 (404,244 and 404,558 bytes). On WebKit it loaded a self-hosted libwebp WASM build and produced a real 500,178-byte WebP.
What I'm changing in my own page
Check blob.type after every toBlob and treat a mismatch as a failure. Probe the formats you need once at startup with a tiny canvas, cache the answer, and only show options that actually work. Grep your code for image/jpg. AVIF I've left out for now: only this WebKit build encodes it natively, and I haven't worked out whether shipping an encoder is worth the download.

Top comments (0)