PostgreSQLUUID Type

UUID Type

A UUID (Universally Unique Identifier) is a 128-bit value, usually written as 32 hexadecimal digits split into five groups — for example a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11. PostgreSQL has a native UUID type that stores this efficiently as raw 16 bytes rather than as text, and it's a common choice for primary keys in systems where uniqueness has to hold across more than one database or service.

Generating UUIDs

Since PostgreSQL 13, the built-in gen_random_uuid() function generates a random (version 4) UUID with no extension required — this is the simplest option for new projects.

gen_random_uuid() — built in since PostgreSQL 13

SQL
CREATE TABLE sessions (
    session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id    INTEGER NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

INSERT INTO sessions (user_id) VALUES (42) RETURNING session_id;
session_id
--------------------------------------
f47ac10b-58cc-4372-a567-0e02b2c3d479

On older PostgreSQL versions (before 13), the equivalent functionality came from the uuid-ossp extension's uuid_generate_v4() function, which has to be enabled first with CREATE EXTENSION. You'll still see this in older codebases — the Extensions page covers enabling and using extensions like this in more depth.

The older extension-based approach

SQL
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE legacy_sessions (
    session_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    user_id    INTEGER NOT NULL
);
UUID vs SERIAL as a primary key

Both are legitimate primary key strategies, and the right choice depends on your system's shape. This is a genuine trade-off, not a case where one option is simply better.

UUID

SERIAL / BIGSERIAL (integer)

Uniqueness

Globally unique without any coordination — safe to generate on multiple servers, offline, or client-side

Unique only within one sequence — merging rows generated on different systems risks collisions

Storage size

16 bytes per value

4 bytes (INTEGER) or 8 bytes (BIGINT)

Index size & performance

Larger indexes; random values cause more index page fragmentation

Smaller indexes; sequential values insert efficiently at the end of the index

Sortability

A plain v4 UUID is random — sorting by it tells you nothing about insertion order

Naturally sortable by insertion order

Guessability

Not sequentially guessable — can't easily enumerate /orders/1, /orders/2...

Sequential values are easy to guess/enumerate if exposed publicly

Note
UUIDs shine in distributed systems: multiple services, offline clients, or data imports from different sources can all generate IDs independently with essentially zero chance of collision — no central sequence to coordinate. Integer sequences shine when everything is generated by one database and you want compact, naturally-ordered, index-friendly keys. Some teams split the difference by using a `BIGSERIAL` internally for join performance and exposing a separate public-facing UUID (or similar) for external identifiers.
  • UUID is a native 128-bit type, stored as 16 bytes.

  • gen_random_uuid() (built in since PostgreSQL 13) is the simplest way to generate one; uuid_generate_v4() from the uuid-ossp extension is the older equivalent.

  • UUIDs need no coordination across systems, but are larger and less naturally sortable than sequential integers.

  • Choose UUIDs for distributed generation and unguessable identifiers; choose sequential integers for compact, naturally-ordered keys within a single database.