Skip to main content
Migrations allow you to evolve your database schema over time while maintaining data integrity and enabling reproducible deployments across environments.

Overview

TrailBase uses Refinery for migration management, providing:
  • Automatic migration discovery - Migrations are loaded from the filesystem
  • Version tracking - Applied migrations are recorded in _schema_history
  • Ordering by timestamp - Migrations execute in chronological order
  • Transactional safety - Each migration runs in a transaction

Migration File Format

Migrations are SQL files with a specific naming convention:
  • U = Reversible (Up) migration
  • V = Versioned (one-way) migration
  • timestamp = Unix timestamp (seconds since epoch)
  • description = Snake_case description

Examples

In practice, both U and V prefixes work the same way in TrailBase. The distinction is primarily semantic to indicate whether the migration is conceptually reversible.

Creating Migrations

Using the CLI

Generate a new migration file:
This creates a file like:
The trail migration command automatically generates a unique timestamp to prevent conflicts.

For Secondary Databases

If you’re using attached databases, specify the database name:
This creates:

Manual Creation

You can also create migration files manually. Just follow the naming convention:
When creating migrations manually, ensure timestamps are unique. If two migrations have the same timestamp, only one will be applied.

Migration Structure

Directory Layout

Migration File Content

Each migration contains one or more SQL statements:
traildepot/migrations/main/U1725019362__create_articles.sql
Migrations can contain multiple statements. Each statement should end with a semicolon.

Common Migration Patterns

Creating Tables

migrations/main/U1725019361__create_todos.sql

Adding Columns

migrations/main/U1725019400__add_priority_to_todos.sql
SQLite’s ALTER TABLE is limited. You cannot:
  • Drop columns (in older SQLite versions)
  • Modify column types
  • Add columns with NOT NULL without a DEFAULT
For complex changes, use the table recreation pattern below.

Recreating Tables

For structural changes that ALTER TABLE can’t handle:
migrations/main/U1725019500__modify_users_table.sql

Creating Indexes

migrations/main/U1725019600__add_indexes.sql

Seeding Data

Migrations can insert initial data:
migrations/main/U1725019371__add_admin_user.sql
migrations/main/U1725019363__create_editor_group.sql

Applying Migrations

Automatic Application

Migrations run automatically when TrailBase starts:
Output:

Migration Tracking

Applied migrations are recorded in the _schema_history table:
Never modify applied migrations. If a migration’s checksum changes, TrailBase will detect the divergence. Instead, create a new migration to make changes.

Development Workflow

Local Development

1

Create Migration

2

Edit SQL File

Add your schema changes to the generated file:
3

Restart TrailBase

The migration applies automatically on startup.
4

Verify Changes

Check the admin UI at http://localhost:4000/_/admin or query directly:

Testing Migrations

For critical migrations, test on a copy of production data:

Rolling Back Changes

TrailBase doesn’t support automatic rollbacks. To undo a migration:
1

Stop TrailBase

2

Restore from Backup

3

Remove Bad Migration

4

Restart TrailBase

Always take backups before running migrations in production. TrailBase creates automatic backups in traildepot/backups/, but manual backups provide extra safety.

Team Collaboration

Version Control

Commit migration files to version control:

Handling Conflicts

If two team members create migrations simultaneously:
  1. Different timestamps - Both migrations apply in timestamp order
  2. Same timestamp - Rare, but rename one migration file to a new timestamp

Deployment Strategy

1

Development

Developers create and test migrations locally:
2

Code Review

Review migration SQL in pull requests:
  • Check for syntax errors
  • Verify STRICT typing
  • Ensure indexes are created
  • Test with production-sized data
3

Staging

Deploy to staging environment:
4

Production

After staging validation, deploy to production:

Best Practices

1

Keep Migrations Small

Each migration should focus on a single logical change:Good:
Bad:
2

Test with Production Data

Test migrations against production-sized datasets to catch performance issues:
3

Document Complex Migrations

Add comments explaining non-obvious changes:
4

Handle Data Carefully

When migrating existing data, preserve user information:
5

Create Indexes Concurrently

For large tables, consider adding indexes in a separate migration after the table is created:
migrations/main/U1725019362__create_logs.sql
migrations/main/U1725019363__index_logs.sql

Troubleshooting

Migration Failed

If a migration fails, TrailBase will abort and log the error:
Solution:
  1. Fix the SQL syntax error
  2. Remove the failed migration from _schema_history:
  3. Restart TrailBase

Divergent Migrations

If a migration’s contents change after being applied:
Solution: Don’t modify applied migrations. Instead:
  1. Create a new migration with the changes
  2. Update the original file only for future deployments

Missing Migration Files

If _schema_history references a missing file, manually remove the entry:

Advanced Topics

Embedded vs. File-based Migrations

TrailBase supports both:
  • File-based (default) - Loaded from traildepot/migrations/
  • Embedded - Compiled into the binary for custom distributions
For most users, file-based migrations are recommended for flexibility.

Multiple Databases

TrailBase supports attaching additional databases:
traildepot/config.textproto
Migrations for attached databases go in separate directories:

Next Steps

Database Setup

Learn schema design patterns

CLI Usage

Master TrailBase CLI commands

First App

Build your first application

Authentication

Add user accounts and auth