Cloudflare R2 Integration
Cloudflare R2 is an S3-compatible object storage service with zero egress fees. Sveltia CMS supports R2 as a media storage backend with direct browser-to-R2 uploads using AWS Signature Version 4 — no backend proxy is required.
Requirements
- A Cloudflare account with an R2 bucket created.
- An R2 API token with Object Read & Write permissions (see Credentials below).
- A
public_urlconfigured for asset previews (see Public Read Access below).
CSP
If your site uses a Content Security Policy (CSP), you need to allow the R2 endpoint and your public URL. See Content Security Policy below for details.
Setup
Credentials
R2 uses its own API token system, separate from the Cloudflare global API key. Create a token via Cloudflare Dashboard > Account Home > R2 > Manage R2 API Tokens:
- Permission: Object Read & Write
- Scope: Restrict to the specific bucket (recommended)
The resulting Access Key ID (64 hex characters) goes in access_key_id in your config. The Secret Access Key is entered by users in the CMS UI when they access the media library for the first time — it is never stored in config.
Public Read Access
R2's S3 API endpoint always requires authentication, so a separate public_url must be configured for asset previews and downloads in the CMS. Without it, preview images will fail to load.
Two options are available:
Option A — Public development URL (non-production):
In R2 Dashboard > [bucket] > Settings, under Public Development URL, click Enable and confirm.
Copy the assigned
pub-{hash}.r2.devURL and set it aspublic_urlin your config:yamlpublic_url: 'https://pub-abcdef1234567890abcdef1234567890.r2.dev'
Note: the r2.dev subdomain is rate limited and intended for development use only.
Option B — Custom domain (recommended for production):
In R2 Dashboard > [bucket] > Settings > Custom Domains, click Add and follow the prompts to connect your domain.
Set that domain as
public_urlin your config:yamlpublic_url: 'https://media.example.com'
In both cases, asset URLs are constructed as {public_url}/{key} (using the full object key, including any prefix).
CORS
Configure via R2 Dashboard > Bucket > Settings > CORS Policy. CORS is required because Sveltia CMS sends custom AWS Signature v4 headers that trigger a preflight request.
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "HEAD"],
"AllowedOrigins": ["https://your-cms-domain.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]Configuration
Here’s an example configuration for Cloudflare R2:
media_libraries:
cloudflare_r2:
access_key_id: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
bucket: my-r2-bucket
account_id: abcdef1234567890abcdef1234567890
public_url: https://pub-abcdef1234567890abcdef1234567890.r2.dev
prefix: cms-uploads/ # Optional[media_libraries.cloudflare_r2]
access_key_id = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
bucket = "my-r2-bucket"
account_id = "abcdef1234567890abcdef1234567890"
public_url = "https://pub-abcdef1234567890abcdef1234567890.r2.dev"
prefix = "cms-uploads/"{
"media_libraries": {
"cloudflare_r2": {
"access_key_id": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"bucket": "my-r2-bucket",
"account_id": "abcdef1234567890abcdef1234567890",
"public_url": "https://pub-abcdef1234567890abcdef1234567890.r2.dev",
"prefix": "cms-uploads/"
}
}
}{
media_libraries: {
cloudflare_r2: {
access_key_id: 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
bucket: 'my-r2-bucket',
account_id: 'abcdef1234567890abcdef1234567890',
public_url: 'https://pub-abcdef1234567890abcdef1234567890.r2.dev',
prefix: 'cms-uploads/', // Optional
},
},
}WARNING
Do not write your Secret Access Key in the configuration file, as it should be kept confidential and not exposed in client-side code. Users will be prompted to enter the key when they use the storage first time, which will be stored securely in the browser’s local storage.
Configuration Properties
| Property | Required | Description |
|---|---|---|
access_key_id | Yes | R2 Access Key ID (64 hex characters). Safe to store in config. |
bucket | Yes | The R2 bucket name. |
account_id | Yes | Your Cloudflare account ID. Used to construct the S3 API endpoint. |
public_url | Yes | Public URL for asset previews and downloads. Required because the R2 S3 API always requires authentication. |
prefix | No | Path prefix within the bucket, e.g. uploads/. |
Content Security Policy
The hosts to allow depend on your public_url setting.
r2.dev subdomain — allow the exact pub-{hash}.r2.dev host:
connect-src https://abcdef1234567890abcdef1234567890.r2.cloudflarestorage.com;
img-src https://pub-abcdef1234567890abcdef1234567890.r2.dev;Custom domain — allow your custom domain, plus the S3 API endpoint for listing and uploading:
connect-src https://abcdef1234567890abcdef1234567890.r2.cloudflarestorage.com;
img-src https://media.example.com;See the CSP documentation for more details.
Accessing the Storage
The Cloudflare R2 media storage can be accessed through the File and Image fields in Sveltia CMS. Enter your Secret Access Key in the CMS UI when prompted, and you’ll be able to upload new media directly to R2 or select existing media from your bucket.
When uploading media, files will be stored in your R2 bucket, and you can take advantage of R2’s capabilities directly from the CMS. You can also select existing media from your R2 storage.
Future Plans
You’ll be able to manage your R2 files directly from the Asset Library in future releases.