Connect your Cloudflare R2 bucket to Dreep
Create an R2 bucket, create an R2 API token with Object Read & Write scoped to that bucket, then enter the bucket name, region auto, your account endpoint and the token's keys under Settings, Storage in Dreep. Dreep writes and deletes a probe object to confirm access. From then on, originals and generated variants land in your bucket and delivery stays on Dreep URLs.
How a request flows
Your app
Presigns an upload with your API key
Dreep upload URL
Browser PUTs bytes to cdn.dreep.cloud
Your R2 bucket
Dreep writes the original server-side
Dreep CDN URL
Transforms on request, variant saved to R2
Before you start
Four conditions decide whether the connection will succeed. Check them first; they account for most failed setups.
A Starter or Pro plan
BYOB needs an active paid subscription on the project ($2.99/mo Starter or $5.99/mo Pro). Free projects are rejected with BYOB_PLAN_UPGRADE_REQUIRED.
An empty project
Connect the bucket before the first upload. A project that already holds images or files can't be switched, so create a fresh project for the R2-backed workload.
One bucket per project, for good
Once connected, the bucket can't be changed for that project. Pick the bucket name and Cloudflare account you intend to keep.
Owner access in Dreep
The Storage tab in Settings is only shown to the project owner. Team members can't add or view the bucket credentials.
Set it up in six steps
Steps one to three happen in the Cloudflare dashboard, four to six in Dreep. Allow about ten minutes.
- 1
Create the bucket
In Cloudflare, open R2 Object Storage and create a bucket, for example acme-media. Leave public access off: Dreep reads with credentials and serves through its own URLs, so the bucket never needs an r2.dev or custom public domain.
- 2
Create a scoped R2 API token
On the R2 overview page, under Account Details, choose Manage next to API Tokens and create a token. Select Object Read & Write and limit it to the bucket from step one. Admin permissions are not needed.
- 3
Copy the keys and your account endpoint
Cloudflare shows the Access Key ID and Secret Access Key once, so save both. Your S3 endpoint is https://<ACCOUNT_ID>.r2.cloudflarestorage.com. Buckets created under the EU jurisdiction use https://<ACCOUNT_ID>.eu.r2.cloudflarestorage.com instead.
- 4
Open Settings, Storage in Dreep
Choose Connect Your Own Bucket and pick Cloudflare R2 as the provider. The form asks for the five fields in the table below.
- 5
Connect and let Dreep probe the bucket
Dreep writes a small object called __dreep_byob_probe_test__, deletes it again, and lists top-level prefixes. If all three calls succeed, the keys are encrypted with AES-256-GCM and the configuration is saved.
- 6
Upload exactly as before
Nothing changes in your code. The same API key, SDK calls and delivery URLs now read from and write to your R2 bucket.
The fields Dreep asks for
| Field | What to enter for R2 | Notes |
|---|---|---|
| Provider | Cloudflare R2 | Sets how the endpoint is handled. |
| Bucket Name | acme-media | Exact name, no r2:// prefix or URL. |
| Region | auto | R2 uses auto. The field is required, so type it in. |
| S3 Endpoint URL | https://<ACCOUNT_ID>.r2.cloudflarestorage.com | Required for R2. Account ID is on the R2 overview page. Don't append the bucket name. |
| Access Key ID | From the R2 API token | Not your Cloudflare API token value. |
| Secret Access Key | From the R2 API token | Shown once by Cloudflare. |
Why Object Read & Write is enough
Cloudflare's Object Read & Write permission allows reading, writing and listing objects in the buckets you choose. That covers every S3 call Dreep makes against a connected bucket:
| S3 operation | When Dreep uses it |
|---|---|
| PutObject | Probe on connect, every original, every generated image or video variant. |
| GetObject / HeadObject | Serving originals and cached variants, checking whether a variant already exists. |
| DeleteObject | Removing the probe object, deleting an asset you delete in Dreep. |
| ListObjectsV2 | Discovering top-level prefixes when you test the connection. |
Admin Read & Write would also let Dreep create and delete buckets, which it never does. Keep the token on Object Read & Write and scoped to one bucket.
What Dreep writes to your bucket
Originals and every variant Dreep generates are stored in R2, so the bucket is a complete copy of the project's media. The layout is shown so you know what to expect; treat it as Dreep-managed and don't rename or move objects under these prefixes.
| Prefix | Contents |
|---|---|
| projects/<project-slug>/<folder-path>/originals/<asset-id>/<filename> | The uploaded file, after any upload-time transform you requested. |
| transforms/<content-hash>/<params-hash>.<ext> | Variants generated on first request (resized images, converted formats, video renditions), reused on later requests. |
Your upload code doesn't change
BYOB is a project setting, not an SDK option. The presign, PUT and confirm flow below is identical on Dreep storage and on R2; only where the bytes end up is different.
import { Dreep } from "dreep";
const dreep = new Dreep({ apiKey: process.env.DREEP_API_KEY });
// Same call as before BYOB. The bytes now land in your R2 bucket.
const upload = await dreep.presignUpload({
filename: "listing-photo.jpg",
contentType: "image/jpeg",
sizeBytes: 2_480_000,
folder: "listings/2026",
});
// Hand upload.uploadUrl to the browser, which PUTs the file, then:
const asset = await dreep.confirmUpload({ id: upload.id });
dreep.url(asset, { width: 800, format: "webp", quality: 80 });client, err := dreep.New(os.Getenv("DREEP_API_KEY"))
if err != nil {
log.Fatal(err)
}
up, err := client.PresignUpload(ctx, dreep.PresignOptions{
ContentType: "image/jpeg",
SizeBytes: 2480000,
Filename: "listing-photo.jpg",
Destination: dreep.Destination{Folder: "listings/2026"},
})
// The browser PUTs to up.UploadURL, then:
asset, err := client.ConfirmUpload(ctx, up.ID, nil)
small := client.URL(asset.ID, &dreep.Transform{Width: 800, Format: dreep.FormatWebP})curl -X POST https://api.dreep.cloud/api/v1/upload/presign \
-H "Authorization: Bearer $DREEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename":"listing-photo.jpg","contentType":"image/jpeg","sizeBytes":2480000,"folder":"listings/2026"}'
# PUT the bytes to the returned uploadUrl, then confirm:
curl -X POST https://api.dreep.cloud/api/v1/upload/<id>/confirm \
-H "Authorization: Bearer $DREEP_API_KEY"Do I need a CORS rule on the bucket?
Not for uploads. Browsers PUT to the upload URL Dreep returns, which lives on cdn.dreep.cloud, and Dreep forwards the bytes to R2 from its own servers. The browser never makes a cross-origin request to r2.cloudflarestorage.com, so R2's CORS policy is never consulted. You only need one if your own front end talks to the bucket directly, outside Dreep.
Troubleshooting connection errors
These are the BYOB error codes Dreep returns, what triggers each one, and the fix.
| Error | Cause | Fix |
|---|---|---|
| BYOB_CONNECTION_FAILED | The probe write or delete was refused: wrong keys, a token without write access, or a token scoped to a different bucket. | Recreate the token with Object Read & Write on this bucket and paste both keys again. Check the endpoint uses your account ID. |
| Bucket not found | The endpoint is correct but no bucket with that name exists in the account or jurisdiction. | Check the spelling, and use the .eu. endpoint for EU-jurisdiction buckets. |
| BYOB_PLAN_UPGRADE_REQUIRED | The project is on Free or its subscription isn't active. | Upgrade the project to Starter or Pro, then connect again. |
| BYOB_PROJECT_NOT_EMPTY | The project already has images or files stored. | Create a new project and connect R2 before the first upload. |
| BYOB_ALREADY_CONFIGURED_IMMUTABLE | A bucket is already connected to this project, and it can't be swapped. | Keep the existing bucket, or use a new project for a different bucket. |
| BYOB_STORAGE_UNREACHABLE | Connection worked, but a later read or write failed: token revoked or rotated, bucket deleted, permissions narrowed. | Restore the token's Object Read & Write access to the bucket. Don't delete the bucket while the project uses it. |
Already have files in this bucket? Import them
BYOB and import do different jobs. BYOB makes Dreep write new uploads and variants into your bucket. Import copies objects that already exist in a bucket into a Dreep project, so they become Dreep assets with Dreep URLs.
To import, open Integrations in the Dreep dashboard, connect Cloudflare R2 with an access key, secret, account endpoint and bucket name, browse the objects, choose what to bring over and a destination folder, and start the job. It runs in the background with progress and logs. It is a one-off copy, not an ongoing sync, so an Object Read only token is enough.
Who this is for
- Teams already standardised on Cloudflare R2 who want media to stay in their own account.
- Projects that need a full copy of originals and variants under their own retention and backup policies.
- Apps that want Dreep's transforms and delivery without moving storage ownership to a vendor.
When Dreep is not the right fit
- Existing Dreep projects with media already uploaded: the bucket must be connected while the project is empty.
- Workloads on the Free plan: BYOB starts at Starter.
- Serving objects already in the bucket as-is: BYOB covers new uploads; existing objects need the separate R2 import, which copies them into a Dreep project.
Frequently asked questions
Which R2 API token permission does Dreep need?
Object Read & Write, scoped to the one bucket you connect. It covers the PutObject, GetObject, HeadObject, DeleteObject and ListObjectsV2 calls Dreep makes. Admin permissions are not required.
What region do I enter for Cloudflare R2?
Enter auto. R2 doesn't use AWS-style regions, and the endpoint URL (https://<ACCOUNT_ID>.r2.cloudflarestorage.com) is what tells Dreep where the bucket lives.
Do I need to make the R2 bucket public?
No. Dreep authenticates with your token for every read and write and serves media through Dreep CDN URLs, so the bucket can stay private with no r2.dev or custom domain.
Can I connect R2 to a project that already has uploads?
No. BYOB has to be connected while the project is empty. Create a new project, connect R2, then point your app at the new project's API key.
Can I switch to a different bucket later?
Not on the same project. A connected bucket is permanent for that project, so choose the bucket and Cloudflare account you intend to keep.
Can Dreep import the objects already in my R2 bucket?
Not through BYOB: connecting R2 only changes where new uploads and variants are stored. Use the separate R2 import under Integrations in the dashboard, which copies the objects you choose into a Dreep project as a background job.
Related
Image transformations on R2
Why teams put Dreep's transforms in front of their R2 bucket.
Bring your own bucket
How BYOB works across S3, R2, Wasabi, Spaces and MinIO.
Connect Amazon S3
The same setup for an S3 bucket, with a least-privilege IAM policy.
Pricing
BYOB is included on Starter and Pro.
Documentation
Uploads, transforms, signed URLs and the BYOB guide.
Keep your media in your own R2 account
Create a project on Starter or Pro, connect your bucket before the first upload, and ship with the same SDK calls.