.agents/skills/convex-create-component/SKILL.md
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.
npx skillsauth add RinKhimera/NOMAQbanq convex-create-componentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create reusable Convex components with clear boundaries and a small app-facing API.
convex/convex.config.ts, schema.ts, and function files../_generated/server imports, not the app's generated files.app.use(...). If the app does not already have convex/convex.config.ts, create it.components.<name> using ctx.runQuery, ctx.runMutation, or ctx.runAction.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:
convex/components/<componentName>/defineComponent(...) in its own convex.config.tsconvex/convex.config.ts with app.use(...)npx convex dev generate the component's own _generated/ filesA 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 { getAuthUserId } from "@convex-dev/auth/server"
import { v } from "convex/values"
import { components } from "./_generated/api"
import { mutation, query } from "./_generated/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, {
userId,
message: args.message,
})
return null
},
})
export const myUnread = query({
args: {},
handler: async (ctx) => {
const userId = await getAuthUserId(ctx)
if (!userId) throw new Error("Not authenticated")
return await ctx.runQuery(components.notifications.lib.listUnread, {
userId,
})
},
})
Note the reference path shape: a function in convex/components/notifications/lib.ts is called as components.notifications.lib.send from the app.
ctx.auth is not available inside components.process.env.Id types become plain strings in the app-facing ComponentApi.v.id("parentTable") for app-owned tables inside component args or schema, because the component has no access to the app's table namespace.query, mutation, and action from the component's own ./_generated/server, not the app's generated files.convex/http.ts, because components cannot register their own HTTP routes.paginator from convex-helpers instead of built-in .paginate(), because .paginate() does not work across the component boundary.args and returns validators to all public component functions, because the component boundary requires explicit type contracts.// Bad: component code cannot rely on app auth or env
const identity = await ctx.auth.getUserIdentity()
const apiKey = process.env.OPENAI_API_KEY
// Good: the app resolves auth and env, then passes explicit values
const userId = await getAuthUserId(ctx)
if (!userId) throw new Error("Not authenticated")
await ctx.runAction(components.translator.translate, {
userId,
apiKey: process.env.OPENAI_API_KEY,
text: args.text,
})
// Bad: assuming a component function is directly callable by clients
export const send = components.notifications.send
// Good: re-export through an app mutation or query
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, {
userId,
message: args.message,
})
return null
},
})
// Bad: parent app table IDs are not valid component validators
args: {
userId: v.id("users")
}
// Good: treat parent-owned IDs as strings at the boundary
args: {
userId: v.string()
}
For additional patterns including function handles for callbacks, deriving validators from schema, static configuration with a globals table, and class-based client wrappers, see references/advanced-patterns.md.
Try validation in this order:
npx convex codegen --component-dir convex/components/<name>npx convex codegennpx convex devImportant:
CONVEX_DEPLOYMENT is configured../_generated/* imports and app-side components.<name>... references will not typecheck.Read exactly one of these after the user confirms the goal:
references/local-components.mdreferences/packaged-components.mdreferences/hybrid-components.mdOfficial docs: Authoring Components
convex/components/<name>/ (or package layout if publishing)./_generated/serverv.string()args and returns validatorsnpx convex dev and fixed codegen or type issuestools
Sets up Convex authentication with user management, identity mapping, and access control. Use this skill when adding login or signup to a Convex app, configuring Convex Auth, Clerk, WorkOS AuthKit, Auth0, or custom JWT providers, wiring auth.config.ts, protecting queries and mutations with ctx.auth.getUserIdentity(), creating a users table with identity mapping, or setting up role-based access control, even if the user just says "add auth" or "make it require login."
development
Initializes a new Convex project from scratch or adds Convex to an existing app. Use this skill when starting a new project with Convex, scaffolding with npm create convex@latest, adding Convex to an existing React, Next.js, Vue, Svelte, or other frontend, wiring up ConvexProvider, configuring environment variables for the deployment URL, or running npx convex dev for the first time, even if the user just says "set up Convex" or "add a backend."
testing
Audits and optimizes Convex application performance across hot-path reads, write contention, subscription cost, and function limits. Use this skill when a Convex feature is slow or expensive, npx convex insights shows high bytes or documents read, OCC conflict errors or mutation retries appear, subscriptions or UI updates are costly, functions hit execution or transaction limits, or the user mentions performance, latency, read amplification, or invalidation problems in a Convex app.
tools
Plans and executes safe Convex schema and data migrations using the widen-migrate-narrow workflow and the @convex-dev/migrations component. Use this skill when a deployment fails schema validation, existing documents need backfilling, fields need adding or removing or changing type, tables need splitting or merging, or a zero-downtime migration strategy is needed. Also use when the user mentions breaking schema changes, multi-deploy rollouts, or data transformations on existing Convex tables.