NC · article
Is Your Supabase App Production-Ready? Measure It, Do Not Guess
Every guide on this subject hands you a checklist. A checklist tells you what to look at; it does not tell you whether you have a problem. These are four numbers you can get out of your own project this afternoon, and what each one means when it comes back wrong.
part of Reviewing AI-Built Software · 3 articles
The Short Answer
“Production-ready” is not a property of Supabase. It is a property of the decision you did or did not make about where tenancy lives — and most teams never made it explicitly. They wrote a policy when a feature needed one, which is how you end up with forty individually reasonable policies and no model.
That is diagnosable in an afternoon. Four numbers, four SQL queries against your own project, and thresholds that are deliberately uncomfortable: three of the four have a passing value of all or zero, because partial enforcement of a security boundary is not partial safety.
Why the Checklists Do Not Help
There is no shortage of Supabase launch checklists, and they are not wrong. Enable RLS. Do not ship the service key. Set up backups. The problem is that a checklist is a list of topics, and every team reads it, nods at each line, and learns nothing they did not already suspect. Nobody finishes a checklist knowing whether they have a problem.
A measurement is different because it can come back badly. It produces a number you did not know, it can contradict you, and it converts “we should probably look at RLS sometime” into “nine of our sixty-one tables are readable with the anon key.” One of those sentences gets scheduled.
The Four Measurements
Run all four. Individually each is a weak signal; together they describe whether a boundary exists or whether one was approximated forty times.
| Measurement | Fine | Stop building |
|---|---|---|
| 1. RLS coverageWhat fraction of public tables have row-level security enabled? | 100% | Anything below it |
| 2. Policy-free tablesHow many tables have RLS on but no policy attached? | Every one deliberate | Any you cannot explain |
| 3. Bypass surfaceHow many code paths use the service-role key or a SECURITY DEFINER function? | A number you can recite | A number you have to go and count |
| 4. Storage alignmentHow many buckets have object policies that match the table policies governing the same records? | All of them | Any bucket you cannot answer for |
1. RLS coverage
The fraction of tables in your public schema with row-level security enabled. The passing value is 100% and there is no sensible argument for less, because Supabase exposes that schema over PostgREST: a table without RLS is readable by anyone holding the anon key, and the anon key is in your client bundle.
The objection is always the same — nothing queries that table directly. That is a statement about your code, not about what is reachable. The table is an HTTP endpoint whether or not you wrote a client for it.
2. Policy-free tables
Tables with RLS enabled and no policy attached. This is not the dangerous failure — it is the confusing one. RLS with no policy denies everything over PostgREST, so the table looks locked down, and it genuinely is.
The cost arrives later. Somebody hits a permissions error in a hurry and reaches for the service-role key or a SECURITY DEFINER function to make it go away, and now the table is reachable by a path with no boundary on it at all. Measurement two is how measurement three gets worse.
Which is why the threshold is not zero. A table with no policy because nothing should ever reach it over the API is a decision. A table with no policy because nobody got to it yet is a deadline.
3. Bypass surface
Every code path that runs with the service-role key, plus every SECURITY DEFINER function. Both legitimately exist. A webhook handler with no user identity has nothing to filter on; a function that deliberately reads across tenants for an admin view is a real requirement.
The threshold is therefore not a count but a question: can you recite them? A bypass surface you can list from memory is a set of decisions. One you have to go and grep for is a set of accidents, and it is the single most common source of the symptom teams actually report — the same screen showing different things to different people.
4. Storage alignment
Storage policies are a separate policy language from table RLS, evaluated against a different object, and they are the layer most often left open. A file reference secured in the database is not a file secured in the bucket. For every bucket, ask whether its object policy agrees with the policy on the table that references those files. Any bucket you cannot answer for counts against you.
Running Them
The first two are single queries against the Postgres catalog. Paste them into the SQL editor in your Supabase dashboard; both are read-only and neither touches your data.
select c.relname as table_name,
c.relrowsecurity as rls_enabled
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
and c.relkind = 'r'
order by c.relrowsecurity, c.relname;select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
and c.relkind = 'r'
and c.relrowsecurity
and not exists (
select 1 from pg_policy p where p.polrelid = c.oid
)
order by c.relname;Measurement three is a repository search rather than a query — grep for your service-role environment variable and for SECURITY DEFINER, then read each result instead of counting it. Measurement four is a manual pass over your buckets, which is tedious, and which is exactly why it is the one usually skipped.
One warning, because it is the mistake I see most often when teams try to shortcut this: do not ask a language model to audit the schema for you. It will produce a confident, well-written assessment of a database it cannot see, listing policies that sound plausible for an application like yours. Deterministic queries first, interpretation second — the same ordering that repository-level code review requires, and for the same reason.
What Good Looks Like
For contrast, the shape these numbers take on a platform where tenancy was decided once and early. On a multi-tenant utility and IoT platform I led as fractional CTO, row-level security ran across a 31-table PostgreSQL schema serving three separate portals — admins, agencies and tenants.
Three audiences, one boundary, expressed the same way in every table. The count of tables was never the hard part. Deciding once what a tenant was, and then not revisiting it, was.
The same principle, one layer up, is what SureCiteAI’s four-layer isolation does: database RLS, then per-tenant vector namespaces, then session tokens carrying tenant context, then subdomain routing. Four layers, each of which independently prevents the leak. That is what “it holds at 3am” costs.
What To Do On Monday
Run measurements one and two before lunch; they are two queries and they will tell you most of what you need. If coverage is 100% and there are no policy-free tables, you are in better shape than most and the remaining work is the bypass surface. If coverage is not 100%, stop reading and go and fix that, because nothing else on the list matters while a table is openly readable.
And if the numbers come back badly, the useful reframe is that you have found it in the cheapest week it will ever be. The cost of settling tenancy does not rise with the size of your schema. It rises with how many places the old assumption has already been copied.
If you would rather have someone else run this against your project and tell you what it means, that is a Supabase architecture review.
Frequently Asked Questions
Is Supabase production-ready?
Yes, and it is almost never the real question. Postgres row-level security is a mature mechanism and Supabase exposes it faithfully. What is usually not production-ready is one particular implementation of it: policies written per table as features were added, with no single decision about where tenancy lives. The mechanism scales. An accumulation of individually reasonable policies does not.
How many tables should have RLS enabled?
All of them in the public schema, without exception, including the ones you believe nothing reads directly. Supabase exposes the public schema over PostgREST, so a table without RLS is readable by anyone holding the anon key, which ships in your client bundle. "Nothing queries that table" is a statement about your code, not about what is reachable.
Does RLS slow Postgres down?
It can, and the cause is almost always the policy rather than the mechanism. A policy that calls a function per row, or that joins to a membership table without a supporting index, turns a sequential scan into something considerably worse. Wrapping the auth call so it is evaluated once per statement rather than once per row, and indexing whatever the policy filters on, recovers most of it. Measure before assuming you have to choose between safety and speed.
We use the service-role key on the server. Is that wrong?
Not wrong, but it must be countable. The service-role key bypasses RLS entirely by design, which is correct for a webhook handler or a scheduled job and catastrophic for a request path carrying a user identity. The measurement that matters is not whether you use it but whether you can list every place you use it in under a minute. If you cannot, that is the finding.
What if the measurements come back bad?
Then you have found it in the cheapest week it will ever be. Every table added after tenancy is settled inherits it for free; every table added before has to be revisited along with its policies, functions and client queries. Bad numbers before a funded phase of development are a scheduling problem. The same numbers after it are a migration.
Ready to discuss your AI project?
Book a free 30-minute discovery call to explore how AI can transform your business. Or if you already have a codebase, get an instant architecture report at SystemAudit.dev No technical knowledge needed, results in 3 minutes.
About the Author
Nic Chin is an AI Architect and Fractional CTO who helps companies design and deploy production AI systems including RAG pipelines, multi-agent systems, and AI automation platforms. He has delivered enterprise AI solutions across the UK, US, and Europe, and provides AI consulting in Malaysia and Singapore.