If you work with Postgres, you've almost certainly run into this:
ERROR: permission denied
SQLSTATE: 42501At first glance 42501 looks like a single, generic error. It isn't. Postgres is very precise about where a permission check fails, and the same SQLSTATE can come from the database, the schema, the object, a sub-object, ownership, or a row-level policy. The message text changes, but the code stays the same.
This article walks through the different categories of permission failure that all surface as ERROR 42501, explains why each one happens, and shows where Postgres behaves differently from MySQL.
What is ERROR 42501?
- SQLSTATE:
42501 - Class:
42— Syntax Error or Access Rule Violation - Meaning: Insufficient privilege
Postgres raises 42501 whenever the planner or executor decides the current role doesn't satisfy a required access rule. The reason it shows up in so many situations is the thing worth internalizing: Postgres has a layered permission model, and a check can fail at any layer.
1. Database-level permission errors
Typical message
ERROR: permission denied for database mydbThe role lacks the CONNECT privilege on the database, which is what you get after something like:
REVOKE CONNECT ON DATABASE mydb FROM app_user;The privilege you need here is CONNECT on the database. This is the outermost gate: even if a role owns schemas or tables inside mydb, it cannot touch the database at all without CONNECT.
2. Schema-level permission errors
Typical message
ERROR: permission denied for schema publicPostgres checks schema permissions before table permissions, so revoking USAGE on a schema cuts off everything underneath it:
REVOKE USAGE ON SCHEMA public FROM app_user;Two privileges matter at this level:
USAGElets a role access objects inside the schema.CREATElets a role create new objects in the schema.
The pitfall that catches people: granting table privileges without granting schema USAGE still gives you ERROR 42501. The table grant is correct, but the role can't reach the schema that holds the table.
3. Table and view permission errors
Typical message
ERROR: permission denied for relation ordersRequired privileges
| Operation | Required privilege |
|---|---|
| SELECT | SELECT |
| INSERT | INSERT |
| UPDATE | UPDATE |
| DELETE | DELETE |
GRANT SELECT ON orders TO app_user;Views add a wrinkle. Postgres also checks permissions on the underlying tables the view reads from, unless the view is defined with SECURITY DEFINER. So a role can have SELECT on the view and still be denied because it lacks access to a base table.
4. Column-level permission errors
Typical message
ERROR: permission denied for column salaryPostgres supports column-level privileges, which means you can grant access to some columns of a table but not others:
GRANT SELECT (id, name) ON employees TO analyst;With that grant in place, querying salary fails with ERROR 42501. In practice this most often shows up when an ORM generates SELECT * queries that quietly pull in a column the role was never granted.
5. Sequence permission errors (a very common case)
Typical message
ERROR: permission denied for sequence orders_id_seqIn Postgres, sequences are independent objects, not something bundled into the table. For auto-increment columns (SERIAL or IDENTITY), the executor has to read from the sequence to get the next value, so the sequence needs its own grant:
USAGEon the sequence is typically enough forINSERT.SELECTis only needed if the application explicitly queries the sequence value.
GRANT USAGE ON SEQUENCE orders_id_seq TO app_user;This is the one that surprises people coming from MySQL. Having INSERT on the table is not enough in Postgres if the insert touches a sequence-backed column.
6. Function and procedure permission errors
Typical message
ERROR: permission denied for function calculate_tax(integer)Calling a function requires the EXECUTE privilege:
GRANT EXECUTE ON FUNCTION calculate_tax(integer) TO app_user;There's a security nuance underneath that's worth knowing:
SECURITY DEFINERfunctions execute with the owner's privileges.- Regular functions execute with the caller's privileges.
Getting this distinction wrong is a frequent source of ERROR 42501. A function that works fine when the owner calls it can fail for everyone else if it was never declared SECURITY DEFINER.
7. Ownership-based permission errors
Typical message
ERROR: must be owner of relation ordersSome operations can't be granted at all. They require ownership of the object, full stop:
ALTER TABLEDROP TABLETRUNCATEALTER TYPEREINDEX
The key point: a role with very broad privileges can still hit ERROR 42501 here, because no amount of GRANT substitutes for owning the object.
8. Row-Level Security (RLS) permission errors
Typical symptom
ERROR: permission denied for relation ordersThis is the sneaky one. The table privileges look completely correct and you still get ERROR 42501.
Concrete example
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY orders_tenant_policy
ON orders
USING (tenant_id = current_setting('app.tenant_id')::int);If the application:
- Has
SELECTprivilege onorders - But does not set
app.tenant_id
SET app.tenant_id = '123';then no rows match the policy, and Postgres denies access entirely with ERROR 42501.
Why this is confusing
RLS failures look exactly like ordinary permission problems, but the root cause is policy logic, not a missing GRANT. You can spend a long time auditing privileges that were never the issue.
Postgres vs MySQL: why permission errors feel different
Permission model comparison
| Aspect | Postgres | MySQL |
|---|---|---|
| Permission granularity | Database → schema → object → column | Mostly database & table |
| Schema permissions | Explicit (USAGE) | Implicit |
| Sequences | Separate objects | Embedded in table |
| Column-level grants | Yes | Limited |
| Row-Level Security | Native | Not native |
| Ownership enforcement | Strict | Looser |
MySQL-specific comparison examples
Auto-increment INSERT
Postgres
INSERT INTO orders (...) VALUES (...);
-- Requires:
-- INSERT on table
-- USAGE on sequenceMySQL
INSERT INTO orders (...) VALUES (...);
-- Requires only:
-- INSERT on tableSchema access
Postgres
REVOKE USAGE ON SCHEMA public FROM app_user;
-- Any table access now fails with ERROR 42501MySQL
REVOKE ALL ON db.* FROM app_user;
-- No separate schema-level permission conceptRow-level enforcement
Postgres
- Access can be denied even with full table privileges, because of RLS policies
MySQL
- Requires application-level filtering
- No built-in equivalent to RLS
A practical debugging checklist for ERROR 42501
When you hit ERROR 42501, work outside-in and check in this order:
- Database – Does the role have
CONNECT? - Schema – Does it have
USAGE? - Object – Table, view, or function privileges?
- Sub-object – Column or sequence permissions?
- Ownership – Is this an owner-only operation?
- Policies – Is Row-Level Security blocking access?
Following the same layering Postgres uses internally usually points you at the missing piece quickly.
Final thoughts
ERROR 42501 isn't vague. It's Postgres enforcing a security model that is deliberately strict and expressive.
Compared to MySQL, Postgres trades some convenience for explicitness, fine-grained control, and stronger security guarantees. That's why the same insert that just works in MySQL can need a separate sequence grant in Postgres.
Once the layered permission model clicks, ERROR 42501 stops being mysterious. It turns into a fairly precise signal that tells you which layer to look at.