The Craftsman standard for database work — schema design, migrations, query writing, indexing, and multi-tenant data scoping. Use this WHENEVER the work touches the database in any form: designing or changing schema, writing migrations, modeling tables or relations, writing or optimizing queries, adding indexes, handling soft-deletes, scoping data to a tenant, or wiring up connection pooling. Trigger even when the user only says "add a column", "this query is slow", "design the data model", or "write a migration" without naming an ORM or dialect.
This skill encodes one engineer's standard for database work, applied the same way across every
repo. The method and opinions live here; the specifics (which ORM, which dialect, which
migration tool, what the tenant-scope helper is called) live in the target repo — always discover
them first, never assume or hardcode.
Operating principle — discover before you build
Different repos are at different points in their DB evolution. Before touching anything, spend a few
minutes mapping what already exists so you extend rather than duplicate or fight the grain:
package.json / lockfile → is drizzle-orm, prisma, knex, pg, mysql2, or similar
already present?
Existing migration directory (e.g. drizzle/, prisma/migrations/, db/migrations/) → what
conventions are already established (naming, timestamps, reversibility)?
Schema files → column naming style (camelCase vs snake_case), timestamp columns (createdAt /
created_at), soft-delete column name and type, how tenant id is stored and named.
Query helpers → is there a shared conditions/helpers file that enforces tenant scoping and
soft-delete filters? Read it before writing any query.
Dialect → Postgres, MySQL, SQLite? Some patterns (advisory locks, RETURNING, expression indexes)
are dialect-specific.
State what you found, then propose the smallest set of changes that achieves the goal.
The data layers (work in this order)
Schema design — model the domain correctly before writing a line of migration code. Choose
the right types, enforce NOT NULL where the domain demands it, express relationships as real
foreign keys, and name columns consistently with the existing schema. See references/schema.md.
Migrations — translate the schema design into a forward-only migration, reviewed before it
ever runs. Prefer reversible migrations where the cost is low (drop → add back); know when
forward-only is the honest choice. See references/migrations.md.
Access patterns — every query that touches a tenant table must go through a shared helper
that enforces the tenant id (always, when multi-tenant) and the soft-delete filter when
the project uses soft-delete. Write queries against real access patterns, not "I might need
this later". See references/access-patterns.md.
Indexing — add indexes for the query patterns you can see in the code, measured with
EXPLAIN/EXPLAIN ANALYZE. An index you haven't verified helps is a write-amplification tax.
See references/indexing.md.
Integrity & safety — prefer DB-level constraints and foreign keys over app-level validation;
wrap multi-step writes in transactions; plan large backfills as separate steps, not inside a
schema migration. See references/integrity.md.
Standing opinions (the non-negotiables)
These judgments keep output consistent — apply them unless the user overrides:
Every tenant-scoped query goes through a shared helper. That helper enforces the tenant id
always (when multi-tenant), and the soft-delete filter when soft-delete is used (see
schema.md "When soft-delete is used"). Querying a tenant table raw — even "just this once" —
is the pattern that ships data leaks. Don't do it.
Migrations are generated and reviewed, never auto-pushed to production. The migration tool's
"push" shortcut skips the review step and, with some versions, produced non-convergent diffs (this
was a confirmed drizzle-kit pre-0.20 behavior; verify the behavior of the version in use before
relying on push even in dev). Generate a migration file, read it, then apply it.
DB constraints beat app-level checks. A unique constraint enforced by the database holds even
when a second process, a migration script, or a future developer bypasses the application layer.
Write the constraint first, then add app-level validation for UX.
Index for measured patterns, not guesses. Run EXPLAIN ANALYZE on the slow query, confirm the
index would be used, then add it. Speculative indexes cost write performance on every insert and
update.
Destructive or large-table migrations are split into stages. Add the column nullable → backfill
in batches → add the constraint → drop the old column. Doing all four steps in one migration risks
long locks and an unrecoverable failure mid-way.
Workflow
Discover the repo's ORM, dialect, migration tool, schema conventions, and tenant-scope
helpers. Report what you found and what's missing.
Propose the schema change or query approach, explaining why the types/constraints/indexes
make sense for the domain.
Implement — generate the migration following the repo's tooling (e.g. pnpm db:generate),
name and format it consistently with existing migrations, write queries through the repo's
shared helpers.
Verify — run the migration up, spot-check the resulting schema, and run EXPLAIN on any
query that will hit a large table or run in a hot path. Migrations you haven't seen applied
aren't done.
Dialect note
These docs assume PostgreSQL. If your project uses SQLite or MySQL, check driver-specific notes in each section — locking semantics, JSON column support, and some index/constraint behaviors differ.
Reference index
Read the one matching the current task — they hold the concrete patterns, not this overview:
references/schema.md — column types, naming conventions, timestamp and soft-delete patterns
When craft-audit plans a db pass for a scope, it turns this checklist into the plan.md
todo list — the checklist is owned by this skill, not improvised by the orchestrator. Tailor to what
discovery found: skip a step that genuinely doesn't apply with a one-line reason; never silently drop
one. Emit findings using craft-audit workspace.md → "Canonical findings.md emission format"
(authority). Heading grammar (variables required — do not hardcode NNN/severity/status):
## <scopeLabel>-DB-<NNN> · severity <🔴|🟡|🟢> · status <open|fixed|wontfix (reason)|regressed|fixed (merged into <ID>)>
Example only: ## <scopeLabel>-DB-001 · severity 🔴 · status open
Required fields under each heading, in order, with these exact labels:
**What breaks (plain language):** · **Technical:** · **Fix:** · **Fingerprint:** ·
**Last-checked:** (optional **Confidence:** — verified | inferred | unverified-from-repo, absent
means verified — then optional **Fix-attempt:** only from craft-fix).
Assign sequential NNN per (scope, domain); judge severity with craft-audit prioritization.md.
Forbidden: ### headings; ## ID · 🔴 · open shorthand; severity/status as body bullets.
Map the repo's ORM, dialect, migration tool, schema conventions, and tenant-scope helpers
before judging anything; flag assumptions made without this discovery → SKILL.md (Operating
principle)
Audit schema modeling: wrong/loose column types, missing NOT NULL on required fields,
relationships not expressed as real foreign keys, float for money, inconsistent naming →
references/schema.md
Check every tenant-scoped query routes through the shared helper enforcing tenant id
(multi-tenant required); soft-delete filter is mandatory on that helper only when the
project uses soft-delete — do not invent a soft-delete requirement on hard-delete schemas;
flag raw tenant-table reads, SELECT *, OFFSET pagination, and N+1 →
references/access-patterns.md · references/schema.md
Verify DB-level integrity: constraints/unique/FK at the database not just app-level checks,
on-delete strategy chosen deliberately, multi-step writes wrapped in transactions →
references/integrity.md
Review migrations: generated-and-reviewed (never auto-pushed/push mode), named and
timestamped consistently, reversible where cheap, breaking changes done expand-contract →
references/migrations.md
Confirm indexes back measured query patterns via EXPLAIN/EXPLAIN ANALYZE, with correct
composite column order; flag speculative, redundant, or write-amplifying indexes →
references/indexing.md
Check destructive or large-table changes are staged (add nullable → batched backfill → add
constraint → drop old), not done in one long-locking migration → references/integrity.md
Connection pool is sized appropriately (not using default unlimited connections, pgBouncer
configured if serverless or many app instances; serverless runtime pooling constraints →
craft-infra) → references/connection-pooling.md
Check PII columns are identified/flagged (comment or naming convention) and kept out of
primary keys, public URLs, and log output → references/schema.md
1---2name: craft-db3description: The Craftsman standard for database work — schema design, migrations, query writing, indexing, and multi-tenant data scoping. Use this WHENEVER the work touches the database in any form: designing or changing schema, writing migrations, modeling tables or relations, writing or optimizing queries, adding indexes, handling soft-deletes, scoping data to a tenant, or wiring up connection pooling. Trigger even when the user only says "add a column", "this query is slow", "design the data model", or "write a migration" without naming an ORM or dialect.4---56# DB Craft78This skill encodes one engineer's standard for database work, applied the same way across every9repo. The **method and opinions** live here; the **specifics** (which ORM, which dialect, which10migration tool, what the tenant-scope helper is called) live in the target repo — always discover11them first, never assume or hardcode.1213## Operating principle — discover before you build1415Different repos are at different points in their DB evolution. Before touching anything, spend a few16minutes mapping what already exists so you extend rather than duplicate or fight the grain:1718- `package.json` / lockfile → is `drizzle-orm`, `prisma`, `knex`, `pg`, `mysql2`, or similar19 already present?20- Existing migration directory (e.g. `drizzle/`, `prisma/migrations/`, `db/migrations/`) → what21 conventions are already established (naming, timestamps, reversibility)?22- Schema files → column naming style (camelCase vs snake_case), timestamp columns (`createdAt` /23 `created_at`), soft-delete column name and type, how tenant id is stored and named.24- Query helpers → is there a shared conditions/helpers file that enforces tenant scoping and25 soft-delete filters? Read it before writing any query.26- Dialect → Postgres, MySQL, SQLite? Some patterns (advisory locks, `RETURNING`, expression indexes)27 are dialect-specific.2829State what you found, then propose the smallest set of changes that achieves the goal.3031## The data layers (work in this order)32331. **Schema design** — model the domain correctly before writing a line of migration code. Choose34 the right types, enforce NOT NULL where the domain demands it, express relationships as real35 foreign keys, and name columns consistently with the existing schema. See `references/schema.md`.36372. **Migrations** — translate the schema design into a forward-only migration, reviewed before it38 ever runs. Prefer reversible migrations where the cost is low (drop → add back); know when39 forward-only is the honest choice. See `references/migrations.md`.40413. **Access patterns** — every query that touches a tenant table must go through a shared helper42 that enforces the tenant id (always, when multi-tenant) **and** the soft-delete filter **when43 the project uses soft-delete**. Write queries against real access patterns, not "I might need44 this later". See `references/access-patterns.md`.45464. **Indexing** — add indexes for the query patterns you can see in the code, measured with47 EXPLAIN/EXPLAIN ANALYZE. An index you haven't verified helps is a write-amplification tax.48 See `references/indexing.md`.49505. **Integrity & safety** — prefer DB-level constraints and foreign keys over app-level validation;51 wrap multi-step writes in transactions; plan large backfills as separate steps, not inside a52 schema migration. See `references/integrity.md`.5354## Standing opinions (the non-negotiables)5556These judgments keep output consistent — apply them unless the user overrides:5758- **Every tenant-scoped query goes through a shared helper.** That helper enforces the tenant id59 always (when multi-tenant), and the soft-delete filter **when soft-delete is used** (see60 `schema.md` "When soft-delete is used"). Querying a tenant table raw — even "just this once" —61 is the pattern that ships data leaks. Don't do it.62- **Migrations are generated and reviewed, never auto-pushed to production.** The migration tool's63 "push" shortcut skips the review step and, with some versions, produced non-convergent diffs (this64 was a confirmed drizzle-kit pre-0.20 behavior; verify the behavior of the version in use before65 relying on push even in dev). Generate a migration file, read it, then apply it.66- **DB constraints beat app-level checks.** A unique constraint enforced by the database holds even67 when a second process, a migration script, or a future developer bypasses the application layer.68 Write the constraint first, then add app-level validation for UX.69- **Index for measured patterns, not guesses.** Run EXPLAIN ANALYZE on the slow query, confirm the70 index would be used, then add it. Speculative indexes cost write performance on every insert and71 update.72- **Destructive or large-table migrations are split into stages.** Add the column nullable → backfill73 in batches → add the constraint → drop the old column. Doing all four steps in one migration risks74 long locks and an unrecoverable failure mid-way.7576## Workflow77781. **Discover** the repo's ORM, dialect, migration tool, schema conventions, and tenant-scope79 helpers. Report what you found and what's missing.802. **Propose** the schema change or query approach, explaining why the types/constraints/indexes81 make sense for the domain.823. **Implement** — generate the migration following the repo's tooling (e.g. `pnpm db:generate`),83 name and format it consistently with existing migrations, write queries through the repo's84 shared helpers.854. **Verify** — run the migration up, spot-check the resulting schema, and run EXPLAIN on any86 query that will hit a large table or run in a hot path. Migrations you haven't seen applied87 aren't done.8889## Dialect note9091These docs assume PostgreSQL. If your project uses SQLite or MySQL, check driver-specific notes in each section — locking semantics, JSON column support, and some index/constraint behaviors differ.9293## Reference index9495Read the one matching the current task — they hold the concrete patterns, not this overview:9697- `references/schema.md` — column types, naming conventions, timestamp and soft-delete patterns98- `references/migrations.md` — generation workflow, naming, reversibility, safe apply checklist99- `references/access-patterns.md` — tenant-scoped query helpers, soft-delete filtering, pagination100- `references/indexing.md` — EXPLAIN workflow, index types, GIN indexes, partial indexes, composite key order101- `references/integrity.md` — transactions, FK strategy, constraint naming, large-table backfill stages102- `references/connection-pooling.md` — pool sizing math, pgBouncer config, Drizzle pool options, leak detection103- `references/seeding-and-testing.md` — idempotent seeds, FK-aware ordering, per-test transaction rollback104105## Audit checklist (for craft-audit)106107When `craft-audit` plans a db pass for a scope, it turns this checklist into the `plan.md`108todo list — the checklist is owned by this skill, not improvised by the orchestrator. Tailor to what109discovery found: skip a step that genuinely doesn't apply with a one-line reason; never silently drop110one. Emit findings using craft-audit `workspace.md` → "Canonical findings.md emission format"111(authority). Heading grammar (variables required — do not hardcode NNN/severity/status):112113`## <scopeLabel>-DB-<NNN> · severity <🔴|🟡|🟢> · status <open|fixed|wontfix (reason)|regressed|fixed (merged into <ID>)>`114115Example only: `## <scopeLabel>-DB-001 · severity 🔴 · status open`116117Required fields under each heading, in order, with these exact labels:118`**What breaks (plain language):**` · `**Technical:**` · `**Fix:**` · `**Fingerprint:**` ·119`**Last-checked:**` (optional `**Confidence:**` — `verified | inferred | unverified-from-repo`, absent120means `verified` — then optional `**Fix-attempt:**` only from craft-fix).121Assign sequential NNN per (scope, domain); judge severity with craft-audit `prioritization.md`.122Forbidden: `###` headings; `## ID · 🔴 · open` shorthand; severity/status as body bullets.123124- [ ] Map the repo's ORM, dialect, migration tool, schema conventions, and tenant-scope helpers125 before judging anything; flag assumptions made without this discovery → `SKILL.md` (Operating126 principle)127- [ ] Audit schema modeling: wrong/loose column types, missing NOT NULL on required fields,128 relationships not expressed as real foreign keys, float for money, inconsistent naming →129 `references/schema.md`130- [ ] Check every tenant-scoped query routes through the shared helper enforcing tenant id131 (multi-tenant required); soft-delete filter is mandatory on that helper **only when the132 project uses soft-delete** — do not invent a soft-delete requirement on hard-delete schemas;133 flag raw tenant-table reads, `SELECT *`, OFFSET pagination, and N+1 →134 `references/access-patterns.md` · `references/schema.md`135- [ ] Verify DB-level integrity: constraints/unique/FK at the database not just app-level checks,136 on-delete strategy chosen deliberately, multi-step writes wrapped in transactions →137 `references/integrity.md`138- [ ] Review migrations: generated-and-reviewed (never auto-pushed/`push` mode), named and139 timestamped consistently, reversible where cheap, breaking changes done expand-contract →140 `references/migrations.md`141- [ ] Confirm indexes back measured query patterns via EXPLAIN/EXPLAIN ANALYZE, with correct142 composite column order; flag speculative, redundant, or write-amplifying indexes →143 `references/indexing.md`144- [ ] Check destructive or large-table changes are staged (add nullable → batched backfill → add145 constraint → drop old), not done in one long-locking migration → `references/integrity.md`146- [ ] Connection pool is sized appropriately (not using default unlimited connections, pgBouncer147 configured if serverless or many app instances; serverless runtime pooling constraints →148 craft-infra) → `references/connection-pooling.md`149- [ ] Check PII columns are identified/flagged (comment or naming convention) and kept out of150 primary keys, public URLs, and log output → `references/schema.md`151
Run npx skillmds@latest add gul-labs/craft-db in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
The Craftsman standard for database work — schema design, migrations, query writing, indexing, and multi-tenant data scoping. Use this WHENEVER the work touches the database in any form: designing or changing schema, writing migrations, modeling tables or relations, writing or optimizing queries, adding indexes, handling soft-deletes, scoping data to a tenant, or wiring up connection pooling. Trigger even when the user only says "add a column", "this query is slow", "design the data model", or "write a migration" without naming an ORM or dialect. It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
gul-labs (@gul-labs) published this skill. Their other Agent Skills are listed on their SkillMD profile.