---
title: "Migrations"
description: "Online schema migrations over your Convex tables."
canonical_url: "https://nuxt-backend.dev/docs/data/jobs/migrations"
---
# Migrations

> Online schema migrations over your Convex tables.

`setupMigrations(components, options?)` from `nuxt-backend/migrations` returns `{ migrations, run }` — the migrations instance and a generic runner. Define migrations with `migrations.define({ table, migrateOne })`.

```ts [backend/migrations.ts]
import { setupMigrations } from 'nuxt-backend/migrations'
import { components } from './_generated/api'
import schema from './schema'

export const { migrations, run } = setupMigrations(components, { schema })

export const setDefaultStatus = migrations.define({
  table: 'messages',
  migrateOne: async (ctx, doc) => {
    if (doc.status === undefined) {
      await ctx.db.patch(doc._id, { status: 'sent' })
    }
  },
})
```

`migrateOne` runs inside a mutation, one document at a time, a batch per transaction. It can write through `ctx.db` as above, or return a partial document and the component applies the patch — `migrateOne: (_ctx, doc) => (doc.status === undefined ? { status: 'sent' } : undefined)`. A migration is also how you backfill an [aggregate](https://nuxt-backend.dev/docs/data/aggregates) for rows that predate its trigger: `migrateOne: (ctx, doc) => messagesCount.insertIfDoesNotExist(ctx, doc)`.

## Options

<table>
<thead>
  <tr>
    <th>
      Option
    </th>
    
    <th>
      Effect
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        schema
      </code>
    </td>
    
    <td>
      Your database schema. Recommended — it types <code>
        doc
      </code>
      
       in <code>
        migrateOne
      </code>
      
       and enables <code>
        customRange
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        defaultBatchSize
      </code>
    </td>
    
    <td>
      Documents processed per transaction batch (default <code>
        100
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        migrationsLocationPrefix
      </code>
    </td>
    
    <td>
      Prefix added to <code>
        fn
      </code>
      
       names when running from the CLI — with <code>
        'bar/baz:'
      </code>
      
      , a migration in <code>
        backend/bar/baz.ts
      </code>
      
       runs as <code>
        { "fn": "foo" }
      </code>
    </td>
  </tr>
</tbody>
</table>

`migrations.define` also takes `batchSize` (this migration's batch size), `customRange` (narrow the pass to an index range — `q => q.withIndex('userId', q => q.eq('userId', id))` — which needs `schema`) and `parallelize` (process each batch's documents in parallel).

## Running

`run` is the generic runner: an internal mutation that starts — or resumes — any defined migration by name and schedules the batches that follow.

```bash
# Start, or resume where it stopped. A finished migration is a no-op.
npx convex run migrations:run '{ "fn": "migrations:setDefaultStatus" }'

# Dry run: one batch, then roll back — see what it would do without committing.
npx convex run migrations:run '{ "fn": "migrations:setDefaultStatus", "dryRun": true }'

# Restart from the beginning.
npx convex run migrations:run '{ "fn": "migrations:setDefaultStatus", "reset": true }'

# A series, one after another.
npx convex run migrations:run '{ "fn": "migrations:setDefaultStatus", "next": ["migrations:backfillMessagesCount"] }'
```

<table>
<thead>
  <tr>
    <th>
      Argument
    </th>
    
    <th>
      Effect
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        fn
      </code>
    </td>
    
    <td>
      The migration to run, as <code>
        file:export
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cursor
      </code>
    </td>
    
    <td>
      Where to start — <code>
        null
      </code>
      
       for the beginning; omitted, it resumes where the last run stopped
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        batchSize
      </code>
    </td>
    
    <td>
      Batch size for this run — overrides the migration's own
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dryRun
      </code>
    </td>
    
    <td>
      Run one batch and throw, so nothing commits
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        next
      </code>
    </td>
    
    <td>
      Migrations to run after this one, in order — a failure or cancel stops the series
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reset
      </code>
    </td>
    
    <td>
      Restart from the beginning (also resets <code>
        next
      </code>
      
      )
    </td>
  </tr>
</tbody>
</table>

The same instance drives migrations from code: `migrations.runOne(ctx, internal.migrations.setDefaultStatus, { dryRun?, batchSize?, cursor?, reset? })` from a mutation or action, `migrations.runSerially(ctx, [internal.migrations.a, internal.migrations.b])` for a post-deploy series, `migrations.getStatus(ctx, { migrations?, limit? })` for the live state (a query can expose it to a page — the playground's status table does), and `migrations.cancel(ctx, ref)` / `migrations.cancelAll(ctx)` to stop an in-flight run, which resumes when run again without a cursor.

<page-keys>

- Try it live in the playground: [/playground/platform/migrations](https://nuxt-backend.dev/playground/platform/migrations)
- API reference: [/docs/reference/api/migrations](https://nuxt-backend.dev/docs/reference/api/migrations)
- Source: [https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/migrations.ts](https://github.com/qruto/nuxt-backend/blob/main/src/convex/integrations/migrations.ts)

</page-keys>


## Sitemap

See the full [sitemap](https://nuxt-backend.dev/sitemap.md) for all pages.
