Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Plan and execute safe Convex schema migrations: add fields, change types, split/merge tables, backfill data.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/migrations-component.md
1# Migrations Component Reference23Complete guide to the4[`@convex-dev/migrations`](https://www.convex.dev/components/migrations)5component for batched, resumable Convex data migrations.67## Installation89```bash10npm install @convex-dev/migrations11```1213## Setup1415```typescript16// convex/convex.config.ts17import { defineApp } from "convex/server";18import migrations from "@convex-dev/migrations/convex.config.js";1920const app = defineApp();21app.use(migrations);22export default app;23```2425```typescript26// convex/migrations.ts27import { Migrations } from "@convex-dev/migrations";28import { components } from "./_generated/api.js";29import { DataModel } from "./_generated/dataModel.js";3031export const migrations = new Migrations<DataModel>(components.migrations);32export const run = migrations.runner();33```3435The `DataModel` type parameter is optional but provides type safety for36migration definitions.3738## Define a Migration3940The `migrateOne` function processes a single document. The component handles41batching and pagination automatically.4243```typescript44// convex/migrations.ts45export const addDefaultRole = migrations.define({46table: "users",47migrateOne: async (ctx, user) => {48if (user.role === undefined) {49await ctx.db.patch(user._id, { role: "user" });50}51},52});53```5455Shorthand: if you return an object, it is applied as a patch automatically.5657```typescript58export const clearDeprecatedField = migrations.define({59table: "users",60migrateOne: () => ({ legacyField: undefined }),61});62```6364## Run a Migration6566From the CLI:6768```bash69# Define a one-off runner in convex/migrations.ts:70# export const runIt = migrations.runner(internal.migrations.addDefaultRole);71npx convex run migrations:runIt7273# Or use the general-purpose runner74npx convex run migrations:run '{"fn": "migrations:addDefaultRole"}'75```7677Programmatically from another Convex function:7879```typescript80await migrations.runOne(ctx, internal.migrations.addDefaultRole);81```8283## Run Multiple Migrations in Order8485```typescript86export const runAll = migrations.runner([87internal.migrations.addDefaultRole,88internal.migrations.clearDeprecatedField,89internal.migrations.normalizeEmails,90]);91```9293```bash94npx convex run migrations:runAll95```9697If one fails, it stops and will not continue to the next. Call it again to retry98from where it left off. Completed migrations are skipped automatically.99100## Dry Run101102Test a migration before committing changes:103104```bash105npx convex run migrations:runIt '{"dryRun": true}'106```107108This runs one batch and then rolls back, so you can see what it would do without109changing any data.110111## Check Migration Status112113```bash114npx convex run --component migrations lib:getStatus --watch115```116117## Cancel a Running Migration118119```bash120npx convex run --component migrations lib:cancel '{"name": "migrations:addDefaultRole"}'121```122123Or programmatically:124125```typescript126await migrations.cancel(ctx, internal.migrations.addDefaultRole);127```128129## Run Migrations on Deploy130131Chain migration execution after deploying:132133```bash134npx convex deploy --cmd 'npm run build' && npx convex run migrations:runAll --prod135```136137## Configuration Options138139### Custom Batch Size140141If documents are large or the table has heavy write traffic, reduce the batch142size to avoid transaction limits or OCC conflicts:143144```typescript145export const migrateHeavyTable = migrations.define({146table: "largeDocuments",147batchSize: 10,148migrateOne: async (ctx, doc) => {149// migration logic150},151});152```153154### Migrate a Subset Using an Index155156Process only matching documents instead of the full table:157158```typescript159export const fixEmptyNames = migrations.define({160table: "users",161customRange: (query) => query.withIndex("by_name", (q) => q.eq("name", "")),162migrateOne: () => ({ name: "<unknown>" }),163});164```165166### Parallelize Within a Batch167168By default each document in a batch is processed serially. Enable parallel169processing if your migration logic does not depend on ordering:170171```typescript172export const clearField = migrations.define({173table: "myTable",174parallelize: true,175migrateOne: () => ({ optionalField: undefined }),176});177```178