Cloudflare R2 storage provider

Cloudflare R2 provider implementing storage.ProviderPort by wrapping the S3 provider against R2's S3-compatible endpoint.

Overview

R2 is Cloudflare's S3-compatible object store with one defining feature, zero egress fees. You pay for storage and per-operation costs, and pay nothing for bandwidth out of the bucket. That one property tends to decide the choice for workloads that serve files publicly. Common cases include image CDNs, static site assets, downloadable artefacts, video manifests, and large dataset distribution.

The provider is a thin wrapper around the S3 provider. It builds a storage_provider_s3.Config with four fields pinned, the https://<account-id>.r2.cloudflarestorage.com endpoint, region auto, DisableChecksum: true, and UsePathStyle: false, then returns storage_provider_s3.NewS3Provider verbatim. The only R2-specific logic is the endpoint format and the account ID check. Disabling the checksum turns off both request checksum calculation and response checksum validation, because R2 rejects the trailing checksums the AWS SDK adds by default.

There are no new code paths. R2 inherits every S3 capability and every future S3 fix for free. Multipart uploads, presigned upload and download URLs, server-side copy, cross-repository copy, and native batch deletes all work because the underlying client is the AWS SDK pointed at R2. The returned provider satisfies the same storage.ProviderPort contract as every other provider, so it drops into WithStorageProvider and WithDefaultStorageProvider with no adapter glue, and exposes the same capability probes (SupportsMultipart, SupportsPresignedURLs, SupportsBatchOperations, SupportsRateLimiting).

The provider is pure Go. It needs no build tag, no CGO, and no system library, so it runs identically across all run modes, including the interpreted dev-i mode.

Requirements

  • A Cloudflare account with R2 enabled and at least one bucket created beforehand.
  • An R2 API token producing an Access Key ID and Secret Access Key. Generate it from the Cloudflare dashboard, under R2 then Manage R2 API Tokens.
  • The Cloudflare account ID (visible in the R2 dashboard URL or the account home page).
  • Network egress to *.r2.cloudflarestorage.com.

Configuration

import (
    "os"

    "piko.sh/piko/wdk/storage/storage_provider_r2"
)

provider, err := storage_provider_r2.NewR2Provider(ctx, &storage_provider_r2.Config{
    RepositoryMappings: map[string]string{ // required, maps logical repo to bucket
        "uploads": "myapp-uploads",
        "assets":  "myapp-assets",
    },
    AccountID: os.Getenv("CF_ACCOUNT_ID"), // required, your Cloudflare account ID
    AccessKey: os.Getenv("R2_ACCESS_KEY"), // static credentials, from the R2 API token
    SecretKey: os.Getenv("R2_SECRET_KEY"), // static credentials, from the R2 API token
})
if err != nil {
    return err
}

The constructor validates only the config pointer and AccountID. It returns an error when either is missing. AccessKey and SecretKey are not validated. When you supply both, the S3 layer uses them as static credentials. When either is empty, it falls back to the AWS default credential chain (environment variables, shared config files, and instance metadata), which suits deployments that inject credentials through the environment.

The provider applies the S3 default rate limit, 100 calls per second with a burst of 200. The constructor accepts variadic storage.ProviderOption values for rate-limit overrides, but the option constructors are internal to the storage package, so the inherited default is the effective limit from user code.

Bootstrap

ssr := piko.New(
    piko.WithStorageProvider("r2", provider),
    piko.WithDefaultStorageProvider("r2"),
)

Tradeoffs

R2's zero-egress pricing is the headline win, but the surface is narrower than mature S3. R2 lacks native lifecycle policies as rich as S3's. It has no Object Lock equivalent. It has less third-party tooling integration. The ecosystem of pre-built integrations (analytics, replication, disaster recovery) is younger. For workloads dominated by storage and operations costs (write-heavy, low-read), R2 may not actually be cheaper than S3. Do the maths against your access pattern before assuming the egress-free win covers everything.

See also

Other storage providers:

  • Amazon S3, the underlying SDK; richest ecosystem, paid egress.
  • Google Cloud Storage, GCP-native object storage with strong consistency.
  • Disk, local filesystem storage for development and single-node deployments.

Storage transformers:

Framework docs:

External: