You need: storage composed.
Why multipart at all
Letting users upload is the single-part version, the object model is why every part lands under one key, quotas and cleanup is what an abandoned multipart costs, and the reference has the sizes.
A single presigned PUT is capped at 5 GiB, and it is not resumable. A dropped connection at 4 GiB starts over.
Multipart raises the ceiling and makes a lost client cheap.
The defaults
| Threshold | 100 MiB — above this an upload goes multipart |
| Part size | 64 MiB |
| Minimum part | 5 MiB, every part but the last |
| Maximum part | 5 GiB |
| Parts per upload | 10,000 — R2’s hard cap |
Below the threshold, one presigned PUT is simpler and cheaper.
The ceiling those imply
10,000 × 64 MiB ≈ 625 GiB per object.
That is well short of R2’s own object cap. Storing objects larger than that means raising partSizeBytes, which gets you a proportionally higher ceiling.
The 5 MiB floor is S3’s rule, not the kit’s
R2 rejects a completion whose non-final part is smaller.
Enforcing it at plan time means the failure lands with a config-shaped message, rather than after the client has already spent bandwidth on every part.
Resuming
GET /storage/<id>/partsRe-lists what R2 actually holds and re-presigns the rest. The client sends only what is missing.
The server holds no transfer state beyond the pending row — which is what makes resume free rather than a feature with its own bookkeeping.
POST /storage/<id>/complete
POST /storage/<id>/abortQuota still applies
The pending row reserves the declared size the moment it is written, so a multipart upload of 300 GiB is reserved against the owner’s quota from the first part, not the last.
An abandoned multipart upload past its TTL is reclaimed by the sweep — and emptying a bucket on teardown deletes the parts of an incomplete upload too, because R2 refuses to delete a bucket that still holds them.
Serving a big file back
GET /storage/<id>Range requests are supported. The download response sets the headers that stop a browser executing what it got, whatever the stored content type says.
For a file that should be reachable by link rather than by session, see sharing files.
When it is a video
A large video usually wants media rather than raw storage — Cloudflare Stream ingests, transcodes and serves adaptive HLS, and the enrichment path reads its audio rendition to transcribe it.
Storage is the right layer when you need the bytes back exactly as they went in.