Generate UUIDs in PostgreSQL: gen_random_uuid and uuid-ossp
PostgreSQL has had two UUID generation paths for years: the historic uuid-ossp extension and the modern gen_random_uuid built into pgcrypto. Pick the new one and never look back.
gen_random_uuid is the answer in PostgreSQL 13+
Since PostgreSQL 13, gen_random_uuid() is available without enabling any extension. It generates a UUIDv4 (random) compliant with RFC 9562 and is exactly what you want for primary keys in 99% of cases.
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
amount NUMERIC NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
If you are on PostgreSQL 12 or earlier, you have two options: upgrade, or enable pgcrypto with CREATE EXTENSION IF NOT EXISTS pgcrypto; to get the same function. Both work the same way.
uuid-ossp is legacy. Stop using it.
uuid-ossp is the historic extension that gave you uuid_generate_v4(), uuid_generate_v1() and friends. It is not deprecated officially but the maintainers themselves recommend gen_random_uuid() for new projects. The internal RNG of pgcrypto is better, and the dependency footprint is smaller.
If you have an existing schema using uuid_generate_v4(), leave it alone. Migrating columns is dangerous and the function works fine. New tables: use gen_random_uuid().
UUIDv7 if you care about index locality
Random UUIDs are awful for B-tree indexes because every insert lands at a random page. Modern Postgres setups with high insert volume use UUIDv7, which prefixes a millisecond timestamp and keeps inserts append-only.
PostgreSQL does not yet ship a built-in UUIDv7 generator (as of v17). You either install an extension like pg_uuidv7, or generate UUIDv7 in your application and INSERT them as regular UUID values. The Postgres community is tracking native support but it has not landed yet.
Performance and storage notes
UUID columns are 16 bytes on disk, exactly twice a BIGINT. The performance hit on a single-table SELECT is invisible. The hit on a JOIN-heavy schema with hundreds of millions of rows is real but small. The hit on cache locality with random UUIDv4 is bigger than either.
If you are joining 4 tables on UUIDs and you measured a regression, UUIDv7 fixes it. If you have not measured anything, stay on gen_random_uuid() and ship.
Working example
sql-- Single one-off
SELECT gen_random_uuid();
-- → 1a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d
-- Default for primary key
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
amount NUMERIC NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Bulk: 1000 fresh UUIDs
SELECT gen_random_uuid()
FROM generate_series(1, 1000);
-- If pre-13: enable pgcrypto first
CREATE EXTENSION IF NOT EXISTS pgcrypto; Just need the result?
When you need a UUID outside Postgres, like seeding a migration or pasting into a YAML config, the browser-based UUID generator gives you the same RFC 9562-compliant value with the Web Crypto API and zero round-trip to a server.
Open UUID v4 Generator →Frequently asked questions
Do I need to enable any extension for gen_random_uuid in PostgreSQL 13+?
No. Since 13 it is in the core. On 12 and earlier you need pgcrypto. uuid-ossp is a separate, older extension and you do not need it for v4.
How many UUIDs can I generate before a collision is realistic?
UUIDv4 has 122 bits of entropy. You would need to generate around 2.71 quintillion UUIDs to hit a 50% collision probability. For any real workload, treat collisions as impossible.
Can I use UUIDs as the primary key on every table without thinking?
Almost. The exception is huge time-series tables with constant inserts where UUIDv4 destroys index locality. There, switch to UUIDv7 or stay with bigserial. For 99% of tables UUIDv4 is fine.