Default Privileges
ALTER DEFAULT PRIVILEGES attaches grant templates to future objects created by a specific role in a schema. Without it, every migration must remember manual GRANT statements.
Search across all documentation pages
ALTER DEFAULT PRIVILEGES attaches grant templates to future objects created by a specific role in a schema. Without it, every migration must remember manual GRANT statements.
-- Run as migration owner (creates tables in deploy)
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT ON TABLES TO app_readers;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_writers;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT USAGE, SELECT ON SEQUENCES TO app_writers;When to reach for this: Bootstrap of a new schema, onboarding a new service role, or fixing recurring "forgot to GRANT" deploy failures.
CREATE ROLE app_owner NOLOGIN;
CREATE ROLE app_migrator LOGIN PASSWORD 'vault';
GRANT app_owner TO app_migrator;
CREATE SCHEMA app AUTHORIZATION app_owner;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
REVOKE ALL ON TABLES FROM PUBLIC;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT ON TABLES TO app_readers;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_writers;
-- Migration creates new table - grants apply automatically
SET ROLE app_owner;
CREATE TABLE app.orders (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY);
RESET ROLE;
-- Verify
SELECT grantee, privilege_type
FROM information_schema.table_privileges
WHERE table_schema = 'app' AND table_name = 'orders';What this demonstrates:
FOR ROLE app_owner matches the creating role - critical detailREVOKE ALL FROM PUBLIC on defaults closes public access for new tablesorders table already has app_readers/app_writers grants-- Wrong: defaults for current user only
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_ro;
-- Right: defaults when app_owner creates objects
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA app
GRANT SELECT ON TABLES TO app_ro;SET ROLE app_owner or connect as app_owner via SET SESSION AUTHORIZATION (superuser only).SELECT * FROM pg_default_acl;| Command | Applies to |
|---|---|
ON TABLES | Tables and views |
ON SEQUENCES | Serial/identity backing sequences |
ON FUNCTIONS | Functions and procedures |
ON TYPES | Domains and types |
-- Existing objects before defaults were set
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_readers;ALTER DEFAULT PRIVILEGES is not retroactive.ALL TABLES + ALTER DEFAULT PRIVILEGES.app_migrator but defaults set for app_owner. Fix: Align creator role or add defaults for both.INSERT fails on nextval. Fix: Default privileges on SEQUENCES too.app_owner do not apply to superuser-created tables. Fix: Never create app tables as superuser.| Alternative | Use When | Don't Use When |
|---|---|---|
| Grant in each migration file | Explicit per-table policies | Easy to forget sequences |
| Event trigger on DDL | Centralized audit/grant | Team lacks trigger maintenance skill |
| Owner = app_api (anti-pattern) | Never recommended | You want least privilege |
Use defaults for routine tables; keep explicit GRANT for exceptions (column-level, sensitive tables).
Repeat ALTER DEFAULT PRIVILEGES per schema or use a bootstrap loop in SQL.
Covered by ON TABLES. Refresh rights on matviews need separate consideration for refresh role.
ALTER DEFAULT PRIVILEGES ... REVOKE ... mirrors grant syntax; inspect pg_default_acl before changes.
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.
Reviewed by Chris St. John·Last updated Jul 18, 2026