Not every identity is an auto-incrementing integer, and not every constraint has to be copy-pasted onto every table. This lesson covers three tools for shaping your own types: uuid keys you can generate anywhere, enum types for a fixed set of labels, and domain types that bundle a base type with a rule you write once. The seed is a small SaaS accounts table that uses all three.
Take a look at how it's shaped:
SELECT column_name, data_type, udt_name
FROM information_schema.columns
WHERE table_name = 'accounts'
ORDER BY ordinal_position;
Notice id reports uuid, and the domain columns show their base type in data_type with the domain name in udt_name — a hint that a domain is a base type wearing a label.
uuid keys with gen_random_uuid()
A uuid is a 128-bit identifier, printed as 32 hex digits in five dash-separated groups. Postgres 13+ ships gen_random_uuid() in core — no extension needed — so you can hand a table a random primary key by default:
SELECT gen_random_uuid() AS a, gen_random_uuid() AS b;
Two calls, two different values, effectively never colliding. The seed's accounts.id is defined as id uuid PRIMARY KEY DEFAULT gen_random_uuid(), so an insert that omits id gets one for free:
INSERT INTO accounts (email, status, seats)
VALUES ('margaret@example.com', 'trial', 3)
RETURNING id, email;
RETURNING id hands the generated key straight back — the same pattern as an identity column, but the value came from a function you could also have called in your application before inserting.
Why (and why not) a UUID key
That last point is the headline: a UUID is generatable anywhere. Your app, a mobile client, or three different services can each mint a key without a round-trip to the database and without coordinating — no shared sequence, no cross-shard collisions when you later split the table across servers. UUIDs are also (an id means the same thing across every table and system) and , so exposing one in a URL doesn't leak "how many accounts exist" the way does.