Migrations Overview
Migrations are version-controlled database schema changes. Each migration defines changes to apply in the up method and reverse operations in the down method.
Introduction
Migrations solve a fundamental problem: how to evolve your database schema safely across different environments. Instead of manually running SQL, migrations provide a programmatic way to define schema changes that can be version-controlled, reviewed, and deployed consistently.
Ormed migrations support SQLite, PostgreSQL, and MySQL, providing a unified API for schema management.
Migration Formats
Ormed supports two migration formats that can be used interchangeably and even mixed within the same project.
Dart Migrations (Default)
Type-safe migrations using a fluent SchemaBuilder. This is the recommended format for most changes as it provides IDE autocompletion and driver-agnostic schema definitions.
dart run ormed_cli:ormed make --name create_users_table
SQL Migrations
Raw .sql files for complex schema changes, database-specific features (like triggers or stored procedures), or when porting existing SQL scripts.
dart run ormed_cli:ormed make --name add_bio_to_users --format sql
This creates a directory containing up.sql and down.sql files.
Simultaneous Support
The migration runner is format-agnostic. It builds a chronological timeline of all registered migrations based on their timestamps. When you run ormed migrate, it will execute Dart classes and SQL files in the correct order, ensuring your schema evolves predictably regardless of which format you chose for a specific change.
Basic Structure
Every migration extends the Migration base class:
class CreatePostsTableBasic extends Migration {
const CreatePostsTableBasic();
void up(SchemaBuilder schema) {
schema.create('users', (table) {
table.integer('id').primaryKey();
table.string('email').unique();
table.string('name');
table.timestamp('created_at').nullable();
});
}
void down(SchemaBuilder schema) {
schema.drop('users', ifExists: true);
}
}
The up method runs when applying migrations, down runs during rollbacks. Always implement both to ensure migrations are reversible.
Creating Tables
Use schema.create() to define a new table:
- Notes
- Code
- Create parent tables before child tables when using foreign keys.
- Keep
upanddownsymmetric so rollbacks stay reliable.
class CreatePostsTable extends Migration {
const CreatePostsTable();
void up(SchemaBuilder schema) {
schema.create('posts', (table) {
table.integer('id').primaryKey().autoIncrement();
table.string('title');
table.text('content');
table.integer('author_id');
table.boolean('published').defaultValue(false);
table.timestamp('created_at').nullable();
table.foreign(
['author_id'],
references: 'users',
referencedColumns: ['id'],
onDelete: ReferenceAction.cascade,
);
table.index(['author_id', 'published']);
});
}
void down(SchemaBuilder schema) {
schema.drop('posts', ifExists: true);
}
}
Modifying Tables
Use schema.table() to alter existing tables:
class AddSlugToPosts extends Migration {
const AddSlugToPosts();
void up(SchemaBuilder schema) {
schema.table('posts', (table) {
table.string('slug').nullable();
table.integer('view_count').defaultValue(0);
table.index(['slug']).unique();
table.renameColumn('content', 'body');
});
}
void down(SchemaBuilder schema) {
schema.table('posts', (table) {
table.renameColumn('body', 'content');
table.dropColumn('view_count');
table.dropColumn('slug');
});
}
}
When adding non-nullable columns to tables with existing data:
- Make the column nullable:
table.string('slug').nullable() - Or provide a default:
table.string('status').defaultValue('draft')
Migration Naming
Migration files use timestamps for ordering: m_YYYYMMDDHHMMSS_slug.dart
Example: m_20241129143052_create_users_table.dart
Choose descriptive names:
- ✅
create_users_table - ✅
add_email_index_to_users - ✅
remove_deprecated_status_column - ❌
update_schema - ❌
migration_v2
Runtime Concepts
| Concept | Description |
|---|---|
| Migration | Dart class extending Migration with up/down methods |
| MigrationDescriptor | Stores migration ID, checksum, and compiled plans |
| MigrationLedger | Tracks applied migrations in the database |
| MigrationRunner | Applies/rolls back migrations and updates the ledger |
Next Steps
- Schema Builder - Complete API for defining columns, indexes, and constraints
- Running Migrations - CLI and programmatic usage
- CLI Commands - Full command reference