@convex-dev/migrations

中文文档
0.3.7发布于 2026/10/10Apache-2.0TypeScriptES Module零依赖近 30 天下载 230.4万

A migrations component for Convex. Define, run, and track your database migrations. Run from a CLI or Convex server function.

原始使用文档
文档目录26 节
  1. Pre-requisite: Convex
  2. Installation
  3. Initialization
  4. Define migrations
  5. Shorthand syntax
  6. Migrating a subset of a table using an index
  7. Running migrations one at a time
  8. Using the Dashboard or CLI
  9. Programmatically
  10. Behavior
  11. Running migrations serially
  12. Using the Dashboard or CLI
  13. Programmatically
  14. Behavior
  15. Operations
  16. Test a migration with dryRun
  17. Restart a migration
  18. Stop a migration
  19. Get the status of migrations
  20. Running migrations as part of a production deploy
  21. Configuration options
  22. Override the internalMutation to apply custom DB behavior
  23. Custom batch size
  24. Parallelizing batches
  25. Shorthand running syntax:
  26. Running migrations synchronously

Convex Stateful Migrations Component

npm version

Define and run migrations, like this one setting a default value for users:

export const setDefaultValue = migrations.define({
  table: "users",
  migrateOne: async (ctx, user) => {
    if (user.optionalField === undefined) {
      await ctx.db.patch(user._id, { optionalField: "default" });
    }
  },
});

You can then run it directly from the CLI or programmatically. See below.

npx convex run migrations:setDefaultValue

Migrations allow you to define functions that run on all documents in a table (or a specified subset). They run in batches asynchronously (online migration).

The component tracks the migrations state so it can avoid running twice, pick up where it left off (in the case of a bug or failure along the way), and expose the migration state in realtime via Convex queries.

See the migration primer post for a conceptual overview of online vs. offline migrations. If your migration is trivial and you're moving fast, also check out lightweight migrations in the dashboard.

Typical steps for doing a migration:

  1. Modify your schema to allow old and new values. Typically this is adding a new optional field or marking a field as optional so it can be deleted. As part of this, update your code to handle both versions.
  2. Define a migration to change the data to the new schema.
  3. Push the migration and schema changes.
  4. Run the migration(s) to completion.
  5. Modify your schema and code to assume the new value. Pushing this change will only succeed once all the data matches the new schema. This is the default behavior for Convex, unless you disable schema validation.

See this Stack post for walkthroughs of common use cases.

Pre-requisite: Convex

You'll need an existing Convex project to use the component. Convex is a hosted backend platform, including a database, serverless functions, and a ton more you can learn about here.

Run npm create convex or follow any of the quickstarts to set one up.

Installation

Install the component package:

npm install @convex-dev/migrations

Create a convex.config.ts file in your app's convex/ folder and install the component by calling use:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import migrations from "@convex-dev/migrations/convex.config.js";

const app = defineApp();
app.use(migrations);

export default app;

Initialization

Examples below are assuming the code is in convex/migrations.ts. This is not a requirement. If you want to use a different file, make sure to change the examples below from internal.migrations.* to your new file name, like internal.myFolder.myMigrationsFile.* or CLI arguments like migrations:* to myFolder/myMigrationsFile:*.

import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api.js";
import schema from "./_generated/schema.js";

export const migrations = new Migrations(components.migrations, { schema });

The schema provides type safety for migration definitions and support for pagination over custom indexes with customRange. As always, database operations in migrations will abide by your schema definition at runtime. Note: if you use custom functions to override internalMutation, see below.

Define migrations

Within the migrateOne function, you can write code to modify a single document in the specified table. Making changes is optional, and you can also read and write to other tables from this function.

export const setDefaultValue = migrations.define({
  table: "myTable",
  migrateOne: async (ctx, doc) => {
    if (doc.optionalField === undefined) {
      await ctx.db.patch(doc._id, { optionalField: "default" });
    }
  },
});

Shorthand syntax

Since the most common migration involves patching each document, if you return an object, it will be applied as a patch automatically.

export const clearField = migrations.define({
  table: "myTable",
  migrateOne: () => ({ optionalField: undefined }),
});
// is equivalent to `await ctx.db.patch(doc._id, { optionalField: undefined })`

Migrating a subset of a table using an index

If you only want to migrate a range of documents, you can avoid processing the whole table by specifying a customRange. You can use any existing index you have on the table, or the built-in by_creation_time index.

import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api.js";
import schema from "./_generated/schema.js";

export const migrations = new Migrations(components.migrations, { schema });

export const validateRequiredField = migrations.define({
  table: "myTable",
  customRange: (query) =>
    query.withIndex("by_requiredField", (q) => q.eq("requiredField", "")),
  migrateOne: async (_ctx, doc) => {
    console.log("Needs fixup: " + doc._id);
    // Shorthand for patching
    return { requiredField: "<unknown>" };
  },
});

Note: to use customRange, you must provide your schema to Migrations.

Running migrations one at a time

Using the Dashboard or CLI

The simplest way to run a migration is to call it directly:

npx convex run migrations:setDefaultValue

Pass --prod to run in production.

Running it from the dashboard:

Dashboard screenshot

You can also define a general-purpose runner that accepts a migration by name:

export const run = migrations.runner();

Then run it with the function name:

npx convex run migrations:run '{fn: "migrations:setDefaultValue"}'

See below for a way to just pass setDefaultValue.

Programmatically

You can also run migrations from other Convex mutations or actions:

await migrations.runOne(ctx, internal.example.setDefaultValue);

Behavior

  • If it is already running it will refuse to start another duplicate worker.
  • If it had previously failed on some batch, it will continue from that batch unless you manually specify cursor or reset.
  • If you provide an explicit cursor (null means to start at the beginning), it will start from there.
  • If you pass reset: true, it will restart the migration from the beginning for all migrations in the group (including any next migrations).
  • If you pass true for dryRun then it will run one batch and then throw, so no changes are committed, and you can see what it would have done. See below This is good for validating it does what you expect.

Running migrations serially

You can run a series of migrations in order. This is useful if some migrations depend on previous ones, or if you keep a running list of all migrations that should run on the next deployment.

Using the Dashboard or CLI

You can pass a list of migrations to runner to have it run a series of migrations instead of just one:

export const runAll = migrations.runner([
  internal.migrations.setDefaultValue,
  internal.migrations.validateRequiredField,
  internal.migrations.convertUnionField,
]);

Then just run:

npx convex run migrations:runAll # migrations:runAll is equivalent to convex/migrations.ts:runAll on the CLI

When calling a migration directly, or with the runner functions, you can pass a "next" argument to run a series of migrations after the first:

npx convex run migrations:setDefaultValue '{next:["migrations:clearField"]}'
# OR
npx convex run migrations:run '{fn: "migrations:setDefaultValue", next:["migrations:clearField"]}'

Programmatically

await migrations.runSerially(ctx, [
  internal.migrations.setDefaultValue,
  internal.migrations.validateRequiredField,
  internal.migrations.convertUnionField,
]);

Behavior

  • If a migration is already in progress when attempted, it will no-op.
  • If a migration had already completed, it will skip it.
  • If a migration had partial progress, it will resume from where it left off.
  • If a migration fails or is canceled, it will not continue on, in case you had some dependencies between the migrations. Call the series again to retry.

Note: if you start multiple serial migrations, the behavior is:

  • If they don't overlap on functions, they will happily run in parallel.
  • If they have a function in common and one completes before the other attempts it, the second will just skip it.
  • If they have a function in common and one is in progress, the second will no-op and not run any further migrations in its series.

Operations

Test a migration with dryRun

Before running a migration that may irreversibly change data, you can validate a batch by passing dryRun to any runner or runOne command:

npx convex run migrations:setDefaultValue '{dryRun: true}'

Restart a migration

Pass reset: true to force a migration to start over from the beginning.

npx convex run migrations:setDefaultValue '{reset: true}'

If you specify next or run a defined series of migrations, this will reset the cursor for all migrations in the group.

npx convex run migrations:runAll '{reset: true}'

You can also pass in any valid cursor to start from. You can find valid cursors in the response of calls to getStatus. This can allow retrying a migration from a known good point as you iterate on the code.

Note: if you're starting a migration from a specific cursor from the CLI or dashboard, do not pass dryRun: false explicitly, leave it undefined.

Stop a migration

You can stop a migration from the CLI or dashboard, calling the component API directly:

npx convex run --component migrations lib:cancel '{name: "migrations:myMigration"}'

Or via migrations.cancel programatically.

await migrations.cancel(ctx, internal.migrations.myMigration);

Get the status of migrations

To see the live status of migrations as they progress, you can query it via the CLI:

npx convex run --component migrations lib:getStatus --watch

The --watch will live-update the status as it changes. Or programmatically:

const status: MigrationStatus[] = await migrations.getStatus(ctx, {
  limit: 10,
});
// or
const status: MigrationStatus[] = await migrations.getStatus(ctx, {
  migrations: [
    internal.migrations.setDefaultValue,
    internal.migrations.validateRequiredField,
    internal.migrations.convertUnionField,
  ],
});

The type is annotated to avoid circular type dependencies, for instance if you are returning the result from a query that is defined in the same file as the referenced migrations.

Running migrations as part of a production deploy

As part of your build and deploy command, you can chain the corresponding npx convex run command, such as:

npx convex deploy --cmd 'npm run build' && npx convex run convex/migrations.ts:runAll --prod

Configuration options

Override the internalMutation to apply custom DB behavior

You can customize which internalMutation implementation the underly migration should use.

This might be important if you use custom functions to intercept database writes to apply validation or trigger operations on changes.

Assuming you define your own internalMutation in convex/functions.ts:

import { internalMutation } from "./functions";
import { Migrations } from "@convex-dev/migrations";
import { components } from "./_generated/api";

export const migrations = new Migrations(components.migrations, {
  internalMutation,
});

See this article for more information on usage and advanced patterns.

Custom batch size

The component will fetch your data in batches of 100, and call your function on each document in a batch. If you want to change the batch size, you can specify it. This can be useful if your documents are large, to avoid running over the transaction limit, or if your documents are updating frequently and you are seeing OCC conflicts while migrating.

export const clearField = migrations.define({
  table: "myTable",
  batchSize: 10,
  migrateOne: () => ({ optionalField: undefined }),
});

You can also override this batch size for an individual invocation:

await migrations.runOne(ctx, internal.migrations.clearField, {
  batchSize: 1,
});

Parallelizing batches

Each batch is processed serially, but within a batch you can have each migrateOne call run in parallel if you pass parallelize: true. If you do so, ensure your callback doesn't assume that each call is isolated. For instance, if each call reads then updates the same counter, then multiple functions in the same batch could read the same counter value, and get off by one. As a result, migrations are run serially by default.

export const clearField = migrations.define({
  table: "myTable",
  parallelize: true,
  migrateOne: () => ({ optionalField: undefined }),
});

Shorthand running syntax:

For those that don't want to type out migrations:myNewMigration every time they run a migration from the CLI or dashboard, especially if you define your migrations elsewhere like ops/db/migrations:myNewMigration, you can configure a prefix:

export const migrations = new Migrations(components.migrations, {
  internalMigration,
  migrationsLocationPrefix: "migrations:",
});

And then just call:

npx convex run migrations:run '{fn: "myNewMutation", next: ["myNextMutation"]}'

Or in code:

await migrations.getStatus(ctx, { migrations: ["myNewMutation"] });
await migrations.cancel(ctx, "myNewMutation");

Running migrations synchronously

If you want to run a migration synchronously from a test or action, you can use runToCompletion. Note that if the action crashes or is canceled, it will not continue migrating in the background.

From an action:

import { components, internal } from "./_generated/api";
import { internalAction } from "./_generated/server";
import { runToCompletion } from "@convex-dev/migrations";

export const myAction = internalAction({
  args: {},
  handler: async (ctx) => {
    //...
    const toRun = internal.example.setDefaultValue;
    await runToCompletion(ctx, components.migrations, toRun);
  },
});

In a test:

import { test } from "vitest";
import { convexTest } from "convex-test";
import component from "@convex-dev/migrations/test";
import { runToCompletion } from "@convex-dev/migrations";
import { components, internal } from "./_generated/api";
import schema from "./schema";

test("test setDefaultValue migration", async () => {
  const t = convexTest(schema);
  // Register the component in the test instance
  component.register(t);

  await t.run(async (ctx) => {
    // Add sample data to migrate
    await ctx.db.insert("myTable", { optionalField: undefined });

    // Run the migration to completion
    const migrationToTest = internal.example.setDefaultValue;
    await runToCompletion(ctx, components.migrations, migrationToTest);

    // Assert that the migration was successful by checking the data
    const docs = await ctx.db.query("myTable").collect();
    expect(docs.every((doc) => doc.optionalField !== undefined)).toBe(true);
  });
});

版本与发布记录

@convex-dev/migrations 共发布 31 个版本,首次发布于 2024/09/21;过去 12 个月发布了 11 个版本。

版本发布日期距今
0.3.7latest2026/10/101 个月前
0.3.62026/07/283 个月前
0.3.52026/05/295 个月前
0.3.5-alpha.1alpha2026/05/215 个月前
0.3.5-alpha.02026/05/215 个月前
0.3.42026/04/256 个月前

关于 @convex-dev/migrations 的常见问题

@convex-dev/migrations 是什么?

A migrations component for Convex. Define, run, and track your database migrations. Run from a CLI or Convex server function.

如何安装 @convex-dev/migrations?

在项目目录运行 npm install @convex-dev/migrations,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:convex(^1.42.0)、convex-helpers(^0.1.115)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@convex-dev/migrations 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@convex-dev/migrations 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@convex-dev/migrations 可以商用吗?

@convex-dev/migrations 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@convex-dev/migrations 的下载量有多少?

根据 npm 官方下载统计,@convex-dev/migrations 在 2026/10/11 – 2026/11/09 这 30 天内被下载 2,303,978 次,日均约 76,799 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@convex-dev/migrations 还在维护吗?

最新版本 0.3.7 发布于 2026/10/10(1 个月前);至今共发布 31 个版本,其中过去 12 个月发布了 11 个;首次发布于 2024/09/21。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。