Skip to content
Engineering

Mastering BYOB Storage (Cloudflare R2 Integration)

BYOB Team

BYOB Team

Updated: · Added storage table, fit guide, and binding behavior notes. House voice cleanup Sep 2026: removed negative parallelism cliches in prose
10 min read

BYOB attaches a Cloudflare R2 bucket as a STORAGE binding and routes every upload and download through SvelteKit server routes that check the session and validate files. R2 charges $0.015 per GB month with zero egress fees, and app signed URLs keep private sharing on your own domain.

Key takeaways

  • • BYOB attaches an R2 bucket as the STORAGE binding, a runtime variable your Worker code reads directly with no credentials to manage
  • • R2 Standard storage costs $0.015 per GB month with zero egress fees, and the free tier covers 10 GB months plus generous operations
  • • Every upload and download passes through a SvelteKit server route that checks the session and validates type and size on the server
  • • Private files use app signed URLs on your own domain instead of exposed bucket endpoints, while Supabase Storage stays a separate path for Supabase projects
Mastering BYOB Storage (Cloudflare R2 Integration)

Manage your project storage ->

Illustration of stacked storage buckets in a warehouse grid with upload arrows
Illustration of stacked storage buckets in a warehouse grid with upload arrows

Mastering BYOB storage (Cloudflare R2 integration) #

Think of object storage as a warehouse. Anyone can rent shelf space. The shops that survive are the ones with a guarded loading dock, where every box gets checked before it goes in or out.

BYOB gives you that dock. When you enable storage, we attach a Cloudflare R2 bucket to your project as the STORAGE binding and route every upload and download through SvelteKit server routes that check who you are and what the file is. No raw credentials in client code. No public bucket endpoints. Just a clean pipe from browser to bucket with a bouncer in the middle.

flowchart LR A[Browser picks file] --> B[POST to SvelteKit server route] B --> C[Auth check & file validation] C --> D[STORAGE binding] D --> E[Cloudflare R2 bucket] E --> F[Metadata stored in D1] F --> G[Generate app-signed URL on request] G --> H[Time-boxed download]

[!TIP] Try it: Image Compressor

Try it right here: image compressor resizerOpen full tool

Loading the interactive tool… or open it here.

Open tool in new tab

How does the STORAGE binding connect your bucket? #

Cloudflare describes a binding as a runtime variable that the Workers runtime provides to your code, declared in the Wrangler file and bound to a resource such as an R2 bucket, as stated in the Cloudflare R2 Workers API docs (https://developers.cloudflare.com/r2/api/workers/workers-api-usage/). You pick the variable name, Cloudflare wires it to the bucket, and your Worker reads and writes through it with no network hop and no access keys.

BYOB does that wiring for you at project setup. Your SvelteKit backend then reaches the bucket through a small server helper:

typescript
// src/lib/server/storage.ts
import { error, type RequestEvent } from '@sveltejs/kit';

export function getStorage(event: RequestEvent) {
  const bucket = event.platform?.env?.STORAGE;
  if (!bucket) throw error(503, 'File storage is not connected.');
  return bucket;
}

Two details matter here. First, the helper fails loudly with a 503 when storage is not connected, so a misconfigured project tells you what is wrong instead of failing in strange ways later. Second, nothing in this file is a secret. The binding is infrastructure, not a password, which means there is nothing to rotate and nothing to leak into a client bundle.

SvelteKit's filesystem router makes the other half natural. Routes map to files, and +server.js files act as API endpoints that run only on the server, as stated in the SvelteKit routing docs (https://svelte.dev/docs/kit/routing). BYOB leans on that split. Browser code talks to /api/storage/* routes. Only those routes ever touch the bucket.

What does R2 cost for a typical project? #

R2 Standard storage is priced at $0.015 per GB-month, Class A operations at $4.50 per million, Class B at $0.36 per million, and egress to the internet is free, as stated in the Cloudflare R2 pricing page (https://developers.cloudflare.com/r2/pricing/). The monthly free tier covers 10 GB-months of storage, 1 million Class A requests, and 10 million Class B requests.

For a typical young project, avatars plus a few hundred uploads, that means storage costs round to zero. The number worth watching is not gigabytes. It is request volume once you start serving images at scale. At that point the zero egress fee is the quiet win, since outbound bandwidth is where traditional object storage bills tend to bite.

One honest caveat. The free tier applies to Standard storage only. If you ever move cold archives to Infrequent Access, different rates and a 30-day minimum storage duration apply. BYOB defaults to the Standard path, so you will not trip over that by accident.

Why do uploads pass through server routes? #

A common anti-pattern is handing the browser a raw presigned URL or, worse, baking credentials into client code. It works until it does not, and when it breaks it breaks in the open.

BYOB enforces a secure-by-default proxy pattern. Uploads and downloads travel through your SvelteKit server routes, something like /api/storage/upload, and each route does three jobs.

It verifies the session before touching the bucket, so anonymous traffic never reaches storage. It validates file type and size on the server as well as in the browser, since client checks are suggestions and server checks are law. And it keeps every credential inside the worker environment, where frontend code cannot see it.

This matches what OWASP prescribes for file uploads: restrict uploads to what the business needs, validate with an allowlist rather than a denylist, treat filenames and metadata as hostile input, and scan content before serving it to other users, as stated in the OWASP file upload guide (https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload). Our generated routes follow that shape, with server-side allowlists for MIME types, byte caps per upload, and sanitized object keys instead of raw user filenames.

A practical example helps. Say users upload profile photos. The route accepts the multipart body, rejects anything that is not JPEG, PNG, or WebP, rejects anything over 5 MB, writes it under a generated key like avatars/{userId}/{uuid}.webp, and returns the key. The browser never learns the bucket name. The bucket never learns about the browser.

App-signed URLs #

Public assets are simple. Serve them from a public path and move on. Private files need more care.

BYOB generates app-signed route URLs for private sharing. Instead of a direct R2 presigned URL that exposes the bucket endpoint, you get a signed link pointing back at your own app, like /api/storage/file?token=xyz. The route validates the token, checks the requester's access, and streams bytes from the STORAGE binding. Keeping the domain consistent has side effects you will appreciate later. Links never leak infrastructure details. You can attach per-request logic, things like download counting, expiry windows, or one-time links, without touching bucket policy. And if you ever migrate storage backends, every shared link keeps working because the URL contract belongs to your app, not to the bucket.

When Supabase is the stack #

R2 is the native path, tuned for SvelteKit on Cloudflare. But some projects live on Supabase, with Postgres as the database and Supabase Auth guarding the rows. For those, BYOB supports Supabase Storage as a distinct option instead of R2 bindings.

Supabase Storage is S3-compatible object storage with access control expressed through Postgres row-level security policies, as stated in the Supabase Storage docs (https://supabase.com/docs/guides/storage). When this path is active, the code generator loads Supabase-specific knowledge: client instantiation, bucket policies, and signed URL helpers in Supabase's shape rather than binding-shaped code.

Pick one path per project based on your database decision. R2 plus D1 keeps everything inside Cloudflare's runtime with binding-style access. Supabase Storage keeps files next to a Supabase database with policy-based access. Mixing both in one project is possible but rarely worth the confusion.

The table below picks the storage shape that fits your files.

Option Best use Limit to respect
R2 bucket binding Images and files inside your app Route all access through server code
App signed URLs Private sharing on your domain Tokens must expire
Supabase storage Projects already on Supabase Keep one storage home per project
Public assets Logos and open downloads Never mix private files in
Server validation Every upload path Reject bad types and sizes early

Tradeoffs worth naming #

The proxy pattern adds a hop. Direct-to-bucket uploads with presigned URLs can push bytes faster for very large files, since data skips your server code. BYOB accepts the small latency cost because the security posture is better and the code stays simpler. If you ever need resumable 5 GB uploads, that is the moment to revisit the decision with eyes open, using multipart upload through a dedicated route.

Bindings also tie you to Cloudflare's runtime. That is a fair trade while BYOB hosts your project, and the generated code is standard enough to adapt if you leave. The storage helper is a thin wrapper, so swapping the backend later means rewriting one module, not every route.

Ship the boring version first. A guarded dock beats a fast open door.

Manage your project storage ->

What we learned building this #

Uploads pass through SvelteKit server routes under src/routes that check the session before touching the STORAGE binding. The pattern matches the rest of the codebase: private work happens on the server, the browser only sees what it earned. Signed URLs keep sharing on your domain instead of an open bucket.

Who this is for (and who should skip it) #

This guide helps builders whose apps accept uploads, serve images, or share private files. If users hand you files that must stay safe and fast, the binding plus server route pattern above is your default.

Skip it if your site is static pages with a few bundled images. The built in asset pipeline already covers you, and object storage only pays off once uploads enter the picture.

  • Best for developers adding user uploads and private file serving.
  • Best for startups serving images fast with signed access.
  • Best for small teams moving past static assets to real storage.

How we picked these

Compared storage claims with Cloudflare R2 API and pricing plus OWASP upload guidance, Supabase storage, and SvelteKit routing docs and reviewed the listed source links.

Frequently asked questions

How does BYOB access R2 without raw credentials?

When storage is enabled BYOB attaches the bucket as the STORAGE binding, which Cloudflare exposes as a runtime variable to the Worker. Server code reads it directly, so there are no access keys to rotate or leak

What does R2 cost for a typical project?

R2 Standard storage is $0.015 per GB month with zero egress fees, and the monthly free tier includes 10 GB months, 1 million Class A operations, and 10 million Class B operations. Most small projects sit inside the free tier

Why proxy through SvelteKit server routes?

The server route verifies the user session, validates file types and sizes on the server, and keeps credentials inside the worker environment. OWASP recommends exactly this shape: allowlist file types, validate metadata, and never trust client supplied filenames

How are private files shared securely?

BYOB generates app signed URLs pointing to the app, which validates the token and streams from STORAGE. That keeps your domain consistent and lets you add custom auth or tracking per request

About the Author

BYOB Team

BYOB Team

The creative minds behind BYOB. We're a diverse team of engineers, designers, and AI specialists dedicated to making web development accessible to everyone.

Ready to start building?

Join thousands of developers using BYOB to ship faster with AI-powered development.

Get Started Free