sqlite-migrate is a Go CLI and embeddable library that generates and safely applies SQLite schema migrations from a plain-SQL schema.sql source of truth — including full automatic rebuilds for changes SQLite’s native ALTER TABLE can’t express (a type change, a new CHECK/UNIQUE constraint, a changed foreign-key action, a rename).

You write schema.sql. sqlite-migrate generate diffs it against the schema your existing migrations produce and writes the next migration file for you — a plain ALTER TABLE/CREATE TABLE when that’s enough, or the full create-copy-drop-rename rebuild when it isn’t. See Safety model for what makes that automation trustworthy rather than reckless.

Installation

go install github.com/mdg-labs/sqlite-migrate/cmd/sqlite-migrate@latest

This requires a Go toolchain matching (or newer than) the go directive in this repository’s go.mod; go install downloads a matching toolchain automatically if the one you have installed is older.

If no tagged release exists yet, build from a clone instead:

git clone https://github.com/mdg-labs/sqlite-migrate.git
cd sqlite-migrate
go build -o sqlite-migrate ./cmd/sqlite-migrate

Either way you get a single, statically-linked sqlite-migrate binary — the SQLite driver (modernc.org/sqlite) is pure Go, so there’s no cgo toolchain or system SQLite library to install alongside it.

Agent skill

If you drive schema changes through an AI coding agent (Claude Code, Cursor, or any other agent the vercel-labs/skills CLI supports), install the sqlite-migrate skill so it runs generate, check, apply, verify, and status correctly and never reaches for --allow-destructive just to get past a refusal:

npx skills add mdg-labs/sqlite-migrate --skill sqlite-migrate

Read the skill itself on GitHub: skills/sqlite-migrate/SKILL.md.

Quickstart

1. Write schema.sql. Every table must be declared STRICT — see why in the safety model.

CREATE TABLE users (
    id         INTEGER PRIMARY KEY,
    email      TEXT NOT NULL UNIQUE,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
) STRICT;

2. Generate the first migration. With no migrations/ directory yet, generate treats this as a cold repo and writes a migration that creates the table from nothing:

sqlite-migrate generate
# wrote migrations/20260917143022_create_users.sql

3. Check the migration journal is internally consistent (no DB connection needed — safe to run in CI):

sqlite-migrate check
# ok: 1 migration(s) verified

4. See what apply would do. apply defaults to a dry run — nothing touches the database until you pass --yes:

sqlite-migrate apply --db app.db
# dry run: 1 pending migration(s) would be applied:
#   20260917143022_create_users
# rerun with --yes to apply

5. Apply it for real:

sqlite-migrate apply --db app.db --yes
# applied 1 migration(s):
#   20260917143022_create_users

This first backs up app.db via VACUUM INTO (see Safety model), then runs the migration in one transaction with foreign_key_check/integrity_check before commit.

6. Change schema.sql and repeat. Add a column, tighten a constraint, change a column’s type, rename something — generate figures out which of those is a plain ALTER TABLE, which needs a full rebuild, and asks a one-line question in your terminal only when it can’t tell a rename from a drop-and-add on its own:

sqlite-migrate generate
# Rename `users.email` to `users.email_address`? [Y/n]

7. Check what’s applied to a given database at any time:

sqlite-migrate status --db app.db
# 20260917143022_create_users  applied at 2026-09-17T14:30:25Z

8. Verify a real database still matches the migration journal, independent of its own bookkeeping table — useful after a manual edit or to audit a database you didn’t apply the migrations to yourself:

sqlite-migrate verify --db app.db
# ok: database matches the migration journal

Full flags for every command: CLI reference.