PostgreSQL error 42501 indicates insufficient privileges to access a table, commonly caused by missing RLS policies, incorrect grants, or authentication issues in Supabase. This error occurs when your application tries to read, insert, or update data without proper permissions configured.
The error 42501 is a PostgreSQL permission denied error that Supabase inherits. It occurs when your database role (anon, authenticated, or service_role) lacks the necessary privileges to perform an operation on a table. This is typically caused by Row Level Security (RLS) policies being too restrictive, missing altogether, or by insufficient database grants. RLS is a PostgreSQL feature that controls row-level access based on the current user. When RLS is enabled on a table without proper policies defined, all access is denied by default. The error can also occur when accessing tables in custom schemas without proper grants, or when database object ownership and permissions are not correctly configured.
First, verify whether RLS is enabled on your table and what policies exist:
-- Check if RLS is enabled
SELECT relname, relrowsecurity
FROM pg_class
WHERE relname = 'your_table_name';
-- View all policies on the table
SELECT policyname, cmd, qual, with_check
FROM pg_policies
WHERE tablename = 'your_table_name';If the table has relrowsecurity = true but no policies appear, RLS is blocking all access. If no policies exist at all, create them (see next steps).
If RLS is enabled but you lack SELECT policies, create one. For public read access:
CREATE POLICY "Enable read access for all users" ON your_table_name
FOR SELECT
USING (true);For authenticated users only:
CREATE POLICY "Enable read for authenticated users" ON your_table_name
FOR SELECT
USING (auth.role() = 'authenticated');For row-level filtering (e.g., users see only their own rows):
CREATE POLICY "Users can see their own rows" ON your_table_name
FOR SELECT
USING (auth.uid() = user_id);Replace your_table_name, user_id, and the conditions with your actual table and column names.
If you're getting 42501 on INSERT or UPDATE operations, create policies for those:
-- Allow authenticated users to insert rows
CREATE POLICY "Enable insert for authenticated users" ON your_table_name
FOR INSERT
WITH CHECK (auth.role() = 'authenticated');
-- Allow users to update only their own rows
CREATE POLICY "Users can update own rows" ON your_table_name
FOR UPDATE
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);The USING clause filters which existing rows can be modified. The WITH CHECK clause validates new/updated row data.
Sometimes the issue is grants, not RLS. Check if the anon/authenticated roles have SELECT privilege:
-- Check grants on your table
SELECT grantee, privilege_type
FROM information_schema.role_table_grants
WHERE table_name = 'your_table_name';If grants are missing, add them:
-- Grant permissions for the anon role
GRANT SELECT ON your_table_name TO anon;
GRANT INSERT ON your_table_name TO anon;
-- Grant permissions for the authenticated role
GRANT SELECT, INSERT, UPDATE ON your_table_name TO authenticated;
-- For custom schemas, also grant schema usage
GRANT USAGE ON SCHEMA my_schema TO anon, authenticated;
GRANT SELECT ON ALL TABLES IN SCHEMA my_schema TO anon, authenticated;Ensure your application is sending a valid JWT token and that Supabase can identify the user:
// In your frontend, ensure you're signed in
const { data: { user } } = await supabase.auth.getUser();
console.log("Current user:", user?.id);
// Use authenticated client for queries (not anon key)
const { data, error } = await supabase
.from('your_table_name')
.select()
.eq('user_id', user.id);
if (error) console.error("Error:", error.code, error.message);For backend operations, ensure you're using the service_role key which bypasses RLS:
const supabase = createClient(url, SERVICE_ROLE_KEY); // Bypasses RLSUse Supabase's authenticated/anon testing or directly test with the correct roles:
-- Test with anon role
SET ROLE anon;
SELECT * FROM your_table_name LIMIT 1;
-- Test with authenticated role
SET ROLE authenticated;
SELECT * FROM your_table_name LIMIT 1;
-- Reset to postgres
RESET ROLE;If a policy fails in one role but works in another, the policy logic is role-specific. Review the policy condition (USING/WITH CHECK) to ensure it's correct for that role.
If you're unsure whether the issue is RLS or grants, temporarily disable RLS to diagnose:
ALTER TABLE your_table_name DISABLE ROW LEVEL SECURITY;If the error disappears, the problem is RLS policies. If it persists, the problem is grants. Once you identify the issue, re-enable RLS:
ALTER TABLE your_table_name ENABLE ROW LEVEL SECURITY;Note: Use this only for diagnosis. RLS should be enabled in production for security.
PERMISSIVE vs RESTRICTIVE policies: By default, RLS uses PERMISSIVE policies (allow access if any policy evaluates true). RESTRICTIVE policies require all policies to evaluate true. Mixing them requires careful logic planning. Custom schemas: Tables in non-public schemas require explicit schema and table grants to anon/authenticated roles, in addition to RLS policies. The public schema handles this automatically. Upsert operations: The upsert (MERGE) operation requires INSERT, UPDATE, and SELECT policies all to be satisfied, so ensure all three are properly configured. PostgreSQL 15+: Views now support 'security_invoker = true' to honor RLS policies, unlike the default security_definer behavior. Service role bypass: The service_role key bypasses RLS entirely for backend operations, making it useful for administrative tasks but unsafe for client-facing queries.
email_address_not_authorized: Email sending to this address is not authorized
Email address not authorized for sending in Supabase Auth
reauthentication_needed: Reauthentication required for security-sensitive actions
Reauthentication required for security-sensitive actions
no_authorization: No authorization header was provided
How to fix "no authorization header was provided" in Supabase
otp_expired: OTP has expired
How to fix 'otp_expired: OTP has expired' in Supabase
bad_oauth_state: OAuth state parameter is missing or invalid
How to fix 'bad_oauth_state: OAuth state parameter missing' in Supabase