PostgreSQL Schema Designer
Design a normalized PostgreSQL schema for your domain, including tables, relationships, indexes, constraints, and a migration file ready to run.
When to use
- Starting a new project and need to model the database
- Adding a new feature area with multiple new tables
- Want a sanity check on your schema design
Prompt
You are a senior database architect. Design a PostgreSQL schema for the
following domain.
## Input
**Domain description:**
{{domain_description}}
**Expected scale:**
{{scale}}
## Output
Produce four sections:
### 1. Entity Relationship Summary
A bulleted overview:
- List every entity (table)
- For each: 2-3 sentence description, expected row count, key relationships
### 2. Schema rationale
Explain key design choices:
- Normalization level (3NF unless you justify denormalizing)
- UUIDs vs serial IDs
- Soft delete vs hard delete
- Audit columns (created_at, updated_at, created_by)
- Any denormalization for read performance
- Where you considered partitioning
### 3. SQL DDL
A single SQL file with:
- All CREATE TABLE statements in dependency order
- All foreign keys with ON DELETE/UPDATE specified
- All indexes (with comments explaining why each exists)
- All CHECK constraints
- All UNIQUE constraints
- Triggers for `updated_at` automation
- ENUMs as PostgreSQL ENUM types or CHECK-constrained text
- Comments on tables and key columns
Use these conventions:
- Lowercase, snake_case identifiers
- Plural table names (`users`, not `user`)
- Foreign keys named `<referenced_table>_id`
- Index names: `idx_<table>_<columns>`
- Junction tables: `<a>_<b>` alphabetical
### 4. Migration file
Format the DDL as a numbered migration file: `migrations/0001_initial_schema.sql`
With matching `migrations/0001_initial_schema.down.sql` (drop in reverse order).
## Quality checklist
Before output, verify:
- [ ] No circular foreign keys
- [ ] Every table has a primary key
- [ ] Every foreign key has a matching index (Postgres doesn't auto-index FKs)
- [ ] Timestamps are TIMESTAMPTZ (timezone-aware), not TIMESTAMP
- [ ] Money columns use NUMERIC(precision, scale), not FLOAT
- [ ] Strings have explicit max lengths or use TEXT with CHECK
- [ ] Soft-delete columns (if any) have partial indexes excluding deleted rows
- [ ] Junction tables have composite primary keysExample input
domain_description: |
Multi-tenant SaaS for project management. Customers (organizations)
have users with roles (owner, admin, member). Each org has projects.
Projects have tasks. Tasks have assignees, due dates, and statuses.
Users can comment on tasks. Soft-delete projects and tasks.
scale: |
~500 orgs, ~10K users, ~100K projects, ~5M tasks. 80% read.Example output snippet
-- migrations/0001_initial_schema.sql
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE TYPE user_role AS ENUM ('owner', 'admin', 'member');
CREATE TYPE task_status AS ENUM ('todo', 'in_progress', 'blocked', 'done');
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL CHECK (length(name) BETWEEN 1 AND 200),
slug TEXT NOT NULL UNIQUE CHECK (slug ~ '^[a-z0-9-]+$'),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMENT ON TABLE organizations IS 'Top-level tenant in the multi-tenant model';
-- ...Tips
- For multi-tenant apps, decide row-level vs schema-level isolation up front and document it
- Always plan for soft-delete from day one — it's painful to retrofit
- Index foreign keys explicitly (Postgres doesn't do this automatically)
- Use
EXPLAIN ANALYZEon your top 5 expected queries before shipping