RowShield
Guides

From prototype to production: the Supabase checklist

Prototypes earn their keep by skipping ceremony, and Supabase rewards that: click through the table editor, paste the anon key, ship the demo. The trouble arrives later, when the prototype becomes the product and the shortcuts become the configuration. Nobody decides to run production on demo posture; it happens by succession, one shipped milestone at a time.

The pass below converts prototype posture into production posture. It is ordered by consequence — data exposure first, key hygiene second, operational niceties last — and each step states what done looks like. Work through it with the dashboard open and the SQL editor in another tab; the whole pass fits inside a day for a modest schema.

One caveat up front: this is a manual sequence, written to be read while clicking through the dashboard. RowShield does not automate the checklist itself; it takes over afterwards, watching the states you have just cleaned so they stay clean between one release and the next.

RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.

Every table answers to a policy

Production rule one: every table PostgREST can reach carries RLS, forced, with policies encoding real intent. The demo habit of leaving a scratch table open — a feedback form, a waitlist, a cache — is how production leaks begin, because scratch tables accumulate rows that stop being scratch: real emails, real messages, real orders.

Audit the schema honestly before writing anything. List the tables and, for each, answer in a sentence who legitimately reads it and who legitimately writes it. Tables without a clean sentence are design smells as much as security holes — the waitlist nobody remembers creating usually lacks an owner in the application code too.

ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.profiles FORCE ROW LEVEL SECURITY;

CREATE POLICY "profiles_self_access"
  ON public.profiles
  FOR ALL
  TO authenticated
  USING (id = (SELECT auth.uid()))
  WITH CHECK (id = (SELECT auth.uid()));

Keys and buckets

Second: the key inventory. The anon key ships publicly and is bounded by policies; the service role key bypasses every policy and must exist only in server-side environments. Search the repository and hosting settings for accidental placements, rotate anything doubtful, and wire deploys so the service key reaches functions through secrets rather than source.

Third: storage. Buckets flipped public for a screenshot tend to remain public through launch. Walk each bucket deliberately, confirm its objects’ sensitivity, and prefer signed URLs for anything user-specific. A public bucket is not always wrong — product images belong public — but the decision should be made once, deliberately, rather than inherited from a demo.

Operational posture

Beyond exposure, production differs in recoverability and visibility. Take a manual backup and confirm you can actually restore it into a scratch project; enable point-in-time recovery if the plan offers it and the data merits it; check that failed sign-in attempts and unusual volumes would surface somewhere a human reads. None of this is glamorous, and all of it is cheaper before launch than after.

Index the columns your policies filter on — owner ids, membership ids, status columns referenced inside USING clauses. An unindexed predicate turns every row-level check into a scan as the table grows, and the slowdown lands on exactly the queries your busiest users run. This is the rare security task that pays a performance dividend too.

From pass to habit

A clean pass decays without a watcher, because the forces that produced demo posture — speed, forgetfulness, optimism — resume the moment launch adrenaline fades. Decide now who notices next time. Either a person re-runs this page monthly, or a scanner does it continuously and pages on change; the second option survives busy quarters better.

RowShield occupies that second slot: scheduled scans diff each table’s state against the last known snapshot, alerts fire on transitions only, and remediation SQL comes generated from your actual columns, FORCE included. It reads catalogue and bucket metadata only — never rows — and is an independent product unaffiliated with Supabase. Hand it the watching; keep the judgment.

Frequently asked

Which single step matters most?
Enable and force RLS on every public table, then give each a real policy. That one step collapses most realistic exposure in a typical prototype, because it converts an internet-readable database into one where access is granted deliberately rather than by omission.
Can I launch first and secure later?
You can, and plenty do. The honest framing is that launch day becomes the moment strangers can start probing, so deferring means accepting a window in which the anon key reads whatever migrations forgot to fence. Compress the window: exposure steps first, operational steps after revenue.
Does this checklist cover compliance?
No. It covers database posture: what the endpoint serves, which keys can bypass policy, which files are public. Regulatory duties around personal data sit above this layer and vary by jurisdiction and product, so treat the checklist as necessary groundwork rather than a verdict.

Check your project in about ten seconds

Paste a URL. No signup, no writes, nothing stored.

Run the free audit
supabase production checklistsupabase go live checklistprototype to production supabasesupabase launch security review