Overview

Cloudflare R2 is a distributed object storage service that provides a platform for storing unstructured data, such as images, videos, logs, and backups. Launched in 2022, R2 distinguishes itself by offering an S3-compatible API, which allows developers to integrate it into existing workflows and applications that already use Amazon S3 or other S3-compatible services with minimal code changes. This compatibility extends to the use of AWS SDKs for various programming languages, simplifying migration and adoption.

One of the primary design goals of R2 is to eliminate egress fees, which are charges incurred when data is transferred out of a cloud provider's network. This approach aims to reduce the total cost of ownership for applications with high data transfer requirements, such as those serving static assets, media content, or user-generated data globally. By distributing data across Cloudflare's global network, R2 is designed to reduce latency for end-users by serving content from locations geographically closer to them.

R2 is often utilized in conjunction with Cloudflare Workers, a serverless execution environment. This integration enables developers to build serverless applications that can store and retrieve data directly at the edge, reducing round-trip times and improving application responsiveness. Use cases for R2 include hosting static websites, serving large media files, storing application backups, and providing storage for serverless functions. Its architecture is built to support applications requiring high availability and durability, with data replicated across multiple locations to protect against data loss.

The service offers two classes of operations: Class A operations for write-intensive actions like listing buckets or uploading objects, and Class B operations for read-intensive actions such as downloading objects. This tiered operational model, combined with its storage pricing, aims to provide a transparent cost structure for various data access patterns. For developers, the Cloudflare R2 developer documentation provides guides and examples for common tasks, including setup, data management, and integration with Cloudflare Workers.

Key features

  • S3-Compatible API: Allows the use of existing AWS SDKs and tools, simplifying migration and integration for applications built to interact with Amazon S3.
  • No Egress Fees: Eliminates charges for data transfer out of the R2 network, designed to reduce costs for applications with high data egress.
  • Global Distribution: Data is stored and replicated across Cloudflare's global network, aiming to improve data availability and reduce latency for users worldwide.
  • Serverless Integration: Seamlessly integrates with Cloudflare Workers, enabling serverless applications to interact with stored data directly at the edge.
  • Access Control: Supports access policies, including public access, private access, and integration with Cloudflare's authentication mechanisms for secure data management.
  • Data Durability: Designed for high data durability through replication across multiple data centers.
  • Free Tier: Provides a free usage tier for storage and operations, allowing developers to test and deploy small-scale applications without initial costs.
  • Compliance Standards: Adheres to compliance standards such as SOC 2 Type II, GDPR, ISO 27001, ISO 27701, and PCI DSS Level 1, supporting regulatory requirements.

Pricing

Cloudflare R2 offers a free tier and then charges based on storage consumed and operations performed. There are no egress fees.

Service Component Free Tier Paid Tier (as of 2026-06-16)
Storage First 10 GB-months $0.015 per GB-month
Class A Operations (write-intensive) First 1,000,000 operations $4.50 per 1,000,000 operations
Class B Operations (read-intensive) First 10,000,000 operations $0.36 per 1,000,000 operations
Egress Fees None None

For detailed and up-to-date pricing information, refer to the Cloudflare R2 pricing page.

Common integrations

  • Cloudflare Workers: Enables direct interaction with R2 buckets from serverless functions deployed on Cloudflare's edge network. Cloudflare Workers R2 examples.
  • AWS SDKs: Supports standard AWS SDKs for various languages (JavaScript, Python, Go, Java, .NET, PHP, Ruby) due to its S3-compatible API. R2 S3 API reference.
  • cURL: Command-line tool for direct HTTP requests, useful for scripting and testing R2 API interactions. R2 API calls with cURL.
  • S3-compatible tools: Integrates with third-party tools and libraries designed for S3, such as S3 clients, backup tools, and data migration utilities.

Alternatives

  • Amazon S3: A widely adopted object storage service offering high scalability, durability, and a broad ecosystem of integrated AWS services.
  • Google Cloud Storage: Google's object storage service, providing various storage classes, data lifecycle management, and integration with Google Cloud Platform services.
  • Azure Blob Storage: Microsoft Azure's object storage solution for massively scalable and secure data storage, integrated with the Azure ecosystem.

Getting started

To get started with Cloudflare R2, you typically create a bucket and then interact with it using an S3-compatible client or the Cloudflare Workers API. The following example demonstrates how to upload and download an object using the AWS SDK for JavaScript v3 within a Node.js environment.

First, install the necessary AWS SDK package:


npm install @aws-sdk/client-s3

Next, configure your R2 endpoint and credentials. These credentials (Access Key ID and Secret Access Key) can be generated in the Cloudflare dashboard under your R2 settings. The endpoint URL will be specific to your R2 bucket. For a more detailed guide on setting up credentials, consult the Cloudflare R2 S3 API compatibility documentation.


import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { createWriteStream } from "node:fs";

const R2_ACCOUNT_ID = process.env.R2_ACCOUNT_ID; // Your Cloudflare Account ID
const R2_ACCESS_KEY_ID = process.env.R2_ACCESS_KEY_ID; // Your R2 Access Key ID
const R2_SECRET_ACCESS_KEY = process.env.R2_SECRET_ACCESS_KEY; // Your R2 Secret Access Key
const R2_BUCKET_NAME = "my-r2-bucket"; // Your R2 bucket name

const s3Client = new S3Client({
  region: "auto", // R2 uses 'auto' region
  endpoint: `https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: R2_ACCESS_KEY_ID,
    secretAccessKey: R2_SECRET_ACCESS_KEY,
  },
});

async function uploadFile(key, body) {
  try {
    const command = new PutObjectCommand({
      Bucket: R2_BUCKET_NAME,
      Key: key,
      Body: body,
    });
    await s3Client.send(command);
    console.log(`Successfully uploaded ${key}`);
  } catch (error) {
    console.error(`Error uploading ${key}:`, error);
  }
}

async function downloadFile(key, outputPath) {
  try {
    const command = new GetObjectCommand({
      Bucket: R2_BUCKET_NAME,
      Key: key,
    });
    const { Body } = await s3Client.send(command);

    if (Body) {
      const writeStream = createWriteStream(outputPath);
      // @ts-ignore - Body is readable stream
      Body.pipe(writeStream);
      await new Promise((resolve, reject) => {
        writeStream.on("finish", resolve);
        writeStream.on("error", reject);
      });
      console.log(`Successfully downloaded ${key} to ${outputPath}`);
    } else {
      console.log(`File ${key} not found.`);
    }
  } catch (error) {
    console.error(`Error downloading ${key}:`, error);
  }
}

async function generatePresignedUrl(key) {
  try {
    const command = new GetObjectCommand({
      Bucket: R2_BUCKET_NAME,
      Key: key,
    });
    const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); // URL valid for 1 hour
    console.log(`Presigned URL for ${key}: ${url}`);
  } catch (error) {
    console.error(`Error generating presigned URL for ${key}:`, error);
  }
}

// Example usage:
(async () => {
  const fileContent = "Hello, Cloudflare R2!";
  const fileName = "my-test-object.txt";
  const downloadPath = "./downloaded-file.txt";

  await uploadFile(fileName, fileContent);
  await downloadFile(fileName, downloadPath);
  await generatePresignedUrl(fileName);
})();