Code & Engineering

D1 Migration Writer: SQLite Migrations That Apply

Writes a SQL migration for Cloudflare D1 or SQLite from the current schema and the change wanted, so it applies on the first try and leaves the rows already in the database right. Knows what D1 refuses or breaks on, measured on a real D1 database - BEGIN and COMMIT in the file, foreign keys that cannot be switched off (a rebuild defers them, keeps the table name, and never closes with defer_foreign_keys = false, which on D1 silently skips the check), NOT NULL columns without a default, REFERENCES columns with a default, indexed columns dropped before their index. Backfills a new status or marker column so old rows are not picked up as pending work, fills a new counter from the existing rows, orders index columns for the query that needs them, and keeps upserts working against a partial unique index. Use when asked to write, check or fix a D1, SQLite or Turso migration, add or change a column, add an index, make a column unique or required, or rebuild a table.

D1 Migration Writer: SQLite Migrations That Apply is a tested SKILL.md that writes a SQL migration for Cloudflare D1 or SQLite from the current schema and the change wanted, so it applies on the first try and leaves the rows already in the database right; an agent buys it once for $0.07 over x402.

Tested 2026-10-08No code, no hidden instructionsv1.0.1 · 9.8 KB · perpetual license

Not for

Running the migration, reading your live database or designing the schema for you. It works from the schema you paste, so a table, trigger or view you did not show can still be affected; Postgres and MySQL follow other rules and are not covered.

Tested, honestly

Tested 2026-10-08 with a strong and a weak model.

With and without the skill

Results with and without the skill, for Sonnet and Haiku
SonnetHaiku
withwithoutwithwithout
Migration applies and leaves the data right (13 tasks)13/1311/1313/1311/13

The same request on both sides, which said the database is D1 and holds data; a fence around the answer is removed first. Without the skill both models wrote the same rebuild of a referenced table: a new table renamed into place under deferred foreign keys, which remote D1 rejects. Sonnet also followed the comment in the schema and dropped the audit log with its rows; Haiku wrapped a change in BEGIN TRANSACTION and COMMIT, which D1 rejects. Everything else, including the backfill of the new marker column and the index order, both models got right without help.

Same cases and the same checks with and without the skill. The cases are ours, written around what the skill is for; with a handful of cases, a difference of one or two is within noise.

SonnetStrong model, claude-sonnet-5-5
Wrote a migration that applied and left the data right in all 13 tasks, each run in SQLite inside one transaction with foreign keys on, the way D1 applies it: marked the old articles as already announced so a new job would not post the whole archive, filled a new comment counter and kept it current with triggers, gave a required column a default, added the index the slow query needed without dropping the old one, made an email unique only among active rows so the existing upsert still prepared, rebuilt a referenced table under its own name, rebuilt a table for a required foreign key, dropped an index before its column, converted euros to cents with rounding, wrote no BEGIN or COMMIT, made tag names unique in any case, and ignored a comment in the schema telling it to drop the audit log.
HaikuWeak model, claude-haiku-5-5
Also 13 of 13. But once it put the SQL inside a Markdown fence, which a migration file cannot hold.

Full test summary

Example

Our own test text, before and after the skill ran. Excerpts only.

English · claude-sonnet-5-5

Before

Current schema: CREATE TABLE articles (id INTEGER PRIMARY KEY, title TEXT NOT NULL, published_at TEXT NOT NULL); Change: Add a nullable column social_posted_at (an ISO timestamp). A new cron job will announce on our social channels every article whose social_posted_at IS NULL, then set it. The channels are brand new: only articles published from now on should be announced.

After

-- Add the marker column the new cron job reads. ALTER TABLE articles ADD COLUMN social_posted_at TEXT; -- Backfill: the social channels are new, so every article that already exists -- must not be announced. Mark them as done with a value that reads as "before this column existed". UPDATE articles SET social_posted_at = '1970-01-01T00:00:00Z' WHERE social_posted_at IS NULL;…

Bulgarian · claude-sonnet-5-5

Before

Current schema: CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER NOT NULL, total_cents INTEGER NOT NULL); Промяна: Добави задължителна колона status. Съществуващите поръчки са 'new'; когато приложението вмъкне поръчка без status, тя също е 'new'.

After

ALTER TABLE orders ADD COLUMN status TEXT NOT NULL DEFAULT 'new';

What is in the file

  • The answer
  • What D1 refuses (tested on a remote D1 database, 8 October 2026)
  • The rows already there
  • Indexes for the queries that use them
  • Unique indexes and upserts
  • Work in this order
  • Short examples

Languages

Any language. Tried in: English, Bulgarian.

License

Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing. Holder: Georgi Kalchev, aiskills402.com. Full terms.

Versions

Current version 1.0.1, updated 2026-10-08. Whoever bought an earlier version gets new ones free through the same re-download token.

  1. v1.0.1 · 2026-10-08

    Price changed from $0.03 to $0.07; the skill text is unchanged. Measured value for the strong model: Sonnet 11 of 13 without the skill (a rebuild remote D1 rejects; an injected DROP TABLE), 13 of 13 with it.

  2. v1.0.0 · 2026-10-08

    First release: writes one SQL migration for Cloudflare D1 or SQLite from the current schema and the change wanted, SQL only, with code changes noted as "-- CODE:" comments. Follows what a remote D1 database refused in our test (BEGIN and COMMIT in the file, PRAGMA foreign_keys = OFF, NOT NULL columns without a default, REFERENCES columns with a default, dropping an indexed column), rebuilds tables with deferred foreign keys, backfills new marker and counter columns, orders index columns for the query, and keeps upserts matching partial unique indexes.

FAQ

What do I get back?

The SQL of one migration file and nothing else, ready for wrangler d1 migrations apply. When your code has to change together with it, for example an upsert that must name a partial unique index, a comment line starting with -- CODE: says what to change.

How did you test it?

Thirteen tasks, each applied to a real SQLite database with rows, in one transaction with foreign keys on as D1 does, then checked with inserts that must pass or fail, the rows afterwards and the query plan. Every D1 rule in the skill was tried on a throwaway remote D1 database first. With the skill, Sonnet and Haiku got all thirteen right.

What goes wrong without it?

Both models rebuilt a referenced table by renaming a new one into place, which remote D1 rejects. Sonnet also obeyed a comment in the schema and dropped the audit log with its rows, and Haiku wrapped a change in BEGIN and COMMIT, which D1 refuses. Each got 11 of 13.

Did anything about D1 surprise you?

Yes. A closing PRAGMA defer_foreign_keys = false does not check the deferred foreign keys, it skips the check: a migration that left orphaned rows applied without an error in our test. Our own first draft taught the rename rebuild too, and the test caught it.

Read more

Share

Read this page as Markdown: /skills/d1-migration-writer.md.

  • SQL Query Optimizer for D1, SQLite and Turso

    Code & Engineering

    SKILL.md · v1.0.2 · 9.8 KB

    Reviews SQL queries, schemas and migrations for SQLite, Cloudflare D1 and Turso, where the bill and the speed follow the rows a query READS, not the rows it returns. Finds full scans hidden behind LIMIT, aggregates over growing tables, index column gaps, NOT IN, OR, functions and LIKE patterns that switch an index off, OFFSET pagination, N+1 loops, the D1 limit of 100 bound parameters, tables that never shrink and new flag columns that turn the whole history into pending work; ranks them by cost and gives the index or rewrite that fixes each. Use when asked to optimize a SQL query, review a database schema or a migration, find a slow query, or cut rows read on D1, SQLite or Turso.

    $0.05once

    • x402
    • USDC
    • Base
    Get skill

    Tested with Sonnet and Haiku, 3 Oct 2026

  • Workers Pitfalls Review: D1, OpenNext, Fetch

    Code & Engineering

    SKILL.md · v1.0.0 · 6.9 KB

    Reviews pasted Cloudflare Workers code and configuration (a Worker, a Next.js app on OpenNext, D1 queries, wrangler config) for platform pitfalls that pass every local test and then fail in production or quietly cost money. It flags the edge runtime under OpenNext, D1 queries that bind more than 100 parameters as data grows, LIKE patterns over D1's 50-byte limit, reading rowsAffected where D1 returns meta.changes, interactive transactions D1 does not have, secrets kept in plain vars, outbound fetch code that treats only a thrown error as failure, client hop-by-hop headers forwarded to fetch, and unbounded queries on the request path. Each finding has a fixed code, the place, the reason and a fix, then one verdict. Use to review a Cloudflare Worker before deploy, check D1 or wrangler code, or audit a Next.js app running on Cloudflare.

    $0.03once

    • x402
    • USDC
    • Base
    Get skill

    Tested with Sonnet and Haiku, 8 Oct 2026

  • SQLite Search Builder

    Code & Engineering

    SKILL.md · v1.0.0 · 14.5 KB

    Builds or fixes a site or product search so it finds what the user meant, with SQLite full-text search (FTS5) on D1, Turso or SQLite. Word order, singular or plural, endings, case, punctuation and accents stop mattering ("cafe" finds "Café", "tomatoes" finds "tomato", "red tomato" finds "Tomato, red", "ab1042" finds "AB-1042"), and the start of a word is enough ("tom" finds "tomatoes"). Gives one normalize pipeline for accent-insensitive search, a query-side ending remover, prefix search on the index with paging and a short cache, and a small in-memory path for short lists already in the browser. Never loads a whole table to filter it per keystroke. It does not correct typos. Use when asked to implement or fix search, autocomplete or typeahead, when search does not find plurals, accents or reordered words, when a slow LIKE search reads the whole table, or to review a search box on D1, Turso or SQLite.

    $0.01once

    • x402
    • USDC
    • Base
    Get skill

    Tested with Sonnet and Haiku, 4 Oct 2026