FileDummy Logo
FileDummy
File Upload & Handling

How to Upload Large Files in Chunks with JavaScript

Learn chunked file upload with JavaScript and the Fetch API. Includes working code, error handling, and free 100MB test files to validate your implementation.

September 14, 202610 min read1,420 views
javascriptfile-uploadfetch-apifrontend

Uploading large files (100MB to 5GB+) over the web presents significant engineering challenges: connection timeouts, memory spikes in browser tabs, and lost progress on transient network dropouts. Slicing files into sequential binary chunks using the browser File API solves these issues.

Understanding the Chunked Upload Architecture

The HTML5 File interface extends Blob, which gives you access to the slice(start, end) method. Slicing does not read the entire file into memory; it creates a reference pointer to byte offsets on disk.

Code
[------------- 100MB File Object on Disk -------------]
  |-- Chunk 0 (0 to 5MB)      --> POST /upload?chunk=0
  |-- Chunk 1 (5 to 10MB)     --> POST /upload?chunk=1
  |-- Chunk 2 (10 to 15MB)    --> POST /upload?chunk=2
  |-- ...
  +-- Complete Signal         --> POST /upload/complete
Verified Test Asset.pdf

Download a real 100MB test PDF to test your chunked upload logic without generating synthetic files.

Download 100MB PDF Sample →

Complete Client-Side Implementation

Here is a production-ready JavaScript implementation with chunk retry logic and progress calculation:

TypeScript
interface UploadOptions {
  file: File;
  endpoint: string;
  chunkSize?: number; // default 5MB
  onProgress?: (percent: number) => void;
}

async function uploadFileInChunks({
  file,
  endpoint,
  chunkSize = 5 * 1024 * 1024,
  onProgress,
}: UploadOptions): Promise<void> {
  const totalChunks = Math.ceil(file.size / chunkSize);
  const uploadId = crypto.randomUUID();

  for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
    const start = chunkIndex * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunkBlob = file.slice(start, end);

    let success = false;
    let retries = 3;

    while (!success && retries > 0) {
      try {
        const formData = new FormData();
        formData.append('file', chunkBlob, file.name);
        formData.append('uploadId', uploadId);
        formData.append('chunkIndex', String(chunkIndex));
        formData.append('totalChunks', String(totalChunks));

        const res = await fetch(endpoint, {
          method: 'POST',
          body: formData,
        });

        if (!res.ok) throw new Error(`Upload failed with status ${res.status}`);
        success = true;

        if (onProgress) {
          const percent = Math.round(((chunkIndex + 1) / totalChunks) * 100);
          onProgress(percent);
        }
      } catch (err) {
        retries--;
        if (retries === 0) throw err;
        await new Promise((r) => setTimeout(r, 1000 * (4 - retries))); // Exponential backoff
      }
    }
  }
}

Backend Assembly Considerations

  • Storage for partial chunks: Store incoming parts in a temporary staging directory or cloud bucket prefix (tmp/uploads/<uploadId>/).
  • Atomic concatenation: In Node.js, use readable and writable streams with fs.createWriteStream({ flags: 'a' }) to append chunks sequentially without loading them all into memory.
  • Orphan chunk cleanup: Configure lifecycle policies or cron tasks to remove abandoned partial uploads older than 24 hours.
NDL

Nguyen Dai Long

Author

Backend Lead • Distributed Systems & Cloud Edge Architecture Specialist

4+ years designing high-throughput file ingestion pipelines, database architectures, and distributed edge storage on Cloudflare R2 & AWS S3. Founder of FileDummy and the NDL Ecosystem.

Was this article helpful?

Click Like to support the author and help other developers discover this guide.

Engineering Discussion & Feedback (0)

Share benchmark results, report edge cases, or ask technical questions.

0/3000

No comments yet. Be the first developer to start the discussion!

Frequently Asked Questions

What is the ideal chunk size for chunked file uploads?â–¾

Most implementations use 1MB–5MB chunks. Smaller chunks (1MB) reduce memory pressure. Larger chunks (5MB) reduce HTTP overhead. For AWS S3 and Cloudflare R2 multipart uploads, the minimum part size is 5MB (except the last part).

How do I resume a failed chunked upload?â–¾

Track the last successfully uploaded chunk index in localStorage or server-side state. On retry, start from the last failed chunk. The tus protocol handles this automatically with server-side state management.

Can I use chunked uploads directly to S3 or Cloudflare R2?â–¾

Yes. Both natively support Multipart Upload API — each part is a separate PUT request. The upload is finalized with a CompleteMultipartUpload call. See our S3/R2 multipart upload article for the full Node.js implementation.

Related Articles