
Convex Migration Helper
Plan and execute Convex schema and data migrations without breaking live readers or writers.
Overview
Convex Migration Helper is an agent skill most often used in Operate (also Build) that plans and runs safe Convex schema and data migrations using widen-migrate-narrow and verification steps.
Install
npx skills add https://github.com/get-convex/agent-skills --skill convex-migration-helperWhat is this skill?
- Widen-migrate-narrow workflow for adding required fields and tightening types
- Reference patterns for indexes, table splits, and migration function definitions
- Zero-downtime deploy sequencing across schema and backfill steps
- Verification techniques after data migrations complete
Adoption & trust: 61.9k installs on skills.sh; 31 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need to change Convex validators or document shape but cannot afford broken queries or half-migrated rows in live data.
Who is it for?
Solo builders shipping Convex apps who must evolve production schemas with explicit backfill and deploy ordering.
Skip if: Greenfield tables with no existing data, or teams that only need generic SQL migration tools outside Convex.
When should I use this skill?
You need to identify the schema change, existing data shape, and widen-migrate-narrow path before editing Convex schema or migration code.
What do I get? / Deliverables
You get an ordered migration path—schema deploys, backfill jobs, and final tighten step—before the agent edits migration code or schema files.
- Migration plan with deploy order
- Schema and migration function edits
- Post-migration verification checklist
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Canonical shelf is operate/infra because migrations are production schema evolution with zero-downtime discipline, even though you often author them during build. Infra covers durable data-shape changes, backfills, and deploy sequencing—not one-off app logic edits.
Where it fits
Design optional role field and index before first production users land.
Sequence pre-launch backfill so launch deploy does not reject legacy documents.
Run a two-deploy migration to tighten a union validator after backfill completes.
Split a bloated table into a new table with a verified copy migration.
How it compares
Convex-native migration playbook, not ad-hoc one-shot patches without deploy sequencing.
Common Questions / FAQ
Who is convex-migration-helper for?
Indie and solo developers on Convex who own schema changes and need agent-guided migration plans aligned with Convex validators and deploys.
When should I use convex-migration-helper?
During Build when designing new fields and indexes, and during Operate when rolling those changes to environments with existing documents—especially before making required fields or index swaps.
Is convex-migration-helper safe to install?
Treat it as procedural guidance for your repo; review the Security Audits panel on this Prism page before granting broad filesystem or deploy permissions to any agent.
SKILL.md
READMESKILL.md - Convex Migration Helper
interface: display_name: "Convex Migration Helper" short_description: "Plan and run safe Convex schema and data migrations." icon_small: "./assets/icon.svg" icon_large: "./assets/icon.svg" brand_color: "#8B5CF6" default_prompt: "Help me plan and execute this Convex migration safely. Start by identifying the schema change, the existing data shape, and the widen-migrate-narrow path before making edits." policy: allow_implicit_invocation: true <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"> <path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"/> </svg> # Migration Patterns Reference Common migration patterns, zero-downtime strategies, and verification techniques for Convex schema and data migrations. ## Adding a Required Field ```typescript // Deploy 1: Schema allows both states users: defineTable({ name: v.string(), role: v.optional(v.union(v.literal("user"), v.literal("admin"))), }).index("by_role", ["role"]); // Migration: backfill the field export const addDefaultRole = migrations.define({ table: "users", migrateOne: async (ctx, user) => { if (user.role === undefined) { await ctx.db.patch(user._id, { role: "user" }); } }, }); // Deploy 2: After migration completes, make it required users: defineTable({ name: v.string(), role: v.union(v.literal("user"), v.literal("admin")), }); ``` ## Deleting a Field Mark the field optional first, migrate data to remove it, then remove from schema: ```typescript // Deploy 1: Make optional // isPro: v.boolean() --> isPro: v.optional(v.boolean()) // Migration export const removeIsPro = migrations.define({ table: "teams", migrateOne: async (ctx, team) => { if (team.isPro !== undefined) { await ctx.db.patch(team._id, { isPro: undefined }); } }, }); // Deploy 2: Remove isPro from schema entirely ``` ## Changing a Field Type Prefer creating a new field. You can combine adding and deleting in one migration: ```typescript // Deploy 1: Add new field, keep old field optional // isPro: v.boolean() --> isPro: v.optional(v.boolean()), plan: v.optional(...) // Migration: convert old field to new field export const convertToEnum = migrations.define({ table: "teams", migrateOne: async (ctx, team) => { if (team.plan === undefined) { await ctx.db.patch(team._id, { plan: team.isPro ? "pro" : "basic", isPro: undefined, }); } }, }); // Deploy 2: Remove isPro from schema, make plan required ``` ## Splitting Nested Data Into a Separate Table ```typescript export const extractPreferences = migrations.define({ table: "users", migrateOne: async (ctx, user) => { if (user.preferences === undefined) return; const existing = await ctx.db .query("userPreferences") .withIndex("by_user", (q) => q.eq("userId", user._id)) .first(); if (!existing) { await ctx.db.insert("userPreferences", { userId: user._id, ...user.preferences, }); } await ctx.db.patch(user._id, { preferences: undefined }); }, }); ``` Make sure your code is already writing to the new `userPreferences` table for new users before running this migration, so you don't miss documents created during the migration window. ## Cleaning Up Orphaned Documents ```typescript export const deleteOrphanedEmbeddings = migrations.define({ table: "embeddings", migrateOne: async (ctx, doc) => { const chunk = await ctx.db .query("chunks") .withIndex("by_embedding", (q) => q.eq("embeddingId", doc._id)) .first(); if (!chunk) { await ctx.db.delete(doc._id); } }, }); ``` ## Zero-Downtime Strategies During the migration window, your app must handle both old