55 Roles and GRANT
PostgreSQL has a single concept — the role — for both users and groups. A role can log in (with LOGIN), be granted into other roles (for group-like behavior), own objects, and have privileges granted to it. GRANT adds privileges; REVOKE removes them. Every database object has an owner with full rights; everyone else needs explicit grants. The principle is least privilege: give each role only what it needs.
What you’ll learn
- Creating roles with and without login
- Granting and revoking privileges on schemas, tables, and columns
- The
USAGEprivilege on schemas (often forgotten) DEFAULT PRIVILEGESfor objects created in the future- Row-level security (RLS) — a brief introduction
Example
Table privileges include SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, and, in PostgreSQL 17, MAINTAIN. On a schema, the most important are USAGE (lets a role refer to objects inside) and CREATE (lets the role create new objects). On a database, CONNECT, CREATE, and TEMPORARY matter.
Privileges on tables are not inherited from schema-level grants. To use a table in schema public, a role needs USAGE on public and a privilege (e.g. SELECT) on the table. Missing the schema-level USAGE produces the common “permission denied for schema public” error.
DEFAULT PRIVILEGES solves the “new tables get no grants” problem: any object created later by a role inherits the configured grants automatically. They apply to objects created by the configured creating role, not automatically to objects created by every role. Configure each migration/owner role that creates tables.
Row-level security (RLS) is a more advanced feature: you ENABLE ROW LEVEL SECURITY on a table and write POLICYs that filter rows by predicate. With RLS, a query like SELECT * FROM tenant_docs returns only the rows visible to the current role/setting when the role is subject to the policy. A freely changeable app.tenant setting is only a demonstration: a real application must prevent untrusted clients from choosing another tenant.
Run the example
To run, use a non-superuser role without BYPASSRLS that can create tables in your scratch database. FORCE ROW LEVEL SECURITY makes this table owner obey the policy. Superusers still see both tenants. The commented role-management commands need appropriate role-administration privileges.
Common pitfalls
- Forgetting
GRANT USAGE ON SCHEMA public. A role can haveSELECTon every table and still fail to query because it lacks schema-level usage. - Granting privileges on existing tables without configuring
DEFAULT PRIVILEGES— every new table the app creates is unreachable by the read-only role. - Mixing
SECURITY DEFINERfunctions with a permissivesearch_pathopens a privilege-escalation door. Always setsearch_pathon such functions. - Storing passwords in
CREATE ROLE ... PASSWORD 'plain'is acceptable for personal databases; for production use SCRAM authentication and never hardcode passwords in SQL scripts.
A common pattern is three roles: app_owner (owns objects, used for migrations), app_read (read-only role for analytics and replicas), app_write (read+write role for the application). Grant app_owner ownership; grant rights to the others.
With superuser access, create a role and grant it SELECT only on fruits. Reconnect as that role (psql -U newrole) and confirm reads work but writes do not. Then experiment with RLS: enable on a table, add a policy, and watch how SELECT results change with SET.
Downloads
SQL file: roles-and-grant.sql · All lesson files · Setup guide