#What's the recommended approach for migrations?
5 messages · Page 1 of 1 (latest)
Hi @ivory heath. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
Convex recommends online, asynchronous migrations: keep your app serving traffic, update the schema to allow both old and new data, backfill with a migration, then tighten the schema once the data is consistent. [Intro migrations; Migrations component]
Below are concrete patterns for adding and removing a required field.
Adding a new required field
Goal: add bar: v.string() to table foobar.
1. Make the field optional in the schema
First deploy a schema that allows existing documents without bar:
// schema.ts
foobar: defineTable({
foo: v.string(),
bar: v.optional(v.string()),
})
This lets you deploy even though existing docs don’t have bar. [Migrating with mutations]
Update your app code to:
- Write
barfor all new/updated documents. - Read
bardefensively (it may beundefineduntil backfill is done).
2. Backfill existing documents with a migration
Use the Migrations component (or your own mutation) to set a default for all existing docs where bar is missing. [Migrations component; Data migrations]
Example with the component:
// convex/migrations.ts
import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api";
import type { DataModel } from "./_generated/dataModel";
export const migrations = new Migrations<DataModel>(components.migrations);
export const setDefaultBar = migrations.define({
table: "foobar",
migrateOne: async (ctx, doc) => {
if (doc.bar === undefined) {
await ctx.db.patch(doc._id, { bar: "something" });
}
},
});
Run this migration to completion. [[Migrations component](<https://www.convex.dev/components/migrations>)]
### 3. Make the field required
Once every document has `bar` set, change the schema to required:
```ts
// schema.ts
foobar: defineTable({
foo: v.string(),
bar: v.string(), // now required
})
Convex will only accept this schema once the data actually conforms, so your generated TypeScript types stay in sync with reality. [Migrating with mutations]
Removing a required field
Goal: remove isPro: v.boolean() from teams.
1. Make the field optional
Relax the schema first:
// schema.ts
teams: defineTable({
// ...
isPro: v.optional(v.boolean()),
})
Convex won’t let you remove a field that still has data; making it optional is the first step. [Best practices]
Update your code to stop relying on isPro (or treat it as optional / deprecated).
2. Clear the field with a migration
Run a migration that sets isPro to undefined everywhere. [Data migrations]
export const removeIsPro = migrations.define({
table: "teams",
migrateOne: async (ctx, team) => {
if (team.isPro !== undefined) {
await ctx.db.patch(team._id, { isPro: undefined });
}
},
});
3. Remove the field from the schema
After the data is cleared, you can safely delete the field from the schema:
// schema.ts
teams: defineTable({
// ... no isPro field
})
Convex will now accept this schema because no documents still have isPro. [Best practices]
Tools you can use
- Migrations component for tracked, resumable online migrations. [Migrations component]
- Dashboard bulk edit for small, “lightweight” changes without writing code. [Lightweight migrations]