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

  1. Browser upload

    Presigned, single-use URL

  2. Your R2 bucket

    Original stored here

  3. Request a URL

    ?width=800&format=webp

  4. Generated once

    Variant written to your R2

  5. Dreep CDN

    Cached delivery

Your bucket holds originals and variants. Delivery goes through Dreep CDN URLs.

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 });

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=75
  • Medium, 800px WebP

    https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=800&format=webp&quality=80
  • Large, 1600px WebP

    https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=1600&format=webp&quality=82
  • Square thumbnail, subject-aware crop

    https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=256&height=256&fit=cover&gravity=attention&format=webp
  • AVIF 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 needR2 + DreepR2 + Workers or Lambda + Sharp
Browser uploadsPresign and confirm API, single-use 10-minute URLsSign R2 URLs yourself, configure bucket CORS
Upload validationType allow-list, 500 MB cap, Content-Type and Content-Length must matchWrite and test it yourself
Resizing and formatsURL parameters: width, height, fit, gravity, format, quality, dpr and moreWrite the function, package an image library for the runtime
Variant cachingGenerated on first request, stored in your bucketDesign cache keys, write-back and cache headers
Private mediaSigned and Private folders, SDK-generated linksBuild signing and verification
VideoSame URL model for MP4, WebM, HLS and GIFA separate FFmpeg pipeline
Ongoing workNone beyond your app codeMemory and timeout tuning, dependency upgrades, monitoring
ControlDreep's parameter set onlyAnything you can code
Building it yourself gives full control and can be the right call for a team with custom pipelines. It is also code you run forever.

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 itemSmall appGrowing app
Dreep planStarter, $2.99/moPro, $5.99/mo
Image transformations included10,000 / month50,000 / month
Data in R2 (originals and variants)40 GB250 GB
R2 storage, first 10 GB free, then $0.015/GB-month$0.45$3.60
R2 operationsInside free tier (assumed)Inside free tier (assumed)
Estimated monthly total$3.44$9.59
R2 Standard pricing from developers.cloudflare.com/r2/pricing at the time of writing (October 2026): $0.015 per GB-month after 10 GB free, 1 million Class A and 10 million Class B operations free per month, then $4.50 and $0.36 per million, no egress fees. Cloudflare bills R2 to you; Dreep bills the plan. Excludes tax and add-on packs.

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

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.