Upload objects·续

const S3 = new S3Client({ region: "auto", endpoint: https://<ACCOUNT_ID>.r2.cloudflarestorage.com, credentials: { accessKeyId: "<ACCESS_KEY_ID>", secretAccessKey: "<SECRET_ACCESS_KEY>", }, });

const upload = new Upload({ client: S3, params: { Bucket: "my-bucket", Key: "large-file.bin", Body: createReadStream("./large-file.bin"), }, // Upload parts in parallel (default: 4) leavePartsOnError: false, });

upload.on("httpUploadProgress", (progress) => { console.log(Uploaded ${progress.loaded ?? 0} bytes); });

const result = await upload.done(); console.log(Upload complete. ETag: ${result.ETag});


```js
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";

const S3 = new S3Client({
    region: "auto",
    endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
    credentials: {
        accessKeyId: "&lt;ACCESS_KEY_ID>",
        secretAccessKey: "&lt;SECRET_ACCESS_KEY>",
    },
});

const upload = new Upload({
    client: S3,
    params: {
        Bucket: "my-bucket",
        Key: "large-file.bin",
        Body: createReadStream("./large-file.bin"),
    },
    leavePartsOnError: false,
});

upload.on("httpUploadProgress", (progress) => {
    console.log(`Uploaded ${progress.loaded ?? 0} bytes`);
});

const result = await upload.done();
console.log(`Upload complete. ETag: ${result.ETag}`);
import boto3

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://&lt;ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="&lt;ACCESS_KEY_ID>",
    aws_secret_access_key="&lt;SECRET_ACCESS_KEY>",
    region_name="auto",
)

# upload_file automatically uses multipart for large files.
# For better throughput with large objects, use the manual multipart example below.
s3.upload_file(
    Filename="./large-file.bin",
    Bucket="my-bucket",
    Key="large-file.bin",
)

Manual multipart upload

Use the low-level API when you need full control over part sizes or upload order.

import {
    S3Client,
    CreateMultipartUploadCommand,
    UploadPartCommand,
    CompleteMultipartUploadCommand,
    AbortMultipartUploadCommand,
    type CompletedPart,
} from "@aws-sdk/client-s3";
import { createReadStream, statSync } from "node:fs";

const S3 = new S3Client({
    region: "auto",
    endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
    credentials: {
        accessKeyId: "&lt;ACCESS_KEY_ID>",
        secretAccessKey: "&lt;SECRET_ACCESS_KEY>",
    },
});

const bucket = "my-bucket";
const key = "large-file.bin";
const partSize = 10 * 1024 * 1024; // 10 MiB per part

// Step 1: Create the multipart upload
const { UploadId } = await S3.send(
    new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),
);

try {
    const fileSize = statSync("./large-file.bin").size;
    const partCount = Math.ceil(fileSize / partSize);
    const parts: CompletedPart[] = [];

// Step 2: Upload each part
    for (let i = 0; i < partCount; i++) {
        const start = i * partSize;
        const end = Math.min(start + partSize, fileSize);
        const { ETag } = await S3.send(
            new UploadPartCommand({
                Bucket: bucket,
                Key: key,
                UploadId,
                PartNumber: i + 1,
                Body: createReadStream("./large-file.bin", { start, end: end - 1 }),
                ContentLength: end - start,
            }),
        );
        parts.push({ PartNumber: i + 1, ETag });
    }

// Step 3: Complete the upload
    await S3.send(
        new CompleteMultipartUploadCommand({
            Bucket: bucket,
            Key: key,
            UploadId,
            MultipartUpload: { Parts: parts },
        }),
    );
    console.log("Multipart upload complete.");
} catch (err) {
    // Abort on failure to clean up incomplete parts
    try {
        await S3.send(
            new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId }),
        );
    } catch (_abortErr) {
        // Best-effort cleanup — the original error is more important
    }
    throw err;
}
import {
    S3Client,
    CreateMultipartUploadCommand,
    UploadPartCommand,
    CompleteMultipartUploadCommand,
    AbortMultipartUploadCommand,
} from "@aws-sdk/client-s3";
import { createReadStream, statSync } from "node:fs";

const S3 = new S3Client({
    region: "auto",
    endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
    credentials: {
        accessKeyId: "&lt;ACCESS_KEY_ID>",
        secretAccessKey: "&lt;SECRET_ACCESS_KEY>",
    },
});

const bucket = "my-bucket";
const key = "large-file.bin";
const partSize = 10 * 1024 * 1024; // 10 MiB per part

// Step 1: Create the multipart upload
const { UploadId } = await S3.send(
    new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),
);

try {
    const fileSize = statSync("./large-file.bin").size;
    const partCount = Math.ceil(fileSize / partSize);
    const parts = [];

// Step 2: Upload each part
    for (let i = 0; i < partCount; i++) {
        const start = i * partSize;
        const end = Math.min(start + partSize, fileSize);
        const { ETag } = await S3.send(
            new UploadPartCommand({
                Bucket: bucket,
                Key: key,
                UploadId,
                PartNumber: i + 1,
                Body: createReadStream("./large-file.bin", { start, end: end - 1 }),
                ContentLength: end - start,
            }),
        );
        parts.push({ PartNumber: i + 1, ETag });
    }

// Step 3: Complete the upload
    await S3.send(
        new CompleteMultipartUploadCommand({
            Bucket: bucket,
            Key: key,
            UploadId,
            MultipartUpload: { Parts: parts },
        }),
    );
    console.log("Multipart upload complete.");
} catch (err) {
    // Abort on failure to clean up incomplete parts
    try {
        await S3.send(
            new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId }),
        );
    } catch (_abortErr) {
        // Best-effort cleanup — the original error is more important
    }
    throw err;
}
import boto3
import math
import os
from concurrent.futures import ThreadPoolExecutor

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://&lt;ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="&lt;ACCESS_KEY_ID>",
    aws_secret_access_key="&lt;SECRET_ACCESS_KEY>",
    region_name="auto",
)

bucket = "my-bucket"
key = "large-file.bin"
file_path = "./large-file.bin"
part_size = 16 * 1024 * 1024  # 16 MiB per part
max_workers = 10  # Number of parallel upload threads

# Step 1: Create the multipart upload
upload_id = None
mpu = s3.create_multipart_upload(Bucket=bucket, Key=key)
upload_id = mpu["UploadId"]

def upload_part(part_number, data):
    response = s3.upload_part(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        PartNumber=part_number,
        Body=data,
    )
    return {"PartNumber": part_number, "ETag": response["ETag"]}

try:
    file_size = os.path.getsize(file_path)
    part_count = math.ceil(file_size / part_size)

# Step 2: Upload parts in parallel
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        futures = []
        with open(file_path, "rb") as f:
            for i in range(part_count):
                data = f.read(part_size)
                futures.append(pool.submit(upload_part, i + 1, data))

parts = [future.result() for future in futures]

# Step 3: Complete the upload
    s3.complete_multipart_upload(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        MultipartUpload={"Parts": parts},
    )
    print("Multipart upload complete.")
except Exception:
    # Abort on failure to clean up incomplete parts
    if upload_id:
        try:
            s3.abort_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id)
        except Exception:
            pass  # Best-effort cleanup — the original error is more important
    raise

Presigned URLs (S3 API)

For client-side uploads where users upload directly to R2 without going through your server, generate a presigned PUT URL. Your server creates the URL and the client uploads to it — no API credentials are exposed to the client.

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const S3 = new S3Client({
    region: "auto",
    endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
    credentials: {
        accessKeyId: "&lt;ACCESS_KEY_ID>",
        secretAccessKey: "&lt;SECRET_ACCESS_KEY>",
    },
});

const presignedUrl = await getSignedUrl(
    S3,
    new PutObjectCommand({
        Bucket: "my-bucket",
        Key: "user-upload.png",
        ContentType: "image/png",
    }),
    { expiresIn: 3600 }, // Valid for 1 hour
);

console.log(presignedUrl);
// Return presignedUrl to the client
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const S3 = new S3Client({
    region: "auto",
    endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
    credentials: {
        accessKeyId: "&lt;ACCESS_KEY_ID>",
        secretAccessKey: "&lt;SECRET_ACCESS_KEY>",
    },
});

const presignedUrl = await getSignedUrl(
    S3,
    new PutObjectCommand({
        Bucket: "my-bucket",
        Key: "user-upload.png",
        ContentType: "image/png",
    }),
    { expiresIn: 3600 }, // Valid for 1 hour
);

console.log(presignedUrl);
// Return presignedUrl to the client
import boto3

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://&lt;ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="&lt;ACCESS_KEY_ID>",
    aws_secret_access_key="&lt;SECRET_ACCESS_KEY>",
    region_name="auto",
)

presigned_url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "my-bucket",
        "Key": "user-upload.png",
        "ContentType": "image/png",
    },
    ExpiresIn=3600,  # Valid for 1 hour
)

print(presigned_url)
# Return presigned_url to the client

For full presigned URL documentation, refer to Presigned URLs.

Refer to R2's S3 API documentation for all supported S3 API methods.

Upload via CLI

Rclone

Rclone ↗︎ is a command-line tool for managing files on cloud storage. Rclone works well for uploading multiple files from your local machine or copying data from other cloud storage providers.

To use rclone, install it onto your machine using their official documentation - Install rclone ↗︎.

Upload files with the rclone copy command:

# Upload a single file
rclone copy /path/to/local/image.png r2:bucket_name

# Upload everything in a directory
rclone copy /path/to/local/folder r2:bucket_name

Verify the upload with rclone ls:

rclone ls r2:bucket_name

For more information, refer to our rclone example.

Wrangler

Note

Wrangler supports uploading files up to 315 MB and only allows one object at a time. For large files or bulk uploads, use rclone or another S3-compatible tool.

Use Wrangler to upload objects. Run the r2 object put command:

wrangler r2 object put test-bucket/image.png --file=image.png

You can set the Content-Type (MIME type), Content-Disposition, Cache-Control and other HTTP header metadata through optional flags.

Multipart upload details

Part size limits

  • Minimum part size: 5 MiB (except for the last part)
  • Maximum part size: 5 GiB
  • Maximum number of parts: 10,000
  • All parts except the last must be the same size

Incomplete upload lifecycles

Incomplete multipart uploads are automatically aborted after 7 days by default. You can change this by configuring a custom lifecycle policy.

ETags

ETags for objects uploaded via multipart differ from those uploaded with a single PUT. The ETag of each part is the MD5 hash of that part's contents. The ETag of the completed multipart object is the hash of the concatenated binary MD5 sums of all parts, followed by a hyphen and the number of parts.

For example, if a two-part upload has part ETags bce6bf66aeb76c7040fdd5f4eccb78e6 and 8165449fc15bbf43d3b674595cbcc406, the completed object's ETag will be f77dc0eecdebcd774a2a22cb393ad2ff-2.

Workers API reference

Full reference for the R2 Workers API including put(), createMultipartUpload(), and more.

S3 API compatibility

Supported S3 API operations and R2-specific behavior.

Presigned URLs

Generate temporary upload and download URLs for client-side access.

Object lifecycles

Configure automatic cleanup of incomplete multipart uploads.

Was this helpful?

YesNo

On this page

Upload objects·续 - 图1Docs

{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/r2/objects/upload-objects/#page","headline":"Upload objects","description":"Upload objects to R2 using single-part or multipart uploads via the dashboard, Workers API, or S3 API.","url":"https://developers.cloudflare.com/r2/objects/upload-objects/","inLanguage":"en","image":"https://developers.cloudflare.com/r2/objects/upload-objects/og.png?v=fca7840785e7dbcc","dateModified":"2026-07-29","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}


description: Access R2 storage through the S3-compatible API, Workers API, or Cloudflare REST API. title: API image: https://developers.cloudflare.com/r2/api/og.png?v=8b2b61c7a71f27e4


Skip to content

Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/r2/llms.txt
Use this file to discover all available pages before exploring further.