Skip to main content

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:

  • Create parent tables before child tables when using foreign keys.
  • Keep up and down symmetric so rollbacks stay reliable.

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

ConceptDescription
MigrationDart class extending Migration with up/down methods
MigrationDescriptorStores migration ID, checksum, and compiled plans
MigrationLedgerTracks applied migrations in the database
MigrationRunnerApplies/rolls back migrations and updates the ledger

Next Steps