# How to Fix the "Permission Denied" Error in Supabase

- Tool: Supabase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Supabase (all plans), @supabase/supabase-js v2+, PostgreSQL 15+
- Last updated: March 2026

## TL;DR

The 'permission denied for table' error (code 42501) in Supabase means the PostgreSQL role making the request lacks GRANT permissions or RLS policies are blocking access. The most common causes are missing GRANT statements for the anon and authenticated roles, tables created by Prisma or raw SQL that skip default permissions, and direct access to restricted schemas like auth or vault. Fix it by granting permissions on your tables and writing proper RLS policies.

## Fixing 'Permission Denied' Errors in Supabase

The permission denied error (PostgreSQL error code 42501) is one of the most common issues in Supabase. It occurs when the API request's PostgreSQL role lacks the necessary GRANT permissions on a table. This is different from RLS — GRANT controls whether a role can access a table at all, while RLS controls which specific rows the role can see. This tutorial explains each cause and provides SQL fixes you can run in the Dashboard SQL Editor.

## Before you start

- A Supabase project where you are experiencing permission errors
- Access to the Supabase Dashboard SQL Editor
- Understanding of the two API roles: anon (unauthenticated) and authenticated (logged-in users)
- Basic SQL knowledge

## Step-by-step guide

### 1. Identify the exact error and the affected table

The permission denied error appears in different forms depending on where you encounter it. In the JS client, it comes back as an error object with code 42501. In the REST API, it appears as a JSON response with the error message. Open your browser's developer console or check the Supabase Dashboard logs to find the exact error message, which includes the table name and the operation that failed.

```
// Example error from the JS client
// {
//   "message": "permission denied for table profiles",
//   "code": "42501",
//   "hint": null,
//   "details": null
// }

// Check in your code
const { data, error } = await supabase.from('profiles').select('*');
if (error) {
  console.error('Error code:', error.code);    // "42501"
  console.error('Message:', error.message);     // "permission denied for table profiles"
}
```

**Expected result:** You can identify the specific table, operation (SELECT, INSERT, UPDATE, DELETE), and role (anon or authenticated) causing the error.

### 2. Grant table permissions to the API roles

Supabase uses two PostgreSQL roles for API access: anon for unauthenticated requests and authenticated for logged-in users. Tables created through the Supabase Dashboard automatically get GRANT permissions for these roles, but tables created via Prisma, raw SQL, or migration tools may not. Run the GRANT command to fix this. You can grant specific operations or all operations depending on your needs.

```
-- Grant all CRUD permissions to both API roles
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLE public.profiles
TO anon, authenticated;

-- If you only want authenticated users to have access
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLE public.profiles
TO authenticated;

-- Grant read-only access to unauthenticated users
GRANT SELECT
ON TABLE public.profiles
TO anon;

-- Grant permissions on ALL tables in public schema
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public
TO anon, authenticated;

-- Grant permissions on future tables too
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLES TO anon, authenticated;
```

**Expected result:** The roles can now access the table. The 42501 error no longer appears for the granted operations.

### 3. Enable RLS and create policies for row-level access

After granting table-level permissions, enable Row Level Security to control which specific rows each user can access. Without RLS enabled, any user with GRANT permissions can access all rows — which is a security risk. With RLS enabled but no policies, all access is silently denied. You need both RLS enabled AND at least one policy for data to flow through the API.

```
-- Enable RLS on the table
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

-- Allow users to read their own profile
CREATE POLICY "Users can read own profile"
ON public.profiles FOR SELECT
TO authenticated
USING ((SELECT auth.uid()) = id);

-- Allow users to update their own profile
CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = id)
WITH CHECK ((SELECT auth.uid()) = id);

-- Allow users to insert their own profile
CREATE POLICY "Users can insert own profile"
ON public.profiles FOR INSERT
TO authenticated
WITH CHECK ((SELECT auth.uid()) = id);
```

**Expected result:** Users can access only their own rows. The combination of GRANT permissions and RLS policies provides complete access control.

### 4. Fix Prisma-specific permission issues

Prisma modifies the default search path and permissions on the public schema, which can cause 42501 errors even on tables that worked before. When Prisma runs migrations, it may revoke default privileges from the public schema. After running Prisma migrations, re-grant permissions to the Supabase API roles. Also make sure your Prisma schema only manages the public schema to avoid interfering with auth and storage schemas.

```
-- Re-grant after Prisma migrations
GRANT USAGE ON SCHEMA public TO anon, authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public
TO anon, authenticated;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public
TO anon, authenticated;

-- Set default privileges for future Prisma-created tables
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO anon, authenticated;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO anon, authenticated;
```

**Expected result:** Tables created by Prisma are accessible through the Supabase API with proper permissions.

### 5. Avoid accessing restricted schemas directly

The auth, vault, and storage schemas are internal to Supabase and not exposed through the auto-generated API. Trying to query auth.users directly from the client will always return a permission denied error. Instead, create a public table (like profiles) that references auth.users via a foreign key, and query that table through the API.

```
-- WRONG: Trying to query auth.users directly from the API
// This will ALWAYS fail with permission denied:
// const { data } = await supabase.from('auth.users').select('*');

-- RIGHT: Create a public profiles table that references auth.users
CREATE TABLE public.profiles (
  id UUID NOT NULL REFERENCES auth.users ON DELETE CASCADE,
  display_name TEXT,
  avatar_url TEXT,
  PRIMARY KEY (id)
);

ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Public profiles are viewable by everyone"
ON public.profiles FOR SELECT
TO anon, authenticated
USING (true);
```

**Expected result:** You query the public.profiles table instead of auth.users, and the API returns data without permission errors.

## Complete code example

File: `fix-permissions.sql`

```sql
-- Complete SQL script to fix permission denied errors in Supabase
-- Run this in the Supabase Dashboard SQL Editor

-- Step 1: Grant schema usage to API roles
GRANT USAGE ON SCHEMA public TO anon, authenticated;

-- Step 2: Grant table permissions for existing tables
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public
TO anon, authenticated;

-- Step 3: Grant sequence permissions (needed for auto-increment IDs)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public
TO anon, authenticated;

-- Step 4: Set default privileges for future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO anon, authenticated;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO anon, authenticated;

-- Step 5: Enable RLS on all public tables (run per table)
-- Replace 'your_table' with each table name
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;

-- Step 6: Create RLS policies (example for profiles table)
CREATE POLICY "Users can read own profile"
ON public.profiles FOR SELECT
TO authenticated
USING ((SELECT auth.uid()) = id);

CREATE POLICY "Users can update own profile"
ON public.profiles FOR UPDATE
TO authenticated
USING ((SELECT auth.uid()) = id)
WITH CHECK ((SELECT auth.uid()) = id);

CREATE POLICY "Users can insert own profile"
ON public.profiles FOR INSERT
TO authenticated
WITH CHECK ((SELECT auth.uid()) = id);

-- Step 7: Verify permissions are correct
SELECT grantee, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE table_schema = 'public'
ORDER BY table_name, grantee;
```

## Common mistakes

- **Confusing GRANT permissions with RLS policies and only creating one of them** — undefined Fix: You need both: GRANT controls table-level access, and RLS controls row-level access. Without GRANT you get 42501 errors. Without RLS policies you get empty results.
- **Trying to query auth.users or other internal schemas directly from the client-side API** — undefined Fix: Create a public table that references auth.users via foreign key. The auth schema is not exposed through the auto-generated API by design.
- **Running Prisma migrations that revoke default privileges on the public schema without re-granting them** — undefined Fix: Add GRANT statements to a post-migration script. Configure Prisma to only manage schemas = ["public"] to avoid touching auth and storage schemas.

## Best practices

- Always enable RLS on every table in the public schema, even if you think the data is not sensitive
- Use ALTER DEFAULT PRIVILEGES to automatically grant permissions on future tables
- Check the Dashboard Logs → API section to see the exact query and role that triggered permission errors
- Create a post-migration script that re-grants permissions after any schema change tool runs
- Never expose the service_role key to bypass permission issues — fix the underlying GRANT and RLS configuration instead
- Use the information_schema.role_table_grants view to audit current permissions across all tables
- Test API access as both anon and authenticated roles to verify permissions work for each use case
- Keep Prisma configured with schemas = ["public"] to prevent it from interfering with Supabase internal schemas

## Frequently asked questions

### What is the difference between GRANT permissions and RLS policies?

GRANT controls whether a PostgreSQL role can access a table at all. RLS controls which specific rows the role can see within that table. You need both: GRANT on the table plus RLS policies for row-level filtering.

### Why do tables created in the Dashboard work but tables created by Prisma do not?

The Supabase Dashboard automatically grants permissions to the anon and authenticated roles when creating tables. Prisma and other migration tools do not add these grants, so you must add them manually after migrations.

### Can I use the service_role key to bypass permission errors?

Yes, the service_role key bypasses all RLS and permission checks. However, this is only safe in server-side code. Never use it in client-side code. Instead, fix the underlying GRANT and RLS configuration.

### Why do I get permission denied for schema auth?

The auth schema is internal to Supabase and not exposed through the API. You cannot query auth.users directly. Create a public.profiles table with a foreign key to auth.users and query that instead.

### How do I grant permissions on a table in a custom schema?

First expose the schema to the API in Dashboard → Settings → API → Schema. Then run GRANT USAGE ON SCHEMA your_schema TO anon, authenticated and grant table-level permissions as usual.

### Will granting permissions make my data public?

No. GRANT only allows the role to attempt operations on the table. RLS policies still control which rows are accessible. With RLS enabled and no policies, all access returns empty results even with GRANT permissions.

### Can RapidDev help resolve persistent permission issues in my Supabase project?

Yes. RapidDev can audit your Supabase project's permission configuration, fix GRANT and RLS issues, and set up a sustainable permissions management workflow that works with your migration tools.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-fix-permission-denied-error-in-supabase
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-fix-permission-denied-error-in-supabase
