Infrastructure and SEO

Cloudflare R2 vs Supabase Storage: when to add R2 in 2026

You already run Supabase. Its Storage egress runs $0.03 to $0.09 per GB, R2 charges zero. Here is the line where adding Cloudflare R2 pays off in 2026.

May 28, 202610 min read
assorted-color filed intermodal containers

Object storage is a flat namespace of files served over HTTP, billed two ways: by the gigabyte you keep and by the gigabyte you move. If you run a SaaS on Supabase, you already have object storage. So the real question in 2026 is rarely which provider to choose from scratch. It is whether the storage bundled with Supabase is enough, or whether you bolt on Cloudflare R2 for the heavy media. The answer turns almost entirely on one number: egress.

TL;DR. Keep files on Supabase Storage when they are private, tied to a logged-in user, and not served at high volume. The row-level-security integration and the built-in image transforms are worth more than the egress you would save. Add Cloudflare R2 when you serve public media at scale: its zero egress fee turns a bill that grows with your popularity into one that grows only with what you store. Many teams end up running both, split along the public/private line.

What each one charges in 2026

The two services price storage within a third of each other. They price moving bytes out an order of magnitude apart. That gap is the whole decision.

  • Storage per GB per month. R2 is $0.015 on its Standard tier and $0.01 on Infrequent Access. Supabase Storage includes 100 GB in the $25 Pro plan, then $0.021 per GB. On stored bytes alone R2 is cheaper, but storage is rarely the line that hurts.
  • Egress to the internet. R2 charges nothing, through the S3 API, the Workers API, the r2.dev domain, or a custom domain. Supabase bills egress at $0.09 per GB uncached and $0.03 per GB served from its CDN cache, with 250 GB included in the Pro plan. This is the difference that compounds.
  • Operations. R2 charges $4.50 per million Class A operations (writes, lists) and $0.36 per million Class B operations (reads). Supabase folds storage requests into its normal request accounting rather than billing per object operation.
  • Free allowance. R2 gives 10 GB of storage, 1 million Class A and 10 million Class B operations every month. Supabase Free includes 1 GB of file storage and 5 GB of egress, shared across the whole project.

These are list prices in US dollars, before any volume discount. The figures come straight from the R2 pricing page and the Supabase Storage pricing docs. Model them against your own traffic before you decide.

The egress math, the part nobody runs until the bill arrives

Two facts about Supabase egress decide most of this comparison, and both are easy to miss.

First, the included egress is shared. The 250 GB in the Pro plan is not a storage allowance. It is a single pool drawn down by your database queries, your Auth calls, your Realtime channels, your Edge Functions, and your storage downloads together. A query-heavy dashboard can spend most of that quota before a single image is served. The headline "250 GB included" is the ceiling for your whole backend, not for your media.

Second, cached and uncached egress cost different amounts. Bytes served from Supabase's CDN cache cost $0.03 per GB. Bytes that miss the cache and hit the origin cost $0.09 per GB, the same as classic S3. A well-cached public asset is three times cheaper to serve than an uncached one, which is why cache hit rate, not raw traffic, is the figure that drives your storage bill on Supabase. Supabase tripled the cached discount and doubled the included quota in a 2025 pricing update, so older comparisons that quote a flat $0.09 are out of date.

Put numbers on it. A 50 MB product video downloaded 20,000 times in a month is roughly 1 TB of egress. On Supabase Pro, past the included pool, that single asset costs about $30 if every byte is cached and about $90 if none are. On R2 the same terabyte costs nothing to egress, plus a few cents of Class B reads. One popular file is enough to justify a second bucket.

Where Supabase Storage wins

The reason to keep files on Supabase is not price. It is that the storage is already wired to the rest of your stack.

Authorization comes from the database. A storage object is governed by the same row-level security policies as your tables. A logged-in user reads only the files their tenant owns, enforced by a policy that joins back to the row referencing the file. No second IAM model, no separate key rotation, no signed-URL plumbing to maintain. For private, user-scoped files this is the feature that matters, and R2 has no equivalent.

Image transformations are built in. Supabase resizes and reformats images on the fly through its transform API for $5 per 1,000 origin images, the first 100 free, and converts to WebP automatically for browsers that support it. R2 has no native transform: you would add Cloudflare Images as a separate product or run a transform Worker. If you serve avatars, thumbnails, or responsive image sets, that built-in pipeline is a real reason to keep images on Supabase.

One service, one SDK. Uploads, downloads, and access policies use the same Supabase client you already call for queries. Storage also speaks the S3 protocol and supports resumable uploads up to 50 GB per file on Pro, so large files and standard S3 tooling both work without leaving the platform.

Where Cloudflare R2 wins

R2 earns its place the moment bytes leave your servers in volume.

Zero egress, at any scale. This is the headline and it holds. Serving 5 TB or 50 TB of public media costs the same on egress: nothing. A bill that would climb with every share, every embed, and every spike in attention on Supabase or S3 instead stays flat and tracks only what you store. For video, downloadable assets, or images served broadly through a CDN, nothing else competes.

Cheaper storage and a cold tier. Standard storage is $0.015 per GB, and Infrequent Access drops it to $0.01 for data you rarely read, with a $0.01 per GB retrieval fee and a 30-day minimum duration. For long-tail user uploads, backups, or logs that sit untouched for months, the cold tier undercuts Supabase's flat $0.021.

It has grown past plain object storage. R2 now ships event notifications that push to a queue and trigger a Worker on every change, and R2 Data Catalog, a managed Apache Iceberg layer for querying objects as tables. Supabase answered the same year with Analytics Buckets, also Iceberg-backed. The two are converging on the same analytics future from opposite starting points, which tells you the storage decision itself is narrowing to egress and integration.

The crossover: when to actually add R2

Stored bytes are not the trigger. The gap between $0.015 and $0.021 per GB is real but small, and you would need to store terabytes for it to matter on its own. Egress is the trigger.

The honest line: while your public-media egress fits inside the egress your plan already includes, after the database and API traffic that share the pool, Supabase Storage is effectively free with your subscription, and adding R2 is premature complexity. Once media downloads become the dominant line on your Supabase bill, hundreds of gigabytes to terabytes a month, R2's zero egress wins decisively, even after you account for running a second service.

The cleanest signal is a small set of assets generating outsized traffic: a launch video, a popular download, a public image embedded across the web. The day one file's egress shows up as a real number on the invoice, move public media to R2 and leave the rest where it is.

The hybrid most SaaS land on

The split that works runs along the public/private line, and it happens to map onto the egress line as well.

Private, auth-scoped files stay on Supabase Storage: profile data, tenant documents, uploaded CSVs, anything where the RLS join does real authorization work and traffic is modest. Public, broadly served media moves to R2 behind a Cloudflare CDN: marketing images, product photos, video, downloadable resources, anything whose audience is everyone and whose cost is egress.

Moving a file off Supabase costs you two things, and you replace each one deliberately. You lose RLS authorization, so public media on R2 is served either openly, behind a Worker that checks a session before streaming the object, or through time-limited signed URLs. You lose the built-in image transforms, so you pre-generate variants, add Cloudflare Images, or run a transform Worker. For genuinely public assets neither loss bites: there is no user to authorize, and the variants can be generated once.

Moving public media from Supabase to R2

The migration is more annoying than hard, and the byte copy is the easy part. Cloudflare's Super Slurper pulls from S3 and Google Cloud Storage, not from Supabase, so there is no one-click import here. Instead you point rclone at Supabase's S3-compatible endpoint as the source and R2 as the destination, and let it stream the objects across. A one-time move of a few hundred gigabytes runs in an afternoon.

The real work is the access layer, and you do it before you copy anything. Every code path that fetched a file through the Supabase client, leaning on RLS for authorization, has to be rewritten to fetch from R2 under its new model: public for open assets, a signed URL or a Worker check for anything that still needs gating. Map those paths first. Then run both stores in parallel for at least one billing cycle: write new uploads to both, read from R2 first with Supabase as the fallback, and stop writing to Supabase only once the R2 read path looks clean in your logs. Flip reads before writes, and flip writes last.

What we reach for by default in 2026

For a new SaaS where Supabase is already the database and auth layer, our default is to start with Supabase Storage alone. It is one less service, the authorization is free, and most products never push enough public egress to need anything more. We add R2 only when the traffic shape demands it: a content-heavy product, a media library, a public download that scales with marketing rather than with headcount.

When R2 goes in, it goes in for one job, public media behind a CDN, while Supabase keeps everything tied to a user. The trap is reaching for R2 on day one because the zero-egress headline is attractive. A second storage service you do not yet need is complexity you pay for in every upload path, every access check, and every migration, long before it saves a cent. Storage is plumbing. Add the second pipe when the first one is genuinely under pressure, not before.

If your decision also has to weigh Amazon S3, for an existing AWS footprint or a specific compliance certificate, we cover the three-way trade-off in Cloudflare R2 vs S3 vs Supabase Storage.

Sources

Photo by frank mckenna on Unsplash

Frequently asked questions

Does Supabase Storage run on Amazon S3 under the hood?
No. Supabase Storage is its own service, fronted by Postgres and governed by row-level security, that happens to speak the S3 protocol so standard S3 clients and tools work against it. It does not inherit S3's pricing, its storage classes, or its egress model. The practical consequence is that you can point an AWS SDK or rclone at a Supabase bucket, but you are billed by Supabase's rules ($0.021 per GB stored, $0.03 to $0.09 per GB egress), not Amazon's.
How do I protect private files on R2 without Supabase's RLS?
R2 has no row-level security, so you replace it at the application edge. The two common patterns are time-limited signed URLs, where your backend mints a short-lived URL after checking the user's session, and a Cloudflare Worker in front of the bucket that validates a session or JWT before streaming the object. Both work, but both are code you write and maintain. This is exactly why private, user-scoped files are usually better left on Supabase Storage, where the database policy does the same job for free.
Will moving images to R2 break my resizing and WebP conversion?
Yes, if you rely on Supabase's built-in transforms. Supabase resizes and converts images to WebP on the fly; R2 does nothing of the kind. When you move images to R2 you pick one of three replacements: pre-generate the variants you need at upload time and store each one, add Cloudflare Images as a separate transform layer in front of R2, or run a Worker that resizes on request and caches the result. For a fixed set of sizes (a thumbnail, a card image, a full view) pre-generating is the simplest and cheapest. For arbitrary on-the-fly sizes, Cloudflare Images or a Worker is the closer match to what Supabase gave you.
When does Supabase Storage egress actually get expensive?
When public downloads start eating the shared egress pool. The 250 GB included in Supabase Pro is not reserved for storage: your database, Auth, Realtime, and Edge Functions all draw from it. Once your media downloads push the project past that pool, every extra gigabyte costs $0.03 cached or $0.09 uncached. A practical threshold: if a handful of public assets generate hundreds of gigabytes a month, you are paying real money for traffic that R2 would serve for free. Private, low-traffic files almost never reach that point and are fine where they are.

Studio

Start a project.

One partner for the digital product you need to build. Faster delivery, modern tech, lower costs. One team, one invoice.