Purpose
Schemas that are obvious to read and cheap to change.
The standard
snake_case; plural table names (panels), singular column names; primary keyid uuid default gen_random_uuid().Every table has
created_at timestamptz default now(); mutable tables addupdated_at(trigger-maintained) andupdated_by.Foreign keys always declared, named
<table>_id, with expliciton deletebehavior.Units in column names (
width_mm,weight_kg,angle_deg); store SI, convert at the edge.Enums as Postgres
enumtypes ortext+checkconstraint; prefercheckwhen values will change.jsonbfor document-shaped data (editor content, tool settings); never for data you'll filter or join on.Indexes on every FK and every column used in
where/order by; no premature composite indexes.Soft delete (
deleted_at) only where restore is a real requirement; otherwise hard delete withon delete cascadethought through.Comment every table (
comment on table … is '…') — it shows up in the dashboard and in generated types.
Anti-patterns
Storing arrays of ids instead of join tables; varchar(255) by habit (use text); nullable everything.
Owner: Matt · Last reviewed: 2026-09