What does USING (true) mean in a Supabase RLS policy?
By StackSiren · Published
using (true) means the policy's condition always passes for every role the policy targets. Every row those roles can already reach through grants is allowed. That is correct for genuinely public read-only data, and wrong for anything private.
People often add it after enabling RLS made the app return blank pages. The blank page was the safe state. An always-true policy is the open door. Supabase's RLS docs use using (true) on purpose for public feeds; Bolt's Security Audit and Supabase's advisors flag the same pattern as RLS Policy Always True when it looks too open.
What USING (true) actually does
A policy is an extra WHERE clause Postgres adds to every matching query. Compare:
-- Always true: every row the role is granted on
create policy "notes_all_read" on public.notes
for select to anon, authenticated
using (true);
-- Owner-scoped: only the caller's rows
create policy "notes_select_own" on public.notes
for select to authenticated
using ((select auth.uid()) = user_id);
Important details:
- The
toclause still matters.to authenticated using (true)does not mean "my users". It means anyone who can sign up. On an app with open registration, that is close to public. usingfilters existing rows (select / update / delete).with check (true)on insert or update accepts any new row shape the grants allow — including rows owned by someone else if your columns permit it.- Grants still apply.
using (true)cannot invent a privilege the role doesn't have, but on many projectsanonandauthenticatedalready have broad grants onpublictables.
When USING (true) is OK
It is appropriate for public, read-only data — for example published posts, a restaurant menu, or a marketing FAQ — when you also lock writes:
alter table public.announcements enable row level security;
revoke all on table public.announcements from anon, authenticated;
grant select on table public.announcements to anon, authenticated;
create policy "Anyone can read announcements"
on public.announcements for select
to anon, authenticated
using (true);
That pattern matches Supabase's own public-feed example: select for everyone, no insert/update/delete grants for client roles. Prefer a real predicate when you have one (published = true, is_public = true) so draft rows stay hidden:
create policy "posts_public_read" on public.posts
for select to anon, authenticated
using (published = true);
When it is not OK
Never leave using (true) (or with check (true)) on:
- Per-user notes, orders, messages, profiles, or settings
- Anything with PII, payment state, or API tokens
- Write policies (
insert/update/delete) unless you truly intend a public writable board — and even then, think hard about spam and abuse
A close cousin that looks safer but isn't: using (auth.uid() is not null). That only proves someone is signed in. Any registered user still sees every row.
Safer rewrites
Owner-only table (replace an always-true catch-all):
drop policy if exists "notes_all_access" on public.notes;
create policy "notes_select_own" on public.notes
for select to authenticated
using ((select auth.uid()) = user_id);
create policy "notes_insert_own" on public.notes
for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "notes_update_own" on public.notes
for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy "notes_delete_own" on public.notes
for delete to authenticated
using ((select auth.uid()) = user_id);
Public read, owner write:
drop policy if exists "posts_open" on public.posts;
create policy "posts_public_read" on public.posts
for select to anon, authenticated
using (published = true);
create policy "posts_owner_write" on public.posts
for all to authenticated
using ((select auth.uid()) = author_id)
with check ((select auth.uid()) = author_id);
Full walkthroughs with curl tests: Lovable Supabase RLS and Bolt + Supabase RLS checklist. If a secret key was used to "get past" RLS, rotate it: service_role key exposed.
Finding always-true policies with pg_policies
Run this in the SQL editor on your project:
select schemaname, tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
and (
qual is null
or qual in ('true', '(true)')
or with_check in ('true', '(true)')
or qual ilike '%true%'
or with_check ilike '%true%'
)
order by tablename, policyname;
qual is the using expression; with_check is the write check. Review every hit by hand — a policy whose expression contains the word true might be fine (published = true), while a bare true on a private table is not.
Also open Supabase Database → Linter / Security Advisor, or Bolt's database Security tab if you use Bolt Cloud / a connected project. Both surface "always true" style findings. Dismiss a warning only when you have intentionally made that table public and removed write grants.
Prove the rewrite
# Signed-out: private tables must not return rows
curl -s "https://YOUR-PROJECT.supabase.co/rest/v1/notes?select=*&limit=5" \
-H "apikey: YOUR_PUBLISHABLE_KEY"
# Public feed: empty or published rows only — never drafts
curl -s "https://YOUR-PROJECT.supabase.co/rest/v1/posts?select=id,title,published&limit=5" \
-H "apikey: YOUR_PUBLISHABLE_KEY"
Then repeat with two signed-in users as in the Lovable and Bolt guides. Our Supabase RLS checker covers the signed-out case on sites you own. More detail: the Supabase RLS guide.
Common ways this shows up in AI-built apps
- Blank page after enabling RLS. Someone turns RLS on, the app returns no rows, and the quickest "fix" is a policy with
using (true)foralloperations. The UI works again; so does every stranger with your publishable key. - No sign-in yet. Bolt's security docs note that without sign-in, builders often write policies that let any visitor read and write. The Security Audit then flags RLS Policy Always True. That warning is only acceptable if you truly want a public writable table.
for allwithusing (true). One broad policy covers select, insert, update and delete. Prefer one policy per command so you can keep public read while locking writes.- Public Storage mistaken for public tables. A public Storage bucket serves files by URL; that is separate from table RLS. Don't open a
filesmetadata table withusing (true)just because the bucket is public — scope the metadata to owners and use signed URLs for private objects. - Trusting the linter dismiss. Advisors flag always-true rules because they are a common foot-gun. Dismiss only after you have select-only grants and a clear reason the data is public.
How builders and advisors surface it
- Supabase Database linter / Security Advisor lists permissive policies. Open each finding and read the SQL before you ignore it.
- Bolt database Security (Security Audit) uses the same family of checks and shows RLS Policy Always True with Ask Bolt to fix. Review the SQL Bolt proposes; ask specifically for
auth.uid() = user_idandwith checkon writes. - Lovable security scans similarly flag access rules that let everyone through. Still run the curl tests — automated checks won't know your product rules.
None of these tools replace a signed-out and second-user test against your own project.
Step-by-step: replace one bad policy
Suppose pg_policies shows this on a private orders table:
-- Bad: any role the policy targets sees and changes every order
create policy "orders_open" on public.orders
for all to authenticated
using (true)
with check (true);
Replace it like this:
begin;
drop policy if exists "orders_open" on public.orders;
create policy "orders_select_own" on public.orders
for select to authenticated
using ((select auth.uid()) = user_id);
create policy "orders_insert_own" on public.orders
for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "orders_update_own" on public.orders
for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy "orders_delete_own" on public.orders
for delete to authenticated
using ((select auth.uid()) = user_id);
-- Optional but recommended on older projects with broad defaults
revoke all on table public.orders from anon;
grant select, insert, update, delete on table public.orders to authenticated;
commit;
Then run the signed-out curl (should not return orders) and the two-user test. If the app breaks, fix the client to send the signed-in session — don't reopen the policy.
Related reading
- Bolt + Supabase RLS checklist
- Setting up Supabase RLS in a Lovable app
- My Supabase service_role key is exposed. What now?
FAQ
Why did my AI builder add USING (true)?
Often to make the UI work again after RLS was enabled with no policies. Ask it for owner-scoped policies with with check instead, then re-test.
Is USING (true) on authenticated only safer?
Only a little. Anyone who registers becomes authenticated. For private data, compare auth.uid() to an owner column.
Does dropping the policy lock the table? With RLS enabled and no matching policy, client roles get zero rows (or a grant error). That is the safe default until you add a scoped policy.
What about WITH CHECK (true)?
Same idea for writes: every insert/update the grants allow is accepted. Prefer with check ((select auth.uid()) = user_id) on owner tables.
How do I keep this from coming back?
Re-run the pg_policies query after every schema change, and scan the live app after deploys. StackSiren's first scan is free; it looks at what your public API returns to a signed-out visitor on sites you own.
Is using (published = true) the same as using (true)?
No. A real column predicate still filters rows. Advisors may still mention policies that look broad; judge them by whether unpublished or private rows can leak.
Can I use using (true) for insert on a contact form?
Sometimes. A public insert-only table (contact messages) can use with check (true) for anon insert if you revoke select/update/delete from anon so visitors cannot read or wipe other people's submissions. Prefer rate limiting and spam controls on top. Never combine public insert with public select on the same sensitive table.
Fix guides for this topic
Platform checklists: Lovable · Bolt