A public contact/lead form needs anonymous users to insert rows into a table, so RLS was enabled with CREATE POLICY ... FOR INSERT TO anon WITH CHECK (true) plus GRANT INSERT ON the table TO anon and GRANT USAGE, SELECT ON SEQUENCE TO anon. Inserts still fail, alternating between "permission denied for table" (42501) and "new row violates row-level security policy for table" (42501). The two errors have different causes: the first is the GRANT layer, the second means the policy ran and returned false — the session was not actually inserting as a role covered by the policy. A maintainer diagnosed that users of the form were not actually signed in as the anon role the policy targeted.
"new row violates row-level security policy" (42501) on INSERT with TO anon WITH CHECK (true)
Call signInAnonymously() once at app start before the form submits, instead of using a bare anon-key client. That gives the request a real session JWT (an anonymous user with isanonymous: true) which travels as the authenticated role, so a policy written TO authenticated passes — and you can attach real contact data to that user later. The reporter confirmed the form worked after applying this configuration, and another user confirmed signInAnonymously() resolved the same failure. Minimal working config for a public lead form: create policy "public form can create quotes" on the table for insert to anon, authenticated with check (true); and call signInAnonymously() client-side. If you must stay fully key-only (no session), then TO anon WITH CHECK (true) plus both grants — grant insert on the table to anon and grant usage, select on the sequence to anon — is the matching pair; mixing one half of each pattern produces exactly the flip-flopping permission-denied vs policy-violation errors. Verify grants with select hastableprivilege('anon','public.yourtable','insert');. Source: https://github.com/supabase/supabase/issues/40488