sqlite-utils 4.0, now with database schema migrations
This covers a coding tool or code-capability update — useful for developers assessing workflow changes and reusable value.
7th July 2026
This morning I released sqlite-utils 4.0 , the 124th release of that project and the first major version bump since 3.0 in November 2020. In addition to some small but significant breaking changes (described in this upgrade guide ), this version introduces three major features: database migrations, nested transactions (via a new db.atomic() method), and support for compound foreign keys.
Database schema migrations using sqlite-utils
Schema migrations define a sequence of changes to be made to a SQLite database, plus a mechanism for tracking which migrations have been applied and applying any that are found to be pending.
Migrations are defined in Python files using the sqlite-utils Python library , which includes a powerful table.transform() method providing enhanced alter table capabilities that are not supported by SQLite’s ALTER TABLE statement.
(table.transform() implements the pattern recommended by the SQLite documentation —create a new temporary table with the new schema, copy across the data, then drop the old table and rename the temporary one in its place.)
Here’s an example migration file which creates a table called creatures, adds an additional column to it in a second step, then changes the types of two of the columns in a third:
from sqlite_utils import Migrations
migrations = Migrations ( "creatures" )
@ migrations () def create_table ( db ): db [ "creatures" ]. create ( { "id" : int , "name" : str , "species" : str }, pk = "id" , )
@ migrations () def add_weight ( db ): db [ "creatures" ]. add_column ( "weight" , float )
@ migrations () def change_column_types ( db ): db [ "creatures" ]. transform ( types = { "species" : int , "weight" : str })
Save that as migrations.py and run it against a fresh database like this:
uvx sqlite-utils migrate data.db migrations.py
Then if you check the schema of that database:
uvx sqlite-utils schema data.db
You’ll see this SQL:
CREATE TABLE " _sqlite_migrations " ( " id " INTEGER PRIMARY KEY , " migration_set " TEXT , " name " TEXT , " applied_at " TEXT ); CREATE UNIQUE INDEX " idx__sqlite_migrations_migration_set_name " ON " _sqlite_migrations " ( " migration_set " , " name " ); CREATE TABLE " creatures " ( " id " INTEGER PRIMARY KEY , " name " TEXT , " species " INTEGER , " weight " TEXT );
The _sqlite_migrations table is used to keep track of which migration functions have been run. The creatures table above is the schema after all three migrations have been applied.
To see a list of migrations, both pending and applied, run this:
uvx sqlite-utils migrate data.db migrations.py --list
Output:
Migrations for: creatures
Applied: create_table - 2026-07-07 17:58:41.360051+00:00 add_weight - 2026-07-07 17:58:41.360608+00:00 change_column_types - 2026-07-07 18:01:15.802000+00:00