FileDummy Logo
FileDummy
File Upload & Handling

Multipart Upload to S3 and Cloudflare R2 with Node.js

Step-by-step guide to AWS S3 multipart uploads using the AWS SDK v3 in Node.js. Fully compatible with Cloudflare R2. Test with a free 500MB sample file.

September 20, 202611 min read1,420 views
nodejsaws-s3cloudflare-r2multipart-uploadbackend

For files larger than 100MB, uploading via a single PutObject HTTP request is error-prone. A single dropped packet can fail the entire upload. AWS S3 and Cloudflare R2 provide the S3 Multipart Upload API, allowing developers to upload independent parts concurrently and assemble them on the server.

S3 Multipart Upload Workflow

  1. Initiate: CreateMultipartUpload returns a unique UploadId.
  2. Upload Parts: Send 5MB to 500MB chunks using UploadPartCommand. Each part returns an ETag.
  3. Complete: Send CompleteMultipartUploadCommand with the ordered list of PartNumbers and ETags.
Verified Test Asset.pdf

Download our 500MB PDF sample file to test and benchmark your S3/R2 multipart upload pipeline.

Download 500MB PDF Sample →

Implementation with AWS SDK v3 in Node.js

TypeScript
import {
  S3Client,
  CreateMultipartUploadCommand,
  UploadPartCommand,
  CompleteMultipartUploadCommand,
  AbortMultipartUploadCommand,
} from '@aws-sdk/client-s3';
import fs from 'fs';

const s3 = new S3Client({
  region: 'auto',
  endpoint: process.env.R2_ENDPOINT, // e.g. https://<accountId>.r2.cloudflarestorage.com
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
  },
});

export async function uploadLargeFileToR2(filePath: string, bucket: string, key: string) {
  const PART_SIZE = 10 * 1024 * 1024; // 10MB per part (must be >= 5MB for S3/R2)
  const fileStats = fs.statSync(filePath);
  const fileStream = fs.createReadStream(filePath, { highWaterMark: PART_SIZE });

  // 1. Initiate Multipart Upload
  const init = await s3.send(
    new CreateMultipartUploadCommand({
      Bucket: bucket,
      Key: key,
    })
  );
  const uploadId = init.UploadId;

  const uploadedParts: { PartNumber: number; ETag: string }[] = [];
  let partNumber = 1;

  try {
    for await (const chunk of fileStream) {
      console.log(`Uploading part ${partNumber}...`);

      const partResult = await s3.send(
        new UploadPartCommand({
          Bucket: bucket,
          Key: key,
          UploadId: uploadId,
          PartNumber: partNumber,
          Body: chunk,
        })
      );

      uploadedParts.push({
        PartNumber: partNumber,
        ETag: partResult.ETag!,
      });

      partNumber++;
    }

    // 2. Complete Upload
    await s3.send(
      new CompleteMultipartUploadCommand({
        Bucket: bucket,
        Key: key,
        UploadId: uploadId,
        MultipartUpload: {
          Parts: uploadedParts.sort((a, b) => a.PartNumber - b.PartNumber),
        },
      })
    );

    console.log('Upload successfully completed!');
  } catch (error) {
    console.error('Upload failed. Aborting multipart upload...', error);
    await s3.send(
      new AbortMultipartUploadCommand({
        Bucket: bucket,
        Key: key,
        UploadId: uploadId,
      })
    );
    throw error;
  }
}

Critical Production Rules

  • Minimum part size: All parts except the final part must be at least 5MB (5,242,880 bytes). Submitting a part smaller than 5MB will throw EntityTooSmall.
  • Always handle aborts: Unfinished multipart parts remain in object storage and incur monthly storage fees until aborted. Set S3 / R2 Lifecycle Rules to automatically purge incomplete multipart uploads after 7 days.
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 minimum part size for S3 multipart uploads?â–¾

Each part (except the last) must be at least 5MB. Parts smaller than 5MB result in an EntityTooSmall error on CompleteMultipartUpload. Cloudflare R2 follows the same S3-compatible constraint.

How many parts can a single S3 multipart upload have?â–¾

A maximum of 10,000 parts per upload. With 5MB minimum part size, this gives a theoretical maximum object size of approximately 50GB.

How do I cancel a multipart upload?â–¾

Always call AbortMultipartUpload on failure or user cancellation. Incomplete parts accrue storage costs on both S3 and R2. Use S3 Lifecycle rules to auto-abort incomplete uploads after N days.

Related Articles