apps/docs/content/docs.v6/guides/data-migration.mdx
When making changes to your database schema in production, it's crucial to ensure data consistency and avoid downtime. This guide shows you how to use the expand and contract pattern to safely migrate data between columns. We'll walk through a practical example of replacing a boolean field with an enum field while preserving existing data.
Before starting this guide, make sure you have:
Start with a basic schema containing a Post model:
generator client {
provider = "prisma-client"
output = "./generated/prisma"
}
datasource db {
provider = "postgresql"
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
}
Create a prisma.config.ts file in the root of your project with the following content:
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: env("DATABASE_URL"),
},
});
:::note
You'll need to install the required packages. If you haven't already, install them using your package manager:
npm install prisma @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg pg dotenv
:::info
If you are using a different database provider (MySQL, SQL Server, SQLite), install the corresponding driver adapter package instead of @prisma/adapter-pg. For more information, see Database drivers.
:::
:::
Create a new branch for your changes:
git checkout -b create-status-field
Update your schema to add the new Status enum and field:
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
status Status @default(Unknown)
}
enum Status {
Unknown
Draft
InProgress
InReview
Published
}
Generate the migration:
npx prisma migrate dev --name add-status-column
Then generate Prisma Client:
npx prisma generate
Create a new TypeScript file for the data migration:
import { PrismaClient } from "../generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import "dotenv/config";
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL,
});
const prisma = new PrismaClient({
adapter,
});
async function main() {
await prisma.$transaction(async (tx) => {
const posts = await tx.post.findMany();
for (const post of posts) {
await tx.post.update({
where: { id: post.id },
data: {
status: post.published ? "Published" : "Unknown",
},
});
}
});
}
main()
.catch(async (e) => {
console.error(e);
process.exit(1);
})
.finally(async () => await prisma.$disconnect());
Add the migration script to your package.json:
{
"scripts": {
"data-migration:add-status-column": "tsx ./prisma/migrations/<migration-timestamp>/data-migration.ts"
}
}
npm run data-migration:add-status-column
Create a new branch for removing the old column:
git checkout -b drop-published-column
Update your schema to remove the published field:
model Post {
id Int @id @default(autoincrement())
title String
content String?
status Status @default(Unknown)
}
enum Status {
Draft
InProgress
InReview
Published
}
Create and run the final migration:
npx prisma migrate dev --name drop-published-column
Then generate Prisma Client:
npx prisma generate
Add the following command to your CI/CD pipeline:
npx prisma migrate deploy
Watch for any errors in your logs and monitor your application's behavior after deployment.
Migration fails due to missing default
Data loss prevention
Transaction rollback
Now that you've completed your first expand and contract migration, you can:
For more information: