Image transformations for your own Cloudflare R2 bucket
Connect your Cloudflare R2 bucket to a Dreep project and your images live in R2. Users upload straight from the browser with presigned URLs, and you request any size, crop, WebP or AVIF variant by adding parameters to a URL. Each variant is generated on the first request, written to your bucket and served from cache. Requires Starter or Pro.
How it fits together
Browser upload
Presigned, single-use URL
Your R2 bucket
Original stored here
Request a URL
?width=800&format=webp
Generated once
Variant written to your R2
Dreep CDN
Cached delivery
Connecting your R2 bucket
Starter or Pro plan
Bring your own bucket is available on paid plans only: Starter at $2.99/month or Pro at $5.99/month.
A new, empty project
A bucket can only be connected while the project has no files. Once connected it cannot be changed for that project, so pick the bucket you intend to keep.
An R2 API token
Create an S3-compatible access key with read, write and delete on the bucket, and use your account endpoint, https://<account_id>.r2.cloudflarestorage.com.
A connection test
When you connect, Dreep writes a probe object to confirm the credentials work before any uploads go to the bucket.
Direct browser uploads into R2
Uploads use the same presign, PUT, confirm flow as any Dreep project. Because the project is connected to R2, confirmed originals land in your bucket. You do not write R2 presigning code or bucket CORS rules, and your API key stays on your server.
import { Dreep } from "dreep";
// API key for the project connected to your R2 bucket. Server-side only.
const dreep = new Dreep({ apiKey: process.env.DREEP_API_KEY });
// 1. Your backend, after authorising the user
const upload = await dreep.presignUpload({
filename: "listing.jpg",
contentType: "image/jpeg",
sizeBytes: 3_400_000,
folder: "listings/8812",
});
// 2. The browser PUTs the bytes to upload.uploadUrl
// (same Content-Type and size as declared above)
// 3. Your backend confirms: the original is now in your R2 bucket
const asset = await dreep.confirmUpload({ id: upload.id });
const medium = dreep.url(asset, { width: 800, format: "webp", quality: 80 });upload, err := client.PresignUpload(ctx, dreep.PresignOptions{
Filename: "listing.jpg",
ContentType: "image/jpeg",
SizeBytes: 3_400_000,
Destination: dreep.Destination{Folder: "listings/8812"},
})
if err != nil {
return err
}
// Return upload.ID and upload.UploadURL to the browser.
// After its PUT succeeds:
asset, err := client.ConfirmUpload(ctx, upload.ID, nil)
if err != nil {
return err
}
medium := client.URL(asset.ID, &dreep.Transform{
Width: 800, Format: dreep.FormatWebP, Quality: 80,
})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.jpg","contentType":"image/jpeg","sizeBytes":3400000,"folder":"listings/8812"}'
curl -X PUT "$UPLOAD_URL" -H "Content-Type: image/jpeg" --data-binary @listing.jpg
curl -X POST "https://api.dreep.cloud/api/v1/upload/$UPLOAD_ID/confirm" \
-H "Authorization: Bearer $DREEP_API_KEY"Why not pre-generate every size?
The usual R2 setup is a job that runs on upload and writes thumbnail, medium and large copies next to the original. It works until the design changes. A new card layout needs a 640px crop, a retina screen wants 2x, someone asks for AVIF: each change means a backfill over every image you have ever stored, and the job that does it is code you own.
Pre-generation also stores variants nobody requests. With on-demand transformation, a size exists only after something asks for it, and a new size is a new URL rather than a migration.
Resize, crop and convert on demand
Three WebP sizes from one original, plus a square crop and an AVIF version. Full parameter list on the image CDN API page.
Small, 320px WebP
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=320&format=webp&quality=75Medium, 800px WebP
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=800&format=webp&quality=80Large, 1600px WebP
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=1600&format=webp&quality=82Square thumbnail, subject-aware crop
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=256&height=256&fit=cover&gravity=attention&format=webpAVIF hero
https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=1600&format=avif&quality=70
Caching and private media
The first request for a given asset and parameter set generates the variant and writes it to your bucket. Every later request for that URL is served from cache, with a one-year immutable Cache-Control header on public assets. Requests that produce or serve a transformed variant count against the plan's monthly image transformations, 10,000 on Starter and 50,000 on Pro. Requests for the untouched original do not.
Your bucket does not need to be public. Put sensitive images in a Signed folder and they are only reachable with a time-limited HMAC-SHA256 link, generated locally by the SDK, for example dreep.signedUrl(asset, { expiresIn: 900 }). The signature covers the asset and expiry, so the client can still add width or format. Private folders go further: dashboard access only.
R2 + Dreep vs R2 + a resize function you maintain
| What you need | R2 + Dreep | R2 + Workers or Lambda + Sharp |
|---|---|---|
| Browser uploads | Presign and confirm API, single-use 10-minute URLs | Sign R2 URLs yourself, configure bucket CORS |
| Upload validation | Type allow-list, 500 MB cap, Content-Type and Content-Length must match | Write and test it yourself |
| Resizing and formats | URL parameters: width, height, fit, gravity, format, quality, dpr and more | Write the function, package an image library for the runtime |
| Variant caching | Generated on first request, stored in your bucket | Design cache keys, write-back and cache headers |
| Private media | Signed and Private folders, SDK-generated links | Build signing and verification |
| Video | Same URL model for MP4, WebM, HLS and GIF | A separate FFmpeg pipeline |
| Ongoing work | None beyond your app code | Memory and timeout tuning, dependency upgrades, monitoring |
| Control | Dreep's parameter set only | Anything you can code |
A simple monthly cost model
Files in your bucket do not count against the Dreep plan's storage allowance, so the plan price stays flat as your library grows. If you need more transformations than the plan includes, image packs are one-time: +10k for $1.99, +50k for $2.99, +100k for $4.99.
| Line item | Small app | Growing app |
|---|---|---|
| Dreep plan | Starter, $2.99/mo | Pro, $5.99/mo |
| Image transformations included | 10,000 / month | 50,000 / month |
| Data in R2 (originals and variants) | 40 GB | 250 GB |
| R2 storage, first 10 GB free, then $0.015/GB-month | $0.45 | $3.60 |
| R2 operations | Inside free tier (assumed) | Inside free tier (assumed) |
| Estimated monthly total | $3.44 | $9.59 |
Who this is for
- Teams already on Cloudflare R2 who want resizing and WebP or AVIF without writing a resize service.
- Apps that must keep originals in a bucket they own and can audit.
- Products with user uploads that need presigned browser uploads straight into R2.
When Dreep is not the right fit
- You want Dreep to serve an existing R2 library in place. BYOB needs an empty project; existing files can be copied into a project with the R2 importer instead.
- You are on the Free plan. BYOB starts at Starter.
- You need custom processing steps, watermarks or overlays. Dreep's parameter set is fixed.
Frequently asked questions
Can I resize images stored in Cloudflare R2?
Yes, for files uploaded through a Dreep project connected to your R2 bucket. Request the asset's CDN URL with parameters such as width, height, fit and format, and Dreep generates the variant from the original in your bucket.
Can I connect a bucket that already has my images in it?
You can connect any bucket you control, but only to a project that is still empty, and Dreep only knows about files uploaded through it. To bring an existing library across, use the R2 importer in the dashboard: connect with your access key, secret, endpoint and bucket name, choose objects, and a background job copies them into a Dreep project. Importing is separate from BYOB, which is where Dreep stores new files.
Where are the generated variants stored?
In your R2 bucket, alongside the originals. Delivery still goes through Dreep CDN URLs, so your bucket does not need public access.
Does my R2 storage count against my Dreep storage limit?
No. For a project connected to your own bucket, Dreep does not meter the bytes stored there. Cloudflare bills you for R2 directly. Image transformations still count against your plan's monthly quota.
Can I switch the bucket later?
Not for the same project. The bucket is fixed once connected. To use a different bucket, create a new project.
Does this work with S3 or other providers?
Yes. The same feature works with AWS S3, Wasabi, DigitalOcean Spaces, MinIO and other S3-compatible storage.
Related
Bring your own bucket
Every supported provider and how connection works.
Image CDN API
Full parameter reference, srcset and presets.
Secure file uploads
What Dreep validates on upload, and what stays with you.
Video transcoding API
MP4, WebM, HLS and GIF from videos in your bucket.
Dreep vs Cloudflare Images
When Cloudflare's own image product fits better.
SDKs
JavaScript and Go clients.
Pricing
Starter and Pro plans, which include BYOB.
BYOB docs
Permissions and dashboard steps.
Connect R2 to a new project
Create a project on Starter, connect your R2 bucket before the first upload, and request your first WebP variant by URL.