Enabling gen_random_uuid() in Postgres: pgcrypto vs uuid-ossp vs Native

gen_random_uuid() is the function most Postgres developers reach for when they need a default UUID value on insert. What's less well understood is that the function has lived in three different places over the last several major versions of Postgres, each with different extension dependencies, different underlying entropy sources, and different implications for how you write migrations. This post walks through the mechanics of each, how to enable them, and how to choose correctly depending on the Postgres version you're targeting.

The Three Sources, at a Glance

Source Requires extension? Available since Function name(s)
uuid-ossp Yes — CREATE EXTENSION "uuid-ossp" Postgres 8.3+ (as contrib) uuid_generate_v1(), uuid_generate_v4(), etc.
pgcrypto Yes — CREATE EXTENSION pgcrypto Postgres 9.1+ (as contrib) gen_random_uuid()
Native (built-in) No extension required Postgres 13+ gen_random_uuid()

The confusing part: pgcrypto's gen_random_uuid() and the native Postgres 13+ gen_random_uuid() share the exact same name, which is why so many migration guides and Stack Overflow answers from before 2021 tell you to install pgcrypto just to get a function that, on PG13+, ships in core.

Mechanics: What Each Extension Actually Does

uuid-ossp

uuid-ossp wraps the OSSP UUID library (or a bundled implementation on some platforms) and exposes RFC 4122-compliant generators for multiple UUID versions — not just v4:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

SELECT uuid_generate_v1();   -- timestamp + MAC address based
SELECT uuid_generate_v1mc(); -- v1, but with a random multicast MAC to avoid leaking the real one
SELECT uuid_generate_v3(uuid_ns_dns(), 'example.com'); -- MD5 name-based
SELECT uuid_generate_v4();   -- random, RFC 4122 v4
SELECT uuid_generate_v5(uuid_ns_dns(), 'example.com'); -- SHA-1 name-based

The mechanical detail that matters: uuid_generate_v1() embeds the server's MAC address and a timestamp directly in the UUID. That's a real information-leakage concern if these UUIDs are ever exposed externally (e.g., as public order IDs), and it's part of why v1 usage has declined in favor of v4 or, more recently, v7.

pgcrypto

pgcrypto is a broader cryptographic extension — it also provides hashing (digest()), HMAC, and PGP encryption functions — but it's frequently installed solely for its gen_random_uuid():

CREATE EXTENSION IF NOT EXISTS pgcrypto;

SELECT gen_random_uuid();

Under the hood, pgcrypto's gen_random_uuid() draws randomness from the same cryptographically secure random source pgcrypto uses for its other functions (OpenSSL's RNG when compiled against it, or an internal fallback). It only ever produces v4 UUIDs — it doesn't expose the v1/v3/v5 generators that uuid-ossp does.

Native gen_random_uuid() (PG13+)

As of Postgres 13, gen_random_uuid() is a built-in function in core — no CREATE EXTENSION statement needed at all:

-- No extension required on PG13+
SELECT gen_random_uuid();

Mechanically, the native implementation pulls from Postgres's internal strong-random-source abstraction (pg_strong_random), which itself uses the OS CSPRNG (/dev/urandom on Linux, arc4random on BSD/macOS, BCryptGenRandom on Windows) — the same trusted source Postgres uses internally for things like SCRAM authentication nonces. Functionally it produces the same shape of output as pgcrypto's version (a v4 UUID), but with zero extension surface area: no shared library to load, no extension to track in your migrations, one less object with elevated install privileges.

Code: Setting This Up Correctly

Migration — Native (PG13+), Preferred

CREATE TABLE customers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

No preceding CREATE EXTENSION statement. This is the correct approach for any greenfield project on Postgres 13 or later — which, as of writing, is the vast majority of managed Postgres offerings (RDS, Cloud SQL, Supabase, Neon all default to 15+).

Migration — pgcrypto (Pre-PG13, or Managed Hosts Without Native Support)

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE customers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

Note the IF NOT EXISTS — on PG13+, this statement is often silently redundant since the function already exists natively, but it's harmless to run and keeps the migration portable if it ever runs against an older cluster.

Migration — uuid-ossp (Only If You Need Non-v4 Variants)

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE audit_events (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    -- name-based UUID derived deterministically from a business key
    dedup_key UUID GENERATED ALWAYS AS (uuid_generate_v5(uuid_ns_url(), source_url)) STORED,
    source_url TEXT NOT NULL,
    occurred_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

This is the one legitimate reason to still reach for uuid-ossp on a modern Postgres install: deterministic, name-based UUIDs (v3/v5). Neither pgcrypto nor native Postgres expose an equivalent — if you need "the same input always produces the same UUID" (useful for idempotency keys or content-addressed IDs), uuid-ossp is still the tool for it.

Application-Side Generation (C#)

Sometimes the right call is to generate the UUID in the application layer instead of the database, particularly with EF Core, so the ID is known before the INSERT executes:

public class Customer
{
    public Guid Id { get; set; } = Guid.NewGuid(); // client-side v4
    public string Email { get; set; } = default!;
}

public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder)
    {
        builder.Property(c => c.Id)
            .HasDefaultValueSql("gen_random_uuid()") // fallback if inserted outside EF
            .ValueGeneratedOnAdd();
    }
}

Guid.NewGuid() in .NET produces a v4 UUID using .NET's own CSPRNG — mechanically equivalent to Postgres's native gen_random_uuid(), just generated in-process rather than server-side. The tradeoff is standard client-vs-server ID generation: client-side avoids a round trip to learn the generated ID, but means the ID isn't guaranteed unique by the database (Postgres will still enforce it via the primary key constraint, it just isn't the one generating the value).

Node.js / JS Equivalent

const { randomUUID } = require('crypto');

const id = randomUUID(); // Node 14.17+, built-in, no dependency

Node's built-in crypto.randomUUID() (no uuid npm package required since Node 14.17) is mechanically the same story as Postgres 13's native function: what used to require a third-party dependency is now built into the runtime.

Comparison With Alternatives

Approach Extension needed UUID versions available Deterministic option Best fit
Native gen_random_uuid() (PG13+) No v4 only No Default choice on any PG13+ cluster
pgcrypto Yes v4 only No Pre-PG13 clusters, or when other pgcrypto features (hashing, PGP) are already in use
uuid-ossp Yes v1, v1mc, v3, v4, v5 Yes (v3/v5) Need deterministic/name-based UUIDs, or non-v4 variants
Client-generated (Guid.NewGuid(), crypto.randomUUID()) N/A v4 No Need the ID before the INSERT round-trip completes
uuidv7() (PG18+ native, or via extension on earlier versions) Depends on version v7 No, but time-ordered Insert-heavy tables where B-tree locality matters

Performance Notes

The performance difference between these three UUID sources is negligible for generation itself — all three ultimately draw from a CSPRNG and the function call overhead is microseconds. Where it does matter:

Factor Native (PG13+) pgcrypto uuid-ossp
Extension load overhead per session None Small, one-time per DB Small, one-time per DB
Attack surface / extra shared library None pgcrypto.so loaded uuid-ossp.so loaded
Superuser/install privilege needed for setup No Yes, to install initially (unless pre-installed by host) Yes, to install initially
Function call cost (relative) Baseline ~Same ~Same, slightly higher for v3/v5 due to hashing

The real-world decision driver isn't raw performance — it's operational surface area. Every CREATE EXTENSION is one more object your migrations, backups, and managed-host permissions have to account for. On PG13+, defaulting to the native function removes that surface area entirely for the common v4 case.

Decision Guide

  1. Running Postgres 13 or later and only need random (v4) UUIDs? Use the native gen_random_uuid(). No extension.
  2. Running Postgres 12 or earlier and can't upgrade soon? Use pgcrypto's gen_random_uuid() — it's the same function signature you'll fall back to natively once you do upgrade, so no code changes later.
  3. Need deterministic UUIDs from a business key, or need v1's timestamp-embedding behavior? Use uuid-ossp — it's still the only first-party option for those variants.
  4. Need the ID before the row is inserted (e.g., for use in a related object graph before SaveChanges()), or want to avoid a DB round-trip for ID generation? Generate client-side (Guid.NewGuid() / crypto.randomUUID()), and optionally keep gen_random_uuid() as the column default as a safety net for rows inserted outside the application.

Conclusion

If you're on Postgres 13 or newer — which is the default on essentially every managed provider today — there's no longer a reason to install pgcrypto or uuid-ossp just to call gen_random_uuid(). The native, extension-free version is mechanically equivalent for the standard v4 case and removes an unnecessary extension from your schema. The extensions still earn their place for specific needs: uuid-ossp for deterministic or non-v4 UUID variants, and pgcrypto for clusters that haven't reached PG13 yet or that already depend on its other cryptographic functions.


Tools and Versions

Need some UUIDs for testing? Try our tools for generating UUIDv4   UUIDv7   ULID   NanoID and decoding validating.
For a detailed comparison of UUID versions, visit our Complete Guide to UUID Versions.