Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
R2 Storage and the Developer Toolchain  ›  Lesson

Large Files — Stream, Split or Sign

Large File Handling 15 minLarge files are handled by streaming bodies instead of buffering, splitting uploads into multipart parts, or issuing presigned URLs so the browser uploads straight to R2.

Large Files — Stream, Split or Sign

A Hoplink user tries to share a 2 GB conference video, and the upload fails. Nothing is wrong with R2: an object can be far bigger than that. The problem is the path the bytes take. A request passing through a Worker has a body size limit (100 MB on the Free and Pro plans), and a Worker has only 128 MB of memory. Big files need a different route.

The rule

Stream what fits; split what is big; sign what bypasses the Worker.

Three techniques, in order of file size:

1. Stream. Never load a file into memory with await request.arrayBuffer(). Pass the stream straight through: env.FILES.put(key, request.body) for uploads and new Response(object.body) for downloads. Memory stays small no matter the file.

2. Split (multipart upload). Break the file into parts, upload each separately, then stitch them together:

const upload = await env.FILES.createMultipartUpload("talks/keynote.mp4");

// each part arrives in its own request; every part but the last must be at least 5 MiB
const part1 = await upload.uploadPart(1, firstChunk);
const part2 = await upload.uploadPart(2, secondChunk);

await upload.complete([part1, part2]);

Parts can upload in parallel, and a failed part can be retried alone instead of restarting the whole file. upload.abort() cancels it. The browser keeps track of the uploadId and part numbers between requests; the Worker resumes with env.FILES.resumeMultipartUpload(key, uploadId).

3. Sign (presigned URLs). Because R2 speaks the S3 API, your Worker can create a short-lived signed URL with an S3 client library and the bucket's access keys. The browser then uploads directly to R2, so the Worker's body limit never applies. The Worker only decides who may upload what, and for how long.

Downloads of large files benefit from range requests: env.FILES.get(key, { range: { offset, length } }) returns just one slice, which lets video players seek without fetching the whole file.

The shortcut

Decide by size, as a ladder:

Choosing an upload route for HoplinkStep 1 of 4
  1. Small (a profile photo, 2 MB)

    Stream request.body into env.FILES.put. Done in one request.

Trap: abandoned multipart uploads. Parts that were uploaded but never completed still take up storage until the upload is aborted or cleaned up — the subject of the next lesson.

Drills

Answer fast — trust the rule.

quick drill

Why is const data = await request.arrayBuffer() a bad idea for a 90 MB upload?

quick drill

In a multipart upload, what is the minimum size of every part except the last?

quick drill

What is the main benefit of a presigned upload URL?

quick drill

A video player wants to jump to minute 40 of a large file. Which read helps?

Remember

  • Stream what fits; split what is big; sign what bypasses the Worker.
  • Shortcut: small → stream, big → multipart, huge → presigned URL.
  • Trap: loading whole files into memory, and leaving multipart uploads unfinished.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainPick the upload route

Hoplink users upload a 3 MB photo, a 400 MB recording and a 4 GB video. Choose an upload approach for each and explain why the obvious single request through the Worker fails for the big ones.