The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Create a schemaCREATE SCHEMA app AUTHORIZATION app_owner;View examples
Set role search pathALTER ROLE app_runtime SET search_path = app, pg_catalog;View examples
Create a login roleCREATE ROLE app_runtime LOGIN PASSWORD NULL;View examples
Create a group roleCREATE ROLE app_readonly NOLOGIN;View examples
Grant role membershipGRANT app_readonly TO analyst_user;View examples
Allow schema lookupGRANT USAGE ON SCHEMA app TO app_readonly;View examples
Grant existing table readsGRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;View examples
Grant sequence useGRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA app TO app_runtime;View examples
Revoke public schema creationREVOKE CREATE ON SCHEMA public FROM PUBLIC;View examples
Grant future table readsALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app GRANT SELECT ON TABLES TO app_readonly;View examples
Test a privilegeSELECT has_table_privilege('app_runtime', 'app.orders', 'SELECT');View examples
Transfer ownershipALTER TABLE app.orders OWNER TO app_owner;View examples

PostgreSQL roles can own objects, inherit memberships, and optionally log in; schemas are namespaces whose CREATE privilege affects object-resolution security. Grant the smallest object privileges needed, separate ownership from runtime login, qualify security-sensitive names, and configure default privileges from the role that will create future objects.

Step by step

Detailed examples

01

Treat writable schemas in search_path as code execution risk

Unqualified names resolve through search_path, and the first valid match wins. A role with CREATE in a searched schema can shadow functions or objects. Revoke unnecessary CREATE, put pg_catalog deliberately, and schema-qualify migration and security-definer code.

Private application namespace
CREATE SCHEMA app AUTHORIZATION app_owner;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
ALTER ROLE app_runtime SET search_path = app, pg_catalog;
Back to quick reference ↑
02

Separate login, membership, and ownership roles

Every database identity is a role; LOGIN permits connection and NOLOGIN roles aggregate privileges or own objects. Membership can inherit privileges or require SET ROLE depending on role options and version. Do not make application logins object owners or superusers.

Three-role application model
CREATE ROLE app_owner NOLOGIN;
CREATE ROLE app_readonly NOLOGIN;
CREATE ROLE app_runtime LOGIN PASSWORD NULL;
GRANT app_readonly TO analyst_user;
Back to quick reference ↑
03

Grant namespace and object privileges separately

USAGE on a schema permits name lookup but not table access. Table grants do not automatically include sequences used by inserts, and ALL TABLES affects only existing objects. PUBLIC is an implicit group containing every role, so review its database and schema grants.

Existing read-only surface
GRANT USAGE ON SCHEMA app TO app_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readonly;
REVOKE INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app FROM app_readonly;
Back to quick reference ↑
04

Configure defaults for the actual creator role

ALTER DEFAULT PRIVILEGES affects future objects created by a specified role, optionally in a schema; it does not rewrite existing objects. Running it as an administrator instead of the migration owner is a common mistake. Apply existing grants and defaults as separate migration steps.

Future tables and sequences
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT SELECT ON TABLES TO app_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
  GRANT USAGE, SELECT ON SEQUENCES TO app_runtime;
Back to quick reference ↑
05

Audit effective rights and ownership before revocation

Owners have inherent capabilities not represented as ordinary grants, superusers bypass privilege checks, and membership can provide indirect rights. Use has_*_privilege functions and catalog views under representative roles. Transfer ownership deliberately before dropping an owner role.

Check effective access and owners
SELECT has_schema_privilege('app_runtime', 'app', 'USAGE'),
       has_table_privilege('app_runtime', 'app.orders', 'SELECT');
SELECT schemaname, tablename, tableowner
FROM pg_tables WHERE schemaname = 'app'
ORDER BY tablename;
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. PostgreSQL Global Development GroupPostgreSQL: Database Rolespostgresql.org
  2. PostgreSQL Global Development GroupPostgreSQL: Schemaspostgresql.org
  3. PostgreSQL Global Development GroupPostgreSQL: GRANTpostgresql.org
  4. PostgreSQL Global Development GroupPostgreSQL: ALTER DEFAULT PRIVILEGESpostgresql.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback