It took me a couple hours, but I got it working for both uploads and downloads with a nice progress bar. My uploadFile method is about 40 lines of formatted code, and my downloadFile method is about 28 lines. It's pretty simple once you figure it out!
Note that a key detail is that your server (and any intermediate servers, such as a reverse-proxy) must support HTTP/2 or QUIC. I spent much more time on that than the frontend code. In 2025, this isn't a problem for any modern client and hasn't been for a few years. However, that may not be true for your backend depending on how mature your codebase is. For example, Express doesn't support http/2 without another dependency. After fussing with it for a bit I threw it out and just used Fastify instead (built-in http/2 and high-level streaming). So I understand any apprehension/reservations there.
Overall, I'm pretty satisfied knowing that fetch has wide support for easy progress tracking.
can we see a gist?
https://github.com/hu0p/fetch-transfer-progress-demo/
Which browsers have you tested this in? I ran the feature detection script from the Chrome docs and neither Safari nor Firefox seem to support fetch upload streaming: https://developer.chrome.com/docs/capabilities/web-apis/fetc...
Safari doesn't appear to support the duplex option (the duplex getter is never triggered), and Firefox can't even handle a stream being used as the body of a Request object, and ends up converting the body to a string, and then setting the content type header to 'text/plain'.Oops. Chrome only! I stand very much corrected. Perhaps I should do less late night development.
It seems my original statement that download, but not upload, is well supported was unfortunately correct after all. I had thought that readable/transform streams were all that was needed, but as you noted it seems I've overlooked the important lack of duplex option support in Safari/Firefox[0][1]. This is definitely not wide support! I had way too much coffee.
Thank you for bringing this to my attention! After further investigation, I encountered the same problem as you did as well. Firefox failed for me exactly as you noted. Interestingly, Safari fails silently if you use a transformStream with file.stream().pipeThrough([your transform stream here]) but it fails with a message noting lack of support if you specifically use a writable transform stream with file.stream().pipeTo([writable transform stream here]).
I came across the article you referenced but of course didn't completely read it. It's disappointing that it's from 2020 and no progress has been made on this. Poking around caniuse, it looks like Safari and Firefox have patchy support for similar behavior in web workers, either via partial support or behind flags. So I suppose there's hope, but I'm sorry if I got anyone's hope too far up :(
[0] https://caniuse.com/mdn-api_fetch_init_duplex_parameter [1] https://caniuse.com/mdn-api_request_duplex