Row Level Security is the difference between a demo and a multi-tenant product. These are the patterns we reuse on almost every Supabase project.
Tenant column + membership join
Every tenant-scoped table gets an organization_id. Policies check auth.uid() against a memberships table rather than embedding roles on the user row. That keeps invites, roles, and soft-deletes simple.
Separate read and write policies
One policy for SELECT, another for INSERT/UPDATE/DELETE. Readers and editors rarely share the same constraints. Collapsing them into a single USING/WITH CHECK block is how subtle privilege bugs sneak in.
Service role stays server-only
- Never expose the service role key to the browser.
- Use it only in trusted server functions for admin jobs and webhooks.
- Prefer user-scoped clients for product paths so RLS is always exercised.
Test policies like application code
Spin up two users in different orgs and assert denied reads. Policy regressions are silent until a customer emails you a screenshot of someone else's data.