
convex-component-authoring
Creates reusable Convex components with defineComponent, a clean client wrapper, their own schema, and an npm publish setup. Use when extracting a feature into a package, building something for the Convex component directory, or when a component's functions are not showing up in the parent app.
- Installs
- 1.1K
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/waynesutton/builder-skills --skill convex-component-authoring- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads convex-component-authoring automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Convex component authoring
Produces a self contained component: its own convex.config.ts, schema, functions, and a typed client wrapper the parent app calls. The one rule: a component cannot read the parent's tables, ctx.auth, or process.env. Whatever it needs, the app passes in as arguments.
When to reach for this
- Extracting a feature (counters, rate limits, audit logs, notifications) into a package
- Building something for the Convex component directory
- A component's functions are missing from
components.<name>in the parent app - A third party integration should own its own tables and background jobs
- The same backend module is needed in several apps
Component or helper function
| Need | Use |
|---|---|
| Shared logic over the app's own tables | Plain TypeScript helper in convex/lib/ |
| Its own tables with a schema the app cannot touch | Component |
| Isolated functions that run in their own transaction | Component |
| Reuse across apps with one install line | Component |
| A React hook or client utility only | npm library, no component |
If the feature does not need persistent state behind an API boundary, a helper function is less work and easier to type.
Folder layout
my-component/
package.json
src/
component/
convex.config.ts # defineComponent("counter")
schema.ts # tables only this component can see
public.ts # queries, mutations, actions
_generated/ # created by npx convex codegen
client/
index.ts # wrapper class the app imports
example/
convex/
convex.config.ts # app.use(counter) for local testing
For a local only component, put the component/ contents at convex/components/<name>/ and skip client/ and package.json.
Define the component
// src/component/convex.config.ts
import { defineComponent } from "convex/server";
const component = defineComponent("counter");
export default component;
// src/component/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
counters: defineTable({
name: v.string(),
value: v.number(),
}).index("by_name", ["name"]),
});
Functions import query and mutation from the component's own _generated/server, never from the app's. Every public function needs args and returns validators; without them the parent sees any.
// src/component/public.ts
import { v } from "convex/values";
import { mutation, query } from "./_generated/server.js";
export const add = mutation({
args: { name: v.string(), amount: v.number() },
returns: v.number(),
handler: async (ctx, args) => {
const existing = await ctx.db
.query("counters")
.withIndex("by_name", (q) => q.eq("name", args.name))
.unique();
if (!existing) {
await ctx.db.insert("counters", { name: args.name, value: args.amount });
return args.amount;
}
const value = existing.value + args.amount;
await ctx.db.patch(existing._id, { value });
return value;
},
});
export const get = query({
args: { name: v.string() },
returns: v.number(),
handler: async (ctx, args) => {
const counter = await ctx.db
.query("counters")
.withIndex("by_name", (q) => q.eq("name", args.name))
.unique();
return counter?.value ?? 0;
},
});
Register it in the parent
// convex/convex.config.ts (parent app)
import { defineApp } from "convex/server";
import counter from "@acme/counter/convex.config.js";
// local component: import counter from "./components/counter/convex.config.js";
const app = defineApp();
app.use(counter);
// A second instance under a different name:
// app.use(counter, { name: "pageViews" });
export default app;
Run npx convex dev. It generates components.counter in the app's convex/_generated/api and the component's own _generated/ folder. The reference path mirrors the file: a function in public.ts is components.counter.public.add.
Component functions arrive in the parent as internal references. Call them with ctx.runQuery and ctx.runMutation; clients cannot hit them directly.
Client wrapper class
The wrapper runs in the app's environment, so it can read ctx.auth and process.env and pass what the component needs. It takes the component reference first and options second.
// src/client/index.ts
import type {
GenericDataModel,
GenericMutationCtx,
GenericQueryCtx,
} from "convex/server";
import type { ComponentApi } from "../component/_generated/component.js";
// Pick only the capabilities each method needs so any ctx with runQuery works
type QueryCtx = Pick<GenericQueryCtx<GenericDataModel>, "runQuery">;
type MutationCtx = Pick<GenericMutationCtx<GenericDataModel>, "runMutation">;
export class Counter {
constructor(
public component: ComponentApi,
private options: { prefix?: string } = {},
) {}
private key(name: string): string {
return this.options.prefix ? `${this.options.prefix}:${name}` : name;
}
async add(ctx: MutationCtx, name: string, amount = 1): Promise<number> {
return await ctx.runMutation(this.component.public.add, {
name: this.key(name),
amount,
});
}
async get(ctx: QueryCtx, name: string): Promise<number> {
return await ctx.runQuery(this.component.public.get, { name: this.key(name) });
}
}
// convex/counter.ts (parent app)
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";
import { components } from "./_generated/api";
import { Counter } from "@acme/counter";
const counter = new Counter(components.counter, { prefix: "app" });
// The app owns auth. The component only sees the string it is handed.
export const incrementMine = mutation({
args: {},
returns: v.number(),
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
return await counter.add(ctx, identity.subject);
},
});
export const getMine = query({
args: {},
returns: v.number(),
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
return await counter.get(ctx, identity.subject);
},
});
These app side functions are what React calls through api.counter.incrementMine. The app decides on auth and rate limits; the component stays generic.
Boundary rules
- No parent tables.
v.id("users")inside a component refers to a component table namedusers, not the app's. Accept parent ids asv.string(). - Ids become strings. Every
Id<"counters">in the component isstringinComponentApi. Return them as strings on purpose. - No
ctx.auth. Resolve the user in the app and passuserId. - No app
process.env. Declare typed env indefineComponent("name", { env: { API_KEY: v.string() } })and let the app supply it withapp.use(c, { env: { API_KEY: ... } }), or pass the value as a function argument. .paginate()does not work across the boundary. Usepaginatorfromconvex-helpersinside the component.- HTTP routes in a component's
http.tsare mounted by the app withhttpPrefix. Component HTTP actions have noctx.auth. - Callbacks into the app travel as function handles:
createFunctionHandle(internal.x.y)in the app, stored asv.string(), cast toFunctionHandle<"mutation">in the component.
Publishing to npm (exports map, build order, README, versioning) is covered in references/publishing.md; open it when the component will be installed from a package rather than a local folder.
Common mistakes
| Mistake | Why it breaks | Do instead |
|---|---|---|
Functions missing from components.<name> | app.use(...) not added, or npx convex dev has not run since | Register in convex.config.ts, run dev, check the reference path matches the file name |
Importing mutation from the app's _generated/server | The function registers on the app, not the component | Import from the component's own ./_generated/server.js |
Calling api.counter.add from React | Component functions are internal references in the parent | Write an app mutation that calls ctx.runMutation(components.counter.public.add) |
v.id("users") in component args | Table numbers differ per component, validation fails | v.string() at the boundary |
ctx.auth.getUserIdentity() inside the component | Always returns no user | Authenticate in the app, pass userId |
process.env.MY_KEY inside the component | Undefined at runtime | Declare env in defineComponent or pass as an argument |
Public function without returns | Parent sees any, loses type safety | Add validators to every public function |
| Reading component env at module scope | Undefined during deploy analysis | Read env.X inside the handler |
Checklist
- Decided a component is needed rather than a helper function
-
convex.config.tscallsdefineComponent("<name>")and exports it - Schema lives in the component; no
v.id()for parent tables anywhere - Functions import from the component's own
_generated/server - Every public function has
argsandreturnsvalidators - Parent registers with
app.use(...)andnpx convex devruns clean - Client wrapper takes
ComponentApifirst, options second, usesPickctx types - App side wrapper functions handle auth and pass ids as strings
- No
ctx.auth,process.env, or.paginate()inside the component - Tested through an example app that installs the component
Docs
FAQ
What does the convex-component-authoring skill do?
Creates reusable Convex components with defineComponent, a clean client wrapper, their own schema, and an npm publish setup. Use when extracting a feature into a package, building something for the Convex component directory, or when a component's functions are not showing up in the parent app.
How do I install convex-component-authoring?
Run npx skills add https://github.com/waynesutton/builder-skills --skill convex-component-authoring in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/convex-component-authoring/ (personal) or .claude/skills/convex-component-authoring/ (project).
Which AI agents can use convex-component-authoring?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is convex-component-authoring safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains convex-component-authoring?
It is published by waynesutton in waynesutton/builder-skills. It has 1,144 recorded installs.
More from waynesutton/builder-skills
convex
waynesutton/builder-skills
Routes Convex work to the right builder skill and covers the handful of rules that apply everywhere: validators on every function, withIndex over filter, idempotent mutations, internal functions for scheduling. Use when a task mentions Convex and no more specific skill matches, or as the first stop before picking one.
convex-best-practices
waynesutton/builder-skills
Production patterns for Convex apps and the rules the @convex-dev/eslint-plugin enforces: validators, indexes, idempotent mutations, avoiding OCC conflicts, thin function wrappers, error handling. Use when reviewing Convex code, asking whether a pattern is right, setting up ESLint, or fixing write conflicts and slow queries.
convex-functions
waynesutton/builder-skills
Writes Convex queries, mutations, actions, and internal functions in the object form with args and returns validators, correct ctx usage, runtime boundaries, and error handling. Use when adding or changing anything in convex/.ts that exports a function, or when deciding between query, mutation, and action.
convex-schema-validator
waynesutton/builder-skills
Designs convex/schema.ts tables, validators, indexes, and relationships, and keeps the schema honest as data evolves. Use when creating tables, adding fields, choosing index fields, modeling relationships, or when a validator error appears at deploy time.
convex-realtime
waynesutton/builder-skills
Builds reactive UIs on Convex subscriptions: useQuery patterns, optimistic updates, pagination that stays live, presence, and avoiding subscription churn. Use when wiring the frontend to Convex, when data does not update live, or when a page rerenders too much.
convex-cron-jobs
waynesutton/builder-skills
Schedules work in Convex: cron jobs in convex/crons.ts, one off scheduled functions with runAfter and runAt, batching large jobs, and cancelling or inspecting the queue. Use when something needs to run on a timer, later, or in the background, or when a cron is not firing.
Popular frontend & design skills
setup-matt-pocock-skills
mattpocock/skills
Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.
frontend-design
anthropics/skills
Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.
hyperframes-animation
heygen-com/hyperframes
All animation knowledge for HyperFrames — atomic motion rules, multi-phase scene blueprints, scene transitions, broader motion-design techniques, AND the seven runtime adapters (GSAP default, plus Lottie, Three.js, Anime.js, CSS keyframes, Web Animations API, TypeGPU). Use for any motion or animation task: pick 2-4 rules and compose, or load a blueprint, or look up runtime-specific API (e.g. GSAP eases / Lottie player / Three.js mixer). Also covers auditing an existing composition's choreography (animation map) and 24 named text-animation effects. HyperFrames-native: single paused timeline, seek-safe, deterministic.
prototype
mattpocock/skills
Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.
hyperframes-core
heygen-com/hyperframes
The HyperFrames composition contract — build one renderable project. Use for composition structure, the data- timing attributes, class="clip", tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.
vercel-react-best-practices
vercel-labs/agent-skills
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.