migtool 11.2.0
dotnet tool install --global migtool --version 11.2.0
dotnet new tool-manifest
dotnet tool install --local migtool --version 11.2.0
#tool dotnet:?package=migtool&version=11.2.0
nuke :add-package migtool --version 11.2.0
Migrate is a SQLite-first toolkit: ordered *.sql files are the dev-time schema source of truth, and mig codegen turns them into typed F# query modules plus a migrations module that ships with the executable.
What it does
- You write ordered
*.sqlmigrations (applied by file name) during development. - You annotate tables/views with
-- mig:comments for the ops you want. mig codegen(orMigLib.Codegen.generate) applies migrations to a temp DB, introspects schema, and emits one.fsmodule per annotated relation plusMigrations.fs(scriptsas ordinary F# string constants) into an output directory.- At runtime you call
MigLib.migrateScripts dbPath Migrations.scriptsand usedbTxnwith generated helpers onMicrosoft.Data.Sqlite.
Migrations travel inside the binary as compiled string data — not as loose *.sql files next to the app, not as EmbeddedResource / content files, and not via reflection. That keeps Native AOT and trimming friendly.
There is no F#-first schema DSL, no SqlProvider, no DbUp dependency, and no automatic normalization.
Annotation example
-- mig:rel User
-- mig:ops insert, select_by(email), select_by_id, count
-- mig:ops upsert
-- mig:bool active
-- mig:datetime created_at
CREATE TABLE app_user (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
active INTEGER NOT NULL,
created_at TEXT NOT NULL
) STRICT;
-- mig:ops select_all, select_one_by(email), count
CREATE VIEW active_user AS
SELECT id, email, created_at FROM app_user WHERE active = 1;
-- Optional AND filters (filter catalog):
-- mig:ops filter_search(created_at desc, id), filter_count
-- mig:filter status eq status
-- mig:filter label_prefix like_prefix label
-- mig:filter text_any eq_any label, notes
CREATE TABLE item (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
status TEXT NOT NULL,
label TEXT NOT NULL,
notes TEXT NOT NULL,
created_at TEXT NOT NULL
) STRICT;
Rules:
- Unannotated relations generate no F#.
- Ops are explicit; no ops means no type.
- Multiple
-- mig:opslines on one relation are merged in order (handy for long lists). - Views allow read ops (
select_*,count,filter_search,filter_count) plus optionaldelete_matching(table, key); other write ops fail at codegen. countemits unfilteredSELECT COUNT(*) FROM [relation]aslet count : TxnStep<int64>(tables and views).- Filter catalog (
-- mig:filter name kind column[, column…]plusfilter_search/filter_count):- Emits
{Rel}Filter(each fieldT option),emptyFilter, publicapplyFilter(WHERE + params for hand SQL),search filter skip take, and/orcountByFilter filter. - Kinds:
eq,neq,gt,gte,lt,lte,like_prefix(bindsv + "%"),eq_any(OR across 2+ columns, one bind). - Present
Somevalues become AND clauses; all-NoneusesWHERE 1=1. - Requires at least one
-- mig:filterwithfilter_searchand/orfilter_count(and vice versa). At most one of each filter op per relation.
- Emits
-- mig:rel Nameis optional; otherwise the F# name is derived from the SQL identifier.
See specs/sql_first_rewrite.md for the full design.
Installation
dotnet tool install --global migtool
Library:
dotnet add package MigLib
Codegen
mig codegen \
--migrations ./Migrations \
--output ./Stores \
--namespace MyApp.Db
This writes Stores/User.fs, Stores/ActiveUser.fs, etc. (module MyApp.Db.User, …), plus Stores/Migrations.fs (module MyApp.Db.Migrations with scripts : (string * string) list). Hand-written companions can live alongside generated files; only files marked // <auto-generated /> are deleted when a relation disappears (Migrations.fs is always kept).
From build.fsx / F#:
open MigLib.Codegen
match generate "./Migrations" "./Stores" "MyApp.Db" with
| Ok r ->
Console.WriteLine(
"generated "
+ string r.relationCount
+ " relations in "
+ r.outputDir)
r.generatedFiles |> List.iter (fun p -> Console.WriteLine(" " + p))
| Error e -> failwith e
Runtime
open System
open MigLib
open MyApp.Db
// apply scripts compiled into the app (module MyApp.Db.Migrations)
do!
migrateScripts dbPath Migrations.scripts
|> Task.map (function Ok () -> () | Error e -> failwith e)
// use transactions + generated queries (module MyApp.Db.User)
let! user =
dbTxn dbPath {
match! User.selectOneByEmail email with
| Some u -> return u
| None ->
let! id =
User.insert
{ Email = email
Active = true
CreatedAt = DateTimeOffset.UtcNow }
return! User.selectById id |> TxnStep.map Option.get
}
Packages
| Package | Role |
|---|---|
| migtool | CLI (mig codegen, mig version) |
| MigLib | Runtime: dbTxn / TxnStep, Query, list-based migrateScripts (AOT-friendly; no reflection) |
| MigLib.Codegen | Dev-time generate API (used by CLI and build.fsx) |
Local build
dotnet fsi build.fsx -- --target build
dotnet fsi build.fsx -- --target install
cd src && dotnet test
Version
Major version 10 is a greenfield rewrite (SQL-first). Older MigSchema / attribute-based workflows are not supported.
Further reading
See documentation/interesting-links.md for SQLite concurrency, migrations, and related theory links.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 11.2.0 | 42 | 8/4/2026 |
| 11.1.0 | 58 | 8/3/2026 |
| 11.0.0 | 56 | 7/31/2026 |
| 10.0.0 | 83 | 7/30/2026 |
| 9.1.0 | 133 | 6/2/2026 |
| 9.0.0 | 131 | 6/1/2026 |
| 8.3.0 | 114 | 5/29/2026 |
| 8.2.0 | 115 | 5/29/2026 |
| 8.1.1 | 127 | 5/28/2026 |
| 8.1.0 | 145 | 5/28/2026 |
| 8.0.4 | 131 | 5/26/2026 |
| 8.0.3 | 131 | 5/26/2026 |
| 8.0.2 | 137 | 5/24/2026 |
| 8.0.1 | 135 | 5/23/2026 |
| 8.0.0 | 131 | 5/17/2026 |
| 7.0.2 | 146 | 4/14/2026 |
| 7.0.1 | 128 | 4/14/2026 |
| 7.0.0 | 147 | 4/11/2026 |
| 6.0.1 | 143 | 4/10/2026 |
| 6.0.0 | 128 | 4/10/2026 |