Roles & Permissions
PostgreSQL uses a single, unified concept — the "role" — for what many other systems split into two separate ideas: users and groups. A role can log in like a user, act as a group that other roles belong to, or both at once.
CREATE ROLE vs CREATE USER
CREATE USER is simply CREATE ROLE with the LOGIN attribute already turned on — they create the same kind of object.
create-role-vs-user.sql
-- These two are functionally almost identical: CREATE ROLE app_user WITH LOGIN PASSWORD 'change-me'; CREATE USER app_user WITH PASSWORD 'change-me'; -- LOGIN is implied -- A role WITHOUT login is typically used as a permission group, -- never connected to directly: CREATE ROLE reporting_group NOLOGIN;
GRANT and REVOKE
Privileges on specific database objects — tables, schemas, sequences, functions — are given out with GRANT and taken away with REVOKE.
grant-revoke.sql
GRANT SELECT ON orders TO reporting_group; GRANT SELECT, INSERT, UPDATE ON orders TO app_user; REVOKE INSERT, UPDATE ON orders FROM app_user;
Role Membership and Inheritance
Roles can be members of other roles, inheriting their privileges. This is how you bundle permissions into reusable groups instead of granting the same set of privileges to every individual user one-by-one.
role-membership.sql
CREATE ROLE alice WITH LOGIN PASSWORD 'change-me'; GRANT reporting_group TO alice; -- alice now inherits every privilege granted to reporting_group
Worked Example: A Read-Only Reporting Role and a Scoped App Role
least-privilege-roles.sql
-- A role for BI/reporting tools: read-only, nothing else. CREATE ROLE reporting_group NOLOGIN; GRANT CONNECT ON DATABASE storefront TO reporting_group; GRANT USAGE ON SCHEMA public TO reporting_group; GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting_group; -- Make sure FUTURE tables are covered too, not just existing ones. ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO reporting_group; CREATE ROLE bi_tool WITH LOGIN PASSWORD 'change-me'; GRANT reporting_group TO bi_tool; -- An application role: only the privileges the app actually needs, -- nothing that lets it alter schema or touch tables it doesn't use. CREATE ROLE storefront_app WITH LOGIN PASSWORD 'change-me'; GRANT CONNECT ON DATABASE storefront TO storefront_app; GRANT USAGE ON SCHEMA public TO storefront_app; GRANT SELECT, INSERT, UPDATE, DELETE ON orders, order_items, customers TO storefront_app; -- Deliberately no privileges on, say, an internal audit_log table.
\duinpsqllists all roles and their attributes.REVOKE ALL ... FROM PUBLICis worth knowing — by default some privileges are granted to the implicitPUBLICpseudo-role, which every role is a member of.Role passwords set inline in SQL as shown above are for illustration — manage real credentials through your organization's secrets tooling.