Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Design and build reusable Convex components with isolated tables, backend functions, and app-facing wrappers.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: convex-create-component3description:4Builds reusable Convex components with isolated tables and app-facing APIs.5Use for new components, reusable backend modules, integrations, or component6boundary work.7---89# Convex Create Component1011Create reusable Convex components with clear boundaries and a small app-facing12API.1314## When to Use1516- Creating a new Convex component in an existing app17- Extracting reusable backend logic into a component18- Building a third-party integration that should own its own tables and19workflows20- Packaging Convex functionality for reuse across multiple apps2122## When Not to Use2324- One-off business logic that belongs in the main app25- Thin utilities that do not need Convex tables or functions26- App-level orchestration that should stay in `convex/`27- Cases where a normal TypeScript library is enough2829## Workflow30311. Ask the user what they are building and what the end goal is. If the repo32already makes the answer obvious, say so and confirm before proceeding.332. Choose the shape using the decision tree below and read the matching34reference file.353. Decide whether a component is justified. Prefer normal app code or a regular36library if the feature does not need isolated tables, backend functions, or37reusable persistent state.384. Make a short plan for:39- what tables the component owns40- what public functions it exposes41- what data must be passed in from the app (auth, env vars, parent IDs)42- what stays in the app as wrappers or HTTP mounts435. Create the component structure with `convex.config.ts`, `schema.ts`, and44function files.456. Implement functions using the component's own `./_generated/server` imports,46not the app's generated files.477. Wire the component into the app with `app.use(...)`. If the app does not48already have `convex/convex.config.ts`, create it.498. Call the component from the app through `components.<name>` using50`ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction`.519. If React clients, HTTP callers, or public APIs need access, create wrapper52functions in the app instead of exposing component functions directly.5310. Run `npx convex dev` and fix codegen, type, or boundary issues before54finishing.5556## Choose the Shape5758Ask the user, then pick one path:5960| Goal | Shape | Reference |61| ------------------------------------------------- | ---------------- | ----------------------------------- |62| Component for this app only | Local | `references/local-components.md` |63| Publish or share across apps | Packaged | `references/packaged-components.md` |64| User explicitly needs local + shared library code | Hybrid | `references/hybrid-components.md` |65| Not sure | Default to local | `references/local-components.md` |6667Read exactly one reference file before proceeding.6869## Default Approach7071Unless the user explicitly wants an npm package, default to a local component:7273- Put it under `convex/components/<componentName>/`74- Define it with `defineComponent(...)` in its own `convex.config.ts`75- Install it from the app's `convex/convex.config.ts` with `app.use(...)`76- Let `npx convex dev` generate the component's own `_generated/` files7778## Component Skeleton7980A minimal local component with a table and two functions, plus the app wiring.8182```ts83// convex/components/notifications/convex.config.ts84import { defineComponent } from "convex/server";8586export default defineComponent("notifications");87```8889```ts90// convex/components/notifications/schema.ts91import { defineSchema, defineTable } from "convex/server";92import { v } from "convex/values";9394export default defineSchema({95notifications: defineTable({96userId: v.string(),97message: v.string(),98read: v.boolean(),99}).index("by_user", ["userId"]),100});101```102103```ts104// convex/components/notifications/lib.ts105import { v } from "convex/values";106import { mutation, query } from "./_generated/server.js";107108export const send = mutation({109args: { userId: v.string(), message: v.string() },110returns: v.id("notifications"),111handler: async (ctx, args) => {112return await ctx.db.insert("notifications", {113userId: args.userId,114message: args.message,115read: false,116});117},118});119120export const listUnread = query({121args: { userId: v.string() },122returns: v.array(123v.object({124_id: v.id("notifications"),125_creationTime: v.number(),126userId: v.string(),127message: v.string(),128read: v.boolean(),129}),130),131handler: async (ctx, args) => {132return await ctx.db133.query("notifications")134.withIndex("by_user", (q) => q.eq("userId", args.userId))135.filter((q) => q.eq(q.field("read"), false))136.collect();137},138});139```140141```ts142// convex/convex.config.ts143import { defineApp } from "convex/server";144import notifications from "./components/notifications/convex.config.js";145146const app = defineApp();147app.use(notifications);148149export default app;150```151152```ts153// convex/notifications.ts (app-side wrapper)154import { v } from "convex/values";155import { mutation, query } from "./_generated/server";156import { components } from "./_generated/api";157import { getAuthUserId } from "@convex-dev/auth/server";158159export const sendNotification = mutation({160args: { message: v.string() },161returns: v.null(),162handler: async (ctx, args) => {163const userId = await getAuthUserId(ctx);164if (!userId) throw new Error("Not authenticated");165166await ctx.runMutation(components.notifications.lib.send, {167userId,168message: args.message,169});170return null;171},172});173174export const myUnread = query({175args: {},176handler: async (ctx) => {177const userId = await getAuthUserId(ctx);178if (!userId) throw new Error("Not authenticated");179180return await ctx.runQuery(components.notifications.lib.listUnread, {181userId,182});183},184});185```186187Note the reference path shape: a function in188`convex/components/notifications/lib.ts` is called as189`components.notifications.lib.send` from the app.190191## Critical Rules192193- Keep authentication in the app, because `ctx.auth` is not available inside194components.195- Keep environment access in the app, because component functions cannot read196`process.env`.197- Pass parent app IDs across the boundary as strings, because `Id` types become198plain strings in the app-facing `ComponentApi`.199- Do not use `v.id("parentTable")` for app-owned tables inside component args or200schema, because the component has no access to the app's table namespace.201- Import `query`, `mutation`, and `action` from the component's own202`./_generated/server`, not the app's generated files.203- Do not expose component functions directly to clients. Create app wrappers204when client access is needed, because components are internal and need205auth/env wiring the app provides.206- If the component defines HTTP handlers, mount the routes in the app's207`convex/http.ts`, because components cannot register their own HTTP routes.208- If the component needs pagination, use `paginator` from `convex-helpers`209instead of built-in `.paginate()`, because `.paginate()` does not work across210the component boundary.211- Add `args` and `returns` validators to all public component functions, because212the component boundary requires explicit type contracts.213214## Patterns215216### Authentication and environment access217218```ts219// Bad: component code cannot rely on app auth or env220const identity = await ctx.auth.getUserIdentity();221const apiKey = process.env.OPENAI_API_KEY;222```223224```ts225// Good: the app resolves auth and env, then passes explicit values226const userId = await getAuthUserId(ctx);227if (!userId) throw new Error("Not authenticated");228229await ctx.runAction(components.translator.translate, {230userId,231apiKey: process.env.OPENAI_API_KEY,232text: args.text,233});234```235236### Client-facing API237238```ts239// Bad: assuming a component function is directly callable by clients240export const send = components.notifications.send;241```242243```ts244// Good: re-export through an app mutation or query245export const sendNotification = mutation({246args: { message: v.string() },247returns: v.null(),248handler: async (ctx, args) => {249const userId = await getAuthUserId(ctx);250if (!userId) throw new Error("Not authenticated");251252await ctx.runMutation(components.notifications.lib.send, {253userId,254message: args.message,255});256return null;257},258});259```260261### IDs across the boundary262263```ts264// Bad: parent app table IDs are not valid component validators265args: {266userId: v.id("users");267}268```269270```ts271// Good: treat parent-owned IDs as strings at the boundary272args: {273userId: v.string();274}275```276277### Advanced Patterns278279For additional patterns including function handles for callbacks, deriving280validators from schema, static configuration with a globals table, and281class-based client wrappers, see `references/advanced-patterns.md`.282283## Validation284285Try validation in this order:2862871. `npx convex codegen --component-dir convex/components/<name>`2882. `npx convex codegen`2893. `npx convex dev`290291Important:292293- Fresh repos may fail these commands until `CONVEX_DEPLOYMENT` is configured.294- Until codegen runs, component-local `./_generated/*` imports and app-side295`components.<name>...` references will not typecheck.296- If validation blocks on Convex login or deployment setup, stop and ask the297user for that exact step instead of guessing.298299## Reference Files300301Read exactly one of these after the user confirms the goal:302303- `references/local-components.md`304- `references/packaged-components.md`305- `references/hybrid-components.md`306307Official docs:308[Authoring Components](https://docs.convex.dev/components/authoring)309310## Checklist311312- [ ] Asked the user what they want to build and confirmed the shape313- [ ] Read the matching reference file314- [ ] Confirmed a component is the right abstraction315- [ ] Planned tables, public API, boundaries, and app wrappers316- [ ] Component lives under `convex/components/<name>/` (or package layout if317publishing)318- [ ] Component imports from its own `./_generated/server`319- [ ] Auth, env access, and HTTP routes stay in the app320- [ ] Parent app IDs cross the boundary as `v.string()`321- [ ] Public functions have `args` and `returns` validators322- [ ] Ran `npx convex dev` and fixed codegen or type issues323