> ## Documentation Index
> Fetch the complete documentation index at: https://vlyai-1c28d863.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrations

> How vly handles schema evolution. Adding fields, renaming, removing, and backfilling existing data.

When you change the schema (add a field, rename a table, change a type), vly handles the migration automatically. This page covers what happens and when to intervene.

## What gets migrated automatically

<CardGroup cols={2}>
  <Card title="Adding a field" icon="plus">
    New optional field — instant. New required field with default — vly backfills.
  </Card>

  <Card title="Renaming a field" icon="text-cursor-input">
    vly copies values from old field to new, drops old. Done in one transaction per row.
  </Card>

  <Card title="Removing a field" icon="trash">
    Field disappears from the validator; data on existing rows is no longer accessible (still on disk; cleaned up over time).
  </Card>

  <Card title="Adding an index" icon="zap">
    Index built in the background; queries use the new index once it's ready.
  </Card>
</CardGroup>

## What needs your attention

<CardGroup cols={2}>
  <Card title="Adding a required field with no default" icon="alert-triangle">
    vly will refuse the migration. Either provide a default in the schema, or run a one-shot mutation to populate the field, then make it required.
  </Card>

  <Card title="Changing a field type" icon="alert-triangle">
    Number → String etc. — vly asks you for a conversion function (`(oldValue) => newValue`).
  </Card>

  <Card title="Removing a table" icon="alert-triangle">
    Lossy. vly confirms with a clear warning and lists how many rows will become inaccessible.
  </Card>
</CardGroup>

## Manual one-shot migrations

For complex transformations:

```typescript convex/migrations/2026-04-18-normalize-emails.ts theme={null}
import { internalMutation } from "../_generated/server";

export default internalMutation({
  args: {},
  handler: async (ctx) => {
    const users = await ctx.db.query("users").collect();
    for (const user of users) {
      const normalized = user.email.toLowerCase().trim();
      if (normalized !== user.email) {
        await ctx.db.patch(user._id, { email: normalized });
      }
    }
  },
});
```

Run via `vly convex run migrations.2026-04-18-normalize-emails`.

## Tips

<Tip>
  **Schema changes are part of [version history](/building/version-history).** Roll back the code; the schema stays. For destructive changes, plan a separate cleanup.
</Tip>

<Tip>
  **Stage schema changes separately from feature changes when possible.** Easier to roll back independently.
</Tip>

## Related

<CardGroup cols={3}>
  <Card title="Schema" icon="database" href="/convex/schema">
    Schema definition.
  </Card>

  <Card title="Version history" icon="history" href="/building/version-history">
    Roll back caveats.
  </Card>

  <Card title="Background jobs" icon="cog" href="/features/data/background-jobs">
    For very long migrations.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.