Building Multi-User Apps with Supabase RLS
2026-01-08 • 4 min read
Building Multi-User Apps with Supabase RLS
When you move from toy projects to real products, the hard problem isn't styling cards. It's isolation: how do you make sure each user only sees their own data? With Supabase, the real answer is not “checking user IDs in the frontend”, it's Row Level Security (RLS).
What RLS is, and why it matters
Row Level Security is a Postgres feature that lets you define policies on tables which execute for every query. Instead of trusting the client to send the “right” filters, the database enforces things like:
CREATE POLICY "Users can see their jobs"
ON jobs FOR SELECT
USING (user_id = auth.uid());With RLS enabled, even if someone opens DevTools and calls the Supabase API manually, they still only get rows where the condition is true. That's a big step up from “we filter by userId in React and hope no one cheats.”
How ApplyCraft uses RLS
In my ApplyCraft project (an AI-powered job tracker), every job, note, and profile is scoped by the authenticated user. The core tables share the same idea:
jobs.user_id = auth.uid()job_notes.user_id = auth.uid()profiles.id = auth.uid()
Policies enforce that users can only select, insert, update, and delete rows where that check passes. Even the AI endpoint, which updates outreach fields on a job, verifies that the job belongs to the current user before writing.
Common mistakes with RLS
- Enabling RLS but leaving “allow all” policies. You've technically turned it on, but effectively disabled it. Be explicit.
- Forgetting write policies. It's easy to add SELECT and forget INSERT/UPDATE/DELETE, then wonder why your app silently fails.
- Trusting only frontend checks. If your table allows unrestricted select, users can bypass your UI and call the REST endpoint directly.
- Not joining on user-owned keys. If a table depends on a foreign key (like notes → jobs), your policies should reflect that ownership chain.
Supabase RLS is the difference between “multi-user UI” and “multi-tenant product.” Once you start treating the database as the security gate, your frontend becomes simpler and your app becomes much harder to abuse.