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);32```3334The `DataModel` type parameter is optional but provides type safety for35migration definitions.3637## Define a Migration3839The `migrateOne` function processes a single document. The component handles40batching and pagination automatically.4142```typescript43// convex/migrations.ts44export const addDefaultRole = migrations.define({45table: "users",46migrateOne: async (ctx, user) => {47if (user.role === undefined) {48await ctx.db.patch(user._id, { role: "user" });49}50},51});52```5354Shorthand: if you return an object, it is applied as a patch automatically.5556```typescript57export const clearDeprecatedField = migrations.define({58table: "users",59migrateOne: () => ({ legacyField: undefined }),60});61```6263## Run a Migration6465From the CLI:6667```bash68npx convex run migrations:addDefaultRole6970# Pass --prod to run in production.71npx convex run migrations:addDefaultRole --prod72```7374The migration exported by `migrations.define` is directly callable from the CLI75or dashboard. You do not need a separate one-off runner for normal single76migrations.7778If you want a general-purpose runner that accepts a migration name, define one:7980```typescript81export const run = migrations.runner();82```8384Then call it with the full function name:8586```bash87npx convex run migrations:run '{"fn": "migrations:addDefaultRole"}'88```8990Programmatically from another Convex function:9192```typescript93await migrations.runOne(ctx, internal.migrations.addDefaultRole);94```9596## Run Multiple Migrations in Order9798For a short ad hoc series, pass `next` when starting the first migration:99100```bash101npx convex run migrations:addDefaultRole '{"next":["migrations:clearDeprecatedField","migrations:normalizeEmails"]}'102```103104For a reusable series, define a runner:105106```typescript107export const runAll = migrations.runner([108internal.migrations.addDefaultRole,109internal.migrations.clearDeprecatedField,110internal.migrations.normalizeEmails,111]);112```113114```bash115npx convex run migrations:runAll116```117118If one fails, it stops and will not continue to the next. Call it again to retry119from where it left off. Completed migrations are skipped automatically.120121Programmatically from another Convex function:122123```typescript124await migrations.runSerially(ctx, [125internal.migrations.addDefaultRole,126internal.migrations.clearDeprecatedField,127internal.migrations.normalizeEmails,128]);129```130131## Dry Run132133Test a migration before committing changes:134135```bash136npx convex run migrations:addDefaultRole '{"dryRun": true}'137```138139This runs one batch and then rolls back, so you can see what it would do without140changing any data.141142## Restart a Migration143144Pass `reset: true` to restart a migration from the beginning:145146```bash147npx convex run migrations:addDefaultRole '{"reset": true}'148```149150If you specify `next` or run a defined series, `reset: true` resets the cursor151for all migrations in the group.152153## Check Migration Status154155```bash156npx convex run --component migrations lib:getStatus --watch157```158159## Cancel a Running Migration160161```bash162npx convex run --component migrations lib:cancel '{"name": "migrations:addDefaultRole"}'163```164165Or programmatically:166167```typescript168await migrations.cancel(ctx, internal.migrations.addDefaultRole);169```170171## Run Migrations on Deploy172173Chain migration execution after deploying:174175```bash176npx convex deploy --cmd 'npm run build' && npx convex run migrations:runAll --prod177```178179## Configuration Options180181### Custom Batch Size182183If documents are large or the table has heavy write traffic, reduce the batch184size to avoid transaction limits or OCC conflicts:185186```typescript187export const migrateHeavyTable = migrations.define({188table: "largeDocuments",189batchSize: 10,190migrateOne: async (ctx, doc) => {191// migration logic192},193});194```195196### Migrate a Subset Using an Index197198Process only matching documents instead of the full table:199200```typescript201export const fixEmptyNames = migrations.define({202table: "users",203customRange: (query) => query.withIndex("by_name", (q) => q.eq("name", "")),204migrateOne: () => ({ name: "<unknown>" }),205});206```207208### Parallelize Within a Batch209210By default each document in a batch is processed serially. Enable parallel211processing if your migration logic does not depend on ordering:212213```typescript214export const clearField = migrations.define({215table: "myTable",216parallelize: true,217migrateOne: () => ({ optionalField: undefined }),218});219```220