PostgreSQL raises SQLSTATE 0L000 (invalid_grantor) when a GRANT or REVOKE statement is executed by a role that is not authorized to grant or revoke the specified privilege on the target object. The error message in psql or application logs typically appears as:
ERROR: grantor must be owner or have WITH GRANT OPTION
or, in contexts involving role membership grants:
ERROR: must have admin option on role "<role_name>"
What This Error Means
SQLSTATE class 0L is a small SQL-standard class dedicated to grantor-related errors. The only defined condition in this class is invalid_grantor (0L000). PostgreSQL surfaces it when the role executing a GRANT or REVOKE lacks the authority to make that privilege change, either because it does not own the object or because it does not hold the privilege itself with WITH GRANT OPTION (for object privileges) or WITH ADMIN OPTION (for role membership).
The PostgreSQL privilege model distinguishes between holding a privilege and being able to grant it. A role that received SELECT on a table without WITH GRANT OPTION can query the table, but it cannot turn around and grant SELECT to a third role. Only the object owner, a superuser, or a role explicitly granted the privilege WITH GRANT OPTION may issue a downstream GRANT.
The same logic applies to role membership. Granting role app_reader to another role requires that the grantor either is a superuser or holds membership in app_reader with WITH ADMIN OPTION. When neither condition is met, PostgreSQL raises 0L000. Transactions that encounter this error are not aborted — the statement fails, but the session remains open and usable.
Common Causes
Attempting to grant a privilege you hold without
WITH GRANT OPTION.
A role was grantedINSERT ON ordersbut notINSERT ON orders WITH GRANT OPTION. It tries toGRANT INSERT ON orders TO another_role, and PostgreSQL rejects it.Granting role membership without
WITH ADMIN OPTION.
A role is a member ofreporting_rolebut was not added withWITH ADMIN OPTION. It tries toGRANT reporting_role TO new_userand the error is raised.Running
GRANTas the wrong database user in application deployment scripts.
A migration or deployment script connects as a limited application user (e.g.,app_user) rather than the schema owner or a superuser, then tries to grant privileges on tables it does not own.Revoking a privilege on behalf of a grantor that is not yourself.
REVOKE ... GRANTED BY <other_role>requires that the executing role is a superuser or the specified grantor. Running this as an unprivileged role causes0L000.
How to Fix invalid_grantor
Grant the privilege
WITH GRANT OPTIONto the issuing role first.
Before a non-owner role can re-grant a privilege, it must itself hold that privilege with the grant option:-- As the table owner or superuser: GRANT SELECT ON orders TO reporting_user WITH GRANT OPTION; -- Now reporting_user can grant to others: GRANT SELECT ON orders TO analyst_user;Grant role membership
WITH ADMIN OPTIONto the issuing role.
If a role needs to manage membership of another role:-- As a superuser or a member of reporting_role WITH ADMIN OPTION: GRANT reporting_role TO manager_role WITH ADMIN OPTION; -- Now manager_role can grant reporting_role to others: GRANT reporting_role TO new_employee;Execute the
GRANTas the object owner or a superuser.
The simplest fix for deployment scripts is to connect as the role that owns the schema or tables:-- Connect as the schema owner: \c mydb schema_owner GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_readonly;In
psqlconnection strings or application configs, ensure the deploying user matches the object owner.Use
GRANT ... GRANTED BYsyntax carefully (PostgreSQL 16+).
PostgreSQL 16 introducedGRANTED BYas an explicit clause. Only superusers can specify an arbitrary grantor role. If you are using this syntax, either remove theGRANTED BYclause or run the statement as a superuser:-- Requires superuser: GRANT SELECT ON orders TO analyst GRANTED BY schema_owner;Check current grant options before attempting downstream grants.
You can inspect which roles hold grant options viainformation_schema.role_table_grantsor\dpin psql:SELECT grantee, privilege_type, is_grantable FROM information_schema.role_table_grants WHERE table_name = 'orders';
Additional Information
- SQLSTATE class
0Lis defined by the SQL standard and contains only0L000. PostgreSQL does not define any sub-conditions beneath it. - The
WITH GRANT OPTIONmechanism for object privileges andWITH ADMIN OPTIONfor role membership are conceptually parallel but use different syntax and are stored separately (pg_class.relaclfor tables vs.pg_auth_members.admin_optionfor roles). - In PostgreSQL 16, additional role-level membership attributes (
SET,INHERIT) were introduced alongsideADMIN OPTION, which may affect which operations require admin option. See PostgreSQL 16 release notes for details. - Application frameworks (Rails, Django, Alembic) that run migrations as the application database user rather than the schema owner are a common source of this error in automated deployments. Always verify the migration-time database role.
- Related SQLSTATE codes in the privilege domain:
42501(insufficient_privilege) — raised when a role tries to use a privilege it does not hold (as opposed to granting one it cannot).
Frequently Asked Questions
Why does my superuser never see this error?
PostgreSQL superusers bypass all privilege checks, including grantor checks. If you want to reproduce or test this error, you must operate as a non-superuser role.
Can I grant privileges that I own without WITH GRANT OPTION?
Yes — if you own the object (the role is the OWNER of the table, sequence, function, etc.), you can always grant any privilege on it to others, regardless of WITH GRANT OPTION. The grant option only applies to non-owner roles that received the privilege from someone else.
What is the difference between 42501 and 0L000?42501 (insufficient_privilege) fires when you try to perform an operation (e.g., SELECT, INSERT, EXECUTE) without holding the necessary privilege. 0L000 fires when you try to grant or revoke a privilege and you lack the authority to do so — the error is about the GRANT/REVOKE statement itself, not the underlying object access.
How do I find out who granted a privilege so I can revoke it correctly?
Use the pg_class.relacl array or query information_schema.role_table_grants. The grantor column shows which role issued the grant. You must be that grantor, the object owner, or a superuser to revoke a specific grant.