convex-migration-helpe…
Safely migrate Convex schemas and data when making breaking changes.
Create reusable Convex components with clear boundaries and a small app-facing API.
$ npx -y skills add get-convex/convex-backend --skill convex-create-component --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/convex-create-componentContext preview
The summary Claude sees to decide when to auto-load this skill.
Create reusable Convex components with clear boundaries and a small app-facing API.
name: convex-create-component description: Designs and builds Convex components with isolated tables, clear boundaries, and app-facing wrappers. Use this skill when creating a new Convex component, extracting reusable backend logic into a component, building a third-party integration that owns its own tables, packaging Convex functionality for reuse, or when the user mentions defineComponent, app.use, ComponentApi, ctx.runQuery/runMutation across component boundaries, or wants to separate concerns into isolated Convex modules.
Create reusable Convex components with clear boundaries and a small app-facing API.
workflows
1. Ask the user what they are building and what the end goal is. If the repo already makes the answer obvious, say so and confirm before proceeding. 2. Choose the shape using the decision tree below and read the matching reference file. 3. Decide whether a component is justified. Prefer normal app code or a regular library if the feature does not need isolated tables, backend functions, or reusable persistent state. 4. Make a short plan for:
5. Create the component structure with `convex.config.ts`, `schema.ts`, and function files. 6. Implement functions using the component's own `./_generated/server` imports, not the app's generated files. 7. Wire the component into the app with `app.use(...)`. If the app does not already have `convex/convex.config.ts`, create it. 8. Call the component from the app through `components.<name>` using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction`. 9. If React clients, HTTP callers, or public APIs need access, create wrapper functions in the app instead of exposing component functions directly. 10. Run `npx convex dev` and fix codegen, type, or boundary issues before finishing.
Ask the user, then pick one path:
| Goal | Shape | Reference | | ------------------------------------------------- | ---------------- | ----------------------------------- | | Component for this app only | Local | `references/local-components.md` | | Publish or share across apps | Packaged | `references/packaged-components.md` | | User explicitly needs local + shared library code | Hybrid | `references/hybrid-components.md` | | Not sure | Default to local | `references/local-components.md` |
Read exactly one reference file before proceeding.
Unless the user explicitly wants an npm package, default to a local component:
A minimal local component with a table and two functions, plus the app wiring.
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
notifications: defineTable({
userId: v.string(),
message: v.string(),
read: v.boolean(),
}).index("by_user", ["userId"]),
});// convex/components/notifications/lib.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";
export const send = mutation({
args: { userId: v.string(), message: v.string() },
returns: v.id("notifications"),
handler: async (ctx, args) => {
return await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
read: false,
});
},
});
export const listUnread = query({
args: { userId: v.string() },
returns: v.array(
v.object({
_id: v.id("notifications"),
_creationTime: v.number(),
userId: v.string(),
message: v.string(),
read: v.boolean(),
}),
),
handler: async (ctx, args) => {
return await ctx.db
.query("notifications")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.filter((q) => q.eq(q.field("read"), false))
.collect();
},
});// convex/convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config.js";
const app = defineApp();
app.use(notifications);
export default app;// convex/notifications.ts (app-side wrapper)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
export const sendNotification = mutation({
args: { message: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Not authenticated");
await ctx.runMutation(components.notifications.lib.send, {Repo: get-convex/convex-backend
Safely migrate Convex schemas and data when making breaking changes.
Diagnose and fix performance problems in Convex applications, one problem class at a time.
Implement secure authentication in Convex with user management and access control.