---
title: "Give every row a stable key · Agent Wranglers"
url: https://agentwranglers.se/playbook/data/schema-design/stable-keys
updated: 2026-10-20
licence: "All rights reserved"
---

# Give every row a stable key

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Schema design](https://agentwranglers.se/playbook/data/schema-design)
- Rule: Give every table a primary key that never changes. Never use a value people can edit, such as an email or a name, as the key.
- How: Use a generated `bigint` or a UUID as the primary key. A 32-bit `int` runs out at about 2.1 billion rows, and widening it later locks the table. Keep natural identifiers as separate `UNIQUE` columns, so a customer can change their email without breaking references. Prefer UUID version 7 (RFC 9562) over random version 4, because time-ordered keys keep indexes compact. PostgreSQL 18 has `uuidv7()` built in, and most languages have a library for older versions.
- Check: A catalog query finds no table without a primary key, and every generated key is `bigint` or a UUID. A search of the code finds no `UPDATE` that sets a key column.

Example:

```sql
-- PostgreSQL: tables without a primary key, expect no rows
SELECT c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r' AND n.nspname = 'public'
  AND NOT EXISTS (SELECT 1 FROM pg_constraint k WHERE k.conrelid = c.oid AND k.contype = 'p');
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers data playbook: https://agentwranglers.se/playbook/data/schema-design/stable-keys`.

- Previous: [Choose exact types](https://agentwranglers.se/playbook/data/schema-design/exact-types)
- Next: [Name things the same way everywhere](https://agentwranglers.se/playbook/data/schema-design/schema-names)
- [Schema design](https://agentwranglers.se/playbook/data/schema-design)
