ALTER TYPE ADD VALUE requires the type owner
Adding a value to a PostgreSQL enum requires owning the type itself, and a pg_class ownership-transfer loop won't reassign enums because standalone types live in pg_type.
Adding a value to a PostgreSQL enum requires owning the type itself: not the schema, not the tables that use it. I hit this when a migration like ALTER TYPE "app"."destination_kind" ADD VALUE 'option' failed in CI:
error: must be owner of type app.destination_kind
This surfaced in a bootstrap script I run during the deploy stage for an MVP project, outside Drizzle: it provisions the database, roles, and ownership before the migration role ever connects.
The role running it owned every table in the schema. That still wasn’t enough.
Usage
The Postgres docs for ALTER TYPE are explicit: the type owner is the only role allowed to add enum values. I checked who actually owned it:
SELECT t.typname, rolname AS owner
FROM pg_type t
JOIN pg_roles r ON r.oid = t.typowner
JOIN pg_namespace n ON n.oid = t.typnamespace
WHERE n.nspname = 'app' AND t.typtype = 'e';
The deploy script I’d written was reassigning every table and sequence to the migration role, but the enums still belonged to the bootstrap role:
-- only covers relations: ordinary/partitioned tables, sequences
FOR r IN
SELECT c.relname, c.relkind
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'app' AND c.relkind IN ('r', 'p', 'S')
LOOP
EXECUTE format('ALTER TABLE app.%I OWNER TO %I', r.relname, 'app_role');
END LOOP;
Enums are not relations. Standalone types (enums e and domains d) live in pg_type, so pg_class loops never touch them. I added a second loop over pg_type:
FOR t IN
SELECT t.typname
FROM pg_type t
JOIN pg_namespace n ON n.oid = t.typnamespace
WHERE n.nspname = 'app' AND t.typtype IN ('e', 'd') AND t.typrelid = 0
LOOP
EXECUTE format('ALTER TYPE app.%I OWNER TO %I', t.typname, 'app_role');
END LOOP;
typrelid = 0 excludes composite types tied to a table row, which already follow the table’s ownership through ALTER TABLE ... OWNER TO.
Notes
The same ownership rule covers every ALTER TYPE action, not just ADD VALUE. Any database bootstrapped as a superuser and then handed to an application role needs its types reassigned too, not only its tables.
References
ALTER TYPE. Which actions require type ownershippg_type. Where standalone types live, and the meaning oftyptypeandtyprelidpg_class. The relation kindsrelkindcovers, and why enums are absent
This post was written with AI assistance.