Select ORMs to compare
Pick one or more ORMs from the bar above
Generate Migration
SQL
-- No migration tool; write SQL manually
ALTER TABLE users
ADD COLUMN age INTEGER;
Django ORM
# Add a new field to the model
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
age = models.IntegerField(null=True) # ← new
SQLAlchemy
# Add a new column to the model
class User(Base):
__tablename__ = "users"
id = mapped_column(Integer, primary_key=True)
name = mapped_column(String(100))
email = mapped_column(String, unique=True)
age = mapped_column(Integer, nullable=True) # ← new
SQLModel
# Add a new field to the model
class User(SQLModel, table=True):
__tablename__ = "users"
id: int | None = Field(default=None, primary_key=True)
name: str = Field(max_length=100)
email: str = Field(unique=True, max_length=255)
age: int | None = Field(default=None) # ← new
Tortoise ORM
# Add a new field to the model
class User(Model):
name = fields.CharField(max_length=100)
email = fields.CharField(max_length=255, unique=True)
age = fields.IntField(null=True) # ← new
Peewee
# Add a new field to the model
class User(Model):
name = CharField(max_length=100)
email = CharField(max_length=255, unique=True)
age = IntegerField(null=True) # ← new
Prisma
// Add a new field to the schema
model User {
id Int @id @default(autoincrement())
name String
email String @unique
age Int? // ← new
}
Drizzle
// Add a new column to the schema
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").unique(),
age: integer("age"), // ← new
});
Kysely
// Add a new column type to DB interface
export interface UserTable {
id: number
name: string
email: string
age: number | null // ← new
}
TypeORM
// Add a new column to the entity
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column({ nullable: true })
age: number; // ← new
}
MikroORM
// Add a new field to the entity
@Entity({ tableName: "users" })
export class User {
@PrimaryKey()
id!: number;
@Property({ length: 100 })
name!: string;
@Property({ length: 255, unique: true })
email!: string;
@Property({ nullable: true })
age?: number; // ← new
}
Sequelize
// Add a new field to the model
class User extends Model {
declare id: CreationOptional<number>;
declare name: string;
declare email: string;
declare age: number | null; // ← new
}
User.init(
{
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
name: { type: DataTypes.STRING(100), allowNull: false },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true },
age: { type: DataTypes.INTEGER, allowNull: true },
},
{ sequelize, tableName: "users" },
);Apply Migration
SQL
-- Run migration file manually
\i migrations/0002_add_user_age.sql
-- Or via command line
-- psql -d mydb -f migrations/0002_add_user_age.sql
Django ORM
$ python manage.py migrate
# Operations to perform:
# Apply all: myapp
# Running migrations:
# Applying myapp.0002_user_age... OK
# Apply only a specific app
$ python manage.py migrate myapp
SQLAlchemy
$ alembic upgrade head
# INFO [alembic.runtime.migration]
# Running upgrade -> ae10, add user age
# Apply next migration only
$ alembic upgrade +1
SQLModel
$ alembic upgrade head
# INFO [alembic.runtime.migration]
# Running upgrade -> ae10, add user age
# Apply next migration only
$ alembic upgrade +1
Tortoise ORM
$ tortoise migrate
# Applying models.0002_add_user_age... OK
# Apply only a specific app
$ tortoise migrate models
Peewee
# Run migration scripts manually
$ python migrations/20240115_add_user_age.py
# Executes upgrade() and adds users.age
Prisma
# Development (generates + applies)
$ npx prisma migrate dev
# Applying migration `20240101_add_user_age`
# ✔ Generated Prisma Client
# Production (applies only)
$ npx prisma migrate deploy
Drizzle
$ npx drizzle-kit migrate
# [✓] migrations applied successfully
# 0001_add_user_age.sql
Kysely
# Apply migrations with Kysely Migrator
$ bun run migrate:up
# Applying migration:
# 20240115_add_user_age
TypeORM
$ npx typeorm migration:run -d data-source.ts
# query: SELECT * FROM "migrations"
# 1 migration is pending
# query: ALTER TABLE "user" ADD "age" integer
# Migration AddUserAge1234567890 has been
# executed successfully
MikroORM
$ npx mikro-orm migration:up
# [migrator] Applying Migration20240115000000
# Apply up to specific migration
$ npx mikro-orm migration:up --to Migration20240115000000
Sequelize
$ npx sequelize-cli db:migrate
# == 20240115000000-add-user-age: migrated
# Apply migrations up to a specific file
$ npx sequelize-cli db:migrate --to 20240115000000-add-user-age.jsRollback Migration
SQL
-- Write reverse migration manually
-- rollback: 0002_add_user_age.sql
ALTER TABLE users DROP COLUMN age;
Django ORM
# Revert to a previous migration
$ python manage.py migrate myapp 0001_initial
# Operations to perform:
# Target: 0001_initial
# Running migrations:
# Unapplying myapp.0002_user_age... OK
# Django auto-generates the reverse operation
# (drops the "age" column)
# Revert all migrations for an app
$ python manage.py migrate myapp zero
SQLAlchemy
# Rollback last migration
$ alembic downgrade -1
# INFO [alembic.runtime.migration]
# Running downgrade ae10 -> 5ba1, add user age
# Executes the downgrade() function:
# op.drop_column("users", "age")
# Rollback to a specific revision
$ alembic downgrade ae1027a6acf
SQLModel
# Rollback last migration
$ alembic downgrade -1
# INFO [alembic.runtime.migration]
# Running downgrade ae10 -> 5ba1, add user age
# Executes the downgrade() function:
# op.drop_column("users", "age")
# Rollback to a specific revision
$ alembic downgrade ae1027a6acf
Tortoise ORM
# Revert to a previous migration
$ tortoise downgrade models 0001_initial
# Unapplying models.0002_add_user_age... OK
Peewee
# Rollback by running reverse migration
$ python migrations/20240115_add_user_age.py --downgrade
# Executes downgrade() and drops users.age
Prisma
# Prisma has no built-in rollback command.
# Options:
# 1. Revert schema and create a new migration
# (remove the "age" field from schema.prisma)
$ npx prisma migrate dev --name revert_user_age
# 2. Manually run reverse SQL
$ npx prisma db execute --file rollback.sql
# rollback.sql:
# ALTER TABLE "User" DROP COLUMN "age";
Drizzle
# Drizzle has no built-in rollback.
# Options:
# 1. Revert schema and generate a new migration
# (remove the "age" column from schema.ts)
$ npx drizzle-kit generate
# 2. Use drizzle-kit push for dev databases
$ npx drizzle-kit push
Kysely
# Rollback last migration
$ bun run migrate:down
# Reverted migration:
# 20240115_add_user_age
TypeORM
# Revert last applied migration
$ npx typeorm migration:revert -d data-source.ts
# query: SELECT * FROM "migrations"
# Reverting AddUserAge1234567890
# query: ALTER TABLE "user" DROP COLUMN "age"
# Migration AddUserAge1234567890 has been
# reverted successfully
# Executes the down() method of the migration
MikroORM
# Revert last applied migration
$ npx mikro-orm migration:down
# [migrator] Reverting Migration20240115000000
# Revert down to a specific migration
$ npx mikro-orm migration:down --to Migration20240101000000
Sequelize
# Revert last applied migration
$ npx sequelize-cli db:migrate:undo
# == 20240115000000-add-user-age: reverted
# Revert down to a specific migration
$ npx sequelize-cli db:migrate:undo:all --to 20240101000000-initial.jsMigration Status
SQL
-- No built-in tracking; use a custom table
SELECT version, name, applied_at
FROM schema_migrations
ORDER BY applied_at DESC;
-- version | name | applied_at
-- 0002 | add_user_age | 2024-01-15
-- 0001 | initial | 2024-01-01
Django ORM
$ python manage.py showmigrations myapp
# myapp
# [X] 0001_initial
# [X] 0002_user_age
# [ ] 0003_user_avatar ← pending
$ python manage.py showmigrations --plan
# [X] myapp.0001_initial
# [X] myapp.0002_user_age
# [ ] myapp.0003_user_avatar
SQLAlchemy
$ alembic current
# ae1027a6acf (head)
$ alembic history --verbose
# Rev: ae10 (head)
# Parent: 5ba1
# Path: alembic/versions/ae10_add_user_age.py
# add user age
#
# Rev: 5ba1
# Parent: <base>
# Path: alembic/versions/5ba1_initial.py
# initial
SQLModel
$ alembic current
# ae1027a6acf (head)
$ alembic history --verbose
# Rev: ae10 (head)
# Parent: 5ba1
# Path: alembic/versions/ae10_add_user_age.py
# add user age
#
# Rev: 5ba1
# Parent: <base>
# Path: alembic/versions/5ba1_initial.py
# initial
Tortoise ORM
$ tortoise history
# models
# [X] 0001_initial
# [X] 0002_add_user_age
Peewee
# No built-in migration status command in Peewee.
# Common workaround: track applied files in a custom
# migrations table and query it.
$ psql "$DATABASE_URL" -c "SELECT * FROM migrations ORDER BY id;"
Prisma
$ npx prisma migrate status
# Status
# 2 migrations found in prisma/migrations
#
# ✔ 20240101_initial (applied)
# ✔ 20240115_add_user_age (applied)
#
# Database schema is up to date!
Drizzle
$ npx drizzle-kit check
# [✓] 0000_initial.sql
# [✓] 0001_add_user_age.sql
# No pending migrations
Kysely
# Show executed and pending migrations
$ bun run migrate:status
# [X] 20240101_init
# [X] 20240115_add_user_age
# [ ] 20240201_add_user_avatar ← pending
TypeORM
$ npx typeorm migration:show -d data-source.ts
# [X] Initial1234567880
# [X] AddUserAge1234567890
# [ ] AddUserAvatar1234567900 ← pending
MikroORM
$ npx mikro-orm migration:list
# [X] Migration20240101000000
# [X] Migration20240115000000
$ npx mikro-orm migration:pending
# [ ] Migration20240201000000 ← pending
Sequelize
$ npx sequelize-cli db:migrate:status
# up 20240101000000-initial.js
# up 20240115000000-add-user-age.js
# down 20240201000000-add-user-avatar.js ← pending