Backend · Skill
Design PostgreSQL tables
design-postgres-tables
Designs or reviews plain PostgreSQL tables - column types, primary and foreign keys, NOT NULL, CHECK and UNIQUE constraints, the indexes your queries need, JSONB where it fits, partitioning and safe live changes. Use when the user is creating Postgres tables, choosing types or keys, reviewing a schema before launch, or adding a column to a busy table.
Works with: Any AI Not yet model-tested; testing Jan 2027
Paste it into ChatGPT, Claude, Gemini or any AI chat, then tell it about your project. How it works
Install in Claude Code, Cursor or Codex (optional)
Only for AI coding tools that read skill folders. Run one line in a terminal, in your project folder. It downloads this skill's text files from TheSkillVault and nothing else; you can read them first with “View raw SKILL.md”.
Claude Code (all your projects)
curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/design-postgres-tables/raw' -o ~/'.claude/skills/design-postgres-tables/SKILL.md' && curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/design-postgres-tables/files/LICENSE.txt' -o ~/'.claude/skills/design-postgres-tables/LICENSE.txt'Cursor (this project)
curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/design-postgres-tables/raw' -o '.cursor/skills/design-postgres-tables/SKILL.md' && curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/design-postgres-tables/files/LICENSE.txt' -o '.cursor/skills/design-postgres-tables/LICENSE.txt'Codex and other Agent Skills tools (this project)
curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/design-postgres-tables/raw' -o '.agents/skills/design-postgres-tables/SKILL.md' && curl -fsSL --create-dirs 'https://theskillvault.ai/api/skills/design-postgres-tables/files/LICENSE.txt' -o '.agents/skills/design-postgres-tables/LICENSE.txt'
Or connect the whole library over MCP
Add TheSkillVault to Cursor or add this to your app's MCP settings (read-only; help):
{
"mcpServers": {
"theskillvault": {
"url": "https://theskillvault.ai/mcp"
}
}
}Good tables make bugs hard to write. Let the database reject bad data with types and constraints, then index the queries you actually run.
When to use
- You are creating the first tables for an app on any Postgres host.
- You are unsure which column type, key or constraint to use.
- You want a schema reviewed before real users arrive.
- You need to add or change a column on a table that is already busy.
When not to use
- Your database is Supabase and you need row level security. Use design-supabase-postgres-schema.
- A query is already slow. Use optimise-slow-sql-queries to read the plan first.
Steps
- List the things you store and how they relate: a user has many orders, an order has many items.
- Give each table a key:
id bigint generated always as identity primary key. Useuuidonly when ids must be unguessable or made outside the database. - Normalise first: one fact in one place. Copy data for speed only after you measure a slow join.
- Pick types from the table below, and add
not nullwherever a value is required. - Add foreign keys with an explicit
on deleterule, then index each foreign key column yourself. - Add
checkanduniqueconstraints for business rules, such as a positive price or one email per user. - Index for real queries: filters, sorts and joins you run often. Skip guesses.
- Use
jsonbonly for optional or changing attributes, and pull hot fields into real columns. - Partition only very large tables (around 100 million rows or more) that every query filters by one key, such as a date.
- Ship schema changes as small migrations, and test them on a copy of production data first.
| Store | Use | Avoid | | --- | --- | --- | | Text | `text`, plus `check (length(x) <= n)` if needed | `varchar(n)`, `char(n)` | | Whole numbers | `bigint` (or `integer` for small counts) | `serial` | | Money, exact decimals | `numeric(12,2)` | `money`, `float` | | Times | `timestamptz` | `timestamp` without zone | | Yes or no | `boolean not null` | text flags | | Fixed short list | `text` + `check (x in (...))` | enums for lists that will grow | | Flexible extras | `jsonb` + a GIN index | `json` |
create table customers (
id bigint generated always as identity primary key,
email text not null,
created_at timestamptz not null default now()
);
create unique index customers_email_lower on customers (lower(email));
create table invoices (
id bigint generated always as identity primary key,
customer_id bigint not null references customers(id) on delete restrict,
status text not null default 'draft' check (status in ('draft','sent','paid','void')),
total numeric(12,2) not null check (total >= 0),
created_at timestamptz not null default now()
);
create index invoices_customer_id on invoices (customer_id);
create index invoices_open on invoices (created_at) where status <> 'paid';
Live changes without downtime
- Add new columns as nullable or with a constant default, then backfill in batches.
- Build indexes with
create index concurrentlyso writes keep flowing. - Add constraints as
not valid, then runvalidate constraintin a later step. - Rename or drop only after every running version of the app has stopped using the old column.
Gotchas
- Postgres does not index foreign keys for you. Missing ones make deletes and joins slow.
uniqueallows many nulls. Useunique nulls not distinct(Postgres 15+) when you want one.- Unquoted names are lowercased. Stick to
snake_caseand never quote identifiers. - Identity and sequence values have gaps after rollbacks. That is normal, so do not "fix" it.
- Partitioned tables cannot have a unique key that leaves out the partition key.
- Take a backup before any destructive migration, and keep connection strings and passwords out of migration files.
Portability
Works with any Postgres host and any tool that runs SQL migrations: Prisma, Drizzle, Rails, Django, Flyway or plain psql. Most rules also apply to other SQL databases if you swap the type names for their equivalent.
Example prompt
"Design Postgres tables for a small booking app with customers, rooms and bookings. Stop double bookings and tell me which indexes I need."
Done when
- Every table has a primary key, required columns are
not nulland every foreign key is indexed. - Business rules are enforced by constraints, not only by app code.
- Each index maps to a query you run, and migrations ran cleanly on a copy of real data.
---
Adapted from "postgresql-table-design" in wshobson/agents (MIT, Copyright (c) 2024 Seth Hobson). See LICENSE.txt.
Bundled files
Licence
Adapted from cevian (contributed in wshobson/agents PR #113, postgresql-table-design); repo by Seth Hobson (Copyright (c) 2024 Seth Hobson), MIT License. Adapted by TheSkillVault on 2026-11-02: rewritten as a design order with an original example schema; vendor-specific extension and hosting mentions (TimescaleDB and hypertables) removed; the long type catalogue was cut to one table; added safe live-change steps, gotchas and done-when. Not the original file.
Licence: MIT License. Attribution when you reuse it: Based on work by cevian (contributed in wshobson/agents PR #113, postgresql-table-design); repo by Seth Hobson (Copyright (c) 2024 Seth Hobson, MIT License), https://github.com/wshobson/agents/tree/7e2fb559cf4b824b27f1fec49fe88f7511066d26/plugins/database-design/skills/postgresql-table-design; adapted by TheSkillVault, https://theskillvault.ai