Schema migration strategies for evolving applications including adding new fields, backfilling data, removing deprecated fields, index migrations, and zero-downtime migration patterns
No setup needed. Let our cloud agents run this skill for you.
Select Provider
Select Model
Claude Sonnet 4.5
$0.20/task
Best for coding tasks
Environment setup included
Convex Migrations
Evolve your Convex database schema safely with patterns for adding fields, backfilling data, removing deprecated fields, and maintaining zero-downtime deployments.
Documentation Sources
Before implementing, do not assume; fetch the latest documentation:
Convex handles schema evolution differently than traditional databases:
No explicit migration files or commands
Schema changes deploy instantly with npx convex dev
Existing data is not automatically transformed
Use optional fields and backfill mutations for safe migrations
Adding New Fields
Start with optional fields, then backfill:
// Step 1: Add optional field to schema// convex/schema.tsimport { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ users: defineTable({ name: v.string(), email: v.string(), // New field - start as optional avatarUrl: v.optional(v.string
// Step 2: Update code to handle both cases// convex/users.tsimport { query } from "./_generated/server";import { v } from "convex/values";export const getUser = query({ args: { userId: v.id("users") }, returns: v.union( v.
// Step 3: Backfill existing documents// convex/migrations.tsimport { internalMutation } from "./_generated/server";import { internal } from "./_generated/api";import { v } from "convex/values";const
// Step 4: After backfill completes, make field required// convex/schema.tsexport default defineSchema({ users: defineTable({ name: v.string(), email: v.string(), avatarUrl: v.string(), // Now required }),});
Removing Fields
Remove field usage before removing from schema:
// Step 1: Stop using the field in queries and mutations// Mark as deprecated in code comments// Step 2: Remove field from schema (make optional first if needed)// convex/schema.tsexport default defineSchema({ posts: defineTable({ title: v.string(), content: v.string(),
Renaming Fields
Renaming requires copying data to new field, then removing old:
// Step 1: Add new field as optional// convex/schema.tsexport default defineSchema({ users: defineTable({ userName: v.string(), // Old field displayName: v.
Adding Indexes
Add indexes before using them in queries:
// Step 1: Add index to schema// convex/schema.tsexport default defineSchema({ posts: defineTable({ title: v.string(), authorId: v.id("users"), publishedAt: v.optional(v.number()),
Changing Field Types
Type changes require careful migration:
// Example: Change from string to number for a "priority" field// Step 1: Add new field with new type// convex/schema.tsexport default defineSchema({ tasks: defineTable
Migration Runner Pattern
Create a reusable migration system:
// convex/schema.tsimport { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ migrations: defineTable({ name: v.string(), startedAt: v.number(), completedAt: v.optional(v.number()),
// convex/migrations.tsimport
// convex/migrations/addUserTimestamps.tsimport { internalMutation } from "../_generated/server";import { internal }
Examples
Schema with Migration Support
// convex/schema.tsimport { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({
Best Practices
Never run npx convex deploy unless explicitly instructed
Never run any git commands unless explicitly instructed
Always start with optional fields when adding new data
Backfill data in batches to avoid timeouts
Test migrations on development before production
Keep track of completed migrations to avoid re-running
Update code to handle both old and new data during transition
Remove deprecated fields only after all code stops using them
Use pagination for large datasets
Add appropriate indexes before running queries on new fields
Common Pitfalls
Making new fields required immediately - Breaks existing documents
Not handling undefined values - Causes runtime errors
Large batch sizes - Causes function timeouts
Forgetting to update indexes - Queries fail or perform poorly
Running migrations without tracking - May run multiple times
Removing fields before code update - Breaks existing functionality
Not testing on development - Production data issues