← Blog
Technical5 min read23 June 2026

How to Expose Public Content in Supabase Without Exposing Private Data

A blog, a CMS, or a landing page needs public content, but that does not mean opening up your whole database.

PN
Paul Noorman
Founder, OurPlan
How to Expose Public Content in Supabase Without Exposing Private Data

A blog, a CMS or marketing pages need public content. It's normal. The problem arises when a team opens the database too wide to serve this content, when only a few tables should be readable without connection.

With Supabase, you can expose public content cleanly. But we must separate the marketing need from the security model. Otherwise, you end up with a base that "works", but gives too much access.

The legitimate need

In many projects, visitors who are not connected must be able to read:

  • blog articles
  • CMS content pages
  • public files
  • highly targeted display data

This does not mean that anon must be able to touch all public tables.

The correct reasoning is: what data should be publicly readable, and nothing more?

The wrong solution

The bad solution is to give broad rights to anon over the entire public schema, because it's faster.

The problem with this approach:

  • you mix public content and private data
  • you make the audit more difficult
  • you increase the risk of forgetting an open table by mistake

A public schema in Postgres is not synonymous with public data. It's just a schema named public.

Good logic

To expose public content properly with Supabase, you must combine:

  1. explicit GRANT
  2. EPIRB on exposed tables
  3. policies that only allow what needs to be read

Example: you have a table public.blog_posts which must be publicly readable, but only editable by authorized users.

grant usage on schema public to anon, authenticated, service_role;

grant select on table public.blogposts to anon; grant select, insert, update, delete on table public.blogposts to authenticated; grant select, insert, update, delete on table public.blogposts to servicerole;

alter table public.blog_posts enable row level security;

create policy "public can read published posts" on public.blog_posts for select to anon using (status = 'published');

create policy "authenticated can read published posts" on public.blog_posts for select to authenticated using (status = 'published'); ```

In this diagram:

  • anon can only read what is explicitly public
  • authenticated has more capabilities if your product requires it
  • service_role remains reserved for the server

Why EPIRB remains useful even for the public

Some teams believe that a public table does not need EPIRB. In practice, the RLS remains very useful for setting readable limits.

For example, you can make public:

  • only published articles
  • only valid files
  • only content of a certain type

Without this layer, you can unintentionally expose drafts, internal content, or working data.

A good separation of tables

If you have a somewhat rich CMS, the best practice is often to separate:

  • public content tables
  • internal management tables
  • private business tables

For example :

  • blog_posts: readable public content
  • cms_config: internal configuration, not public
  • messages, invoices, clients: strictly private

This separation makes GRANT much easier to understand.

The very common case of the marketing site

A marketing site or an agency site generally only needs anon on a very limited scope:

  • page content
  • published articles
  • some navigation or presentation data

Everything else must remain firm.

If you give anon broad access "while waiting", that "while waiting" often becomes the final state.

How to check that your public display is clean

Here is a useful little checklist:

  1. List all tables that anon has access to.
  2. For each table, ask yourself if an external visitor really needs to read it.
  3. Verify that RLS is enabled on each exposed table.
  4. Check that policies limit public or published content.
  5. Test reading with the real role anon, not with an admin account.

This last step is essential. Many false positives come from tests done too authoritatively.

What to do for new Supabase projects

Since the 2026 change to the Data API, it is even more important to formalize public exposure at the time of migration.

The healthy schema becomes:

  • we create the table
  • we give service_role what the server needs
  • we give anon only select if the content must be public
  • we activate the EPIRB
  • we write a clear policy which describes what is truly public

This method avoids both functional breakage and overexposure.

What to remember

Exposing public content with Supabase is simple if you keep a clear rule: public does not mean the entire schema, only the data that is truly intended to be public.

The good model is based on explicit GRANT, RLS, and policies that describe what the visitor can read. It's cleaner for security, more readable for the team, and more stable as the project grows.

Official sources

How to Expose Public Content in Supabase Without Exposing Private Data | OurPlan