Claude Marketplaces
SkillsMCP serversMarketplacesDigestTools
Submit

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP servers by category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control
Claude Marketplaces

An independent directory of skills, plugins and MCP servers for Claude Code. Not affiliated with Anthropic.

Browse

  • Skills
  • MCP servers
  • Marketplaces
  • Weekly digest
  • Skill index
  • MCP index
  • Marketplace index

Site

  • About
  • Tools
  • Submit
  • Feedback
  • Privacy
  • llms.txt
  • Plugins reference

Maker

  • Snug
  • Agent Caffeine
  • knobs
  • Wax
  • Vectoys
  • mertduzgun.com
Crafted at
Skills/Frontend Development
waynesutton avatar

Convex Component Authoring

waynesutton/convexskills

Convex Component Authoring teaches developers how to create self-contained, reusable Convex components with isolated database tables, functions, and TypeScript types that can be shared across projects. It's designed for backend developers building packages on the Convex platform who need to properly structure, export, and manage dependencies in component modules. The skill solves the problem of packaging Convex functionality (queries, mutations, actions, and schemas) into shareable, production-ready components with proper isolation and API boundaries.

installs
1.1K
GitHub stars
401
last updated
Aug 21, 2026
View on GitHubSave

Install

$ npx -y skills add waynesutton/convexskills --skill convex-component-authoring --agent claude-code

Installs into .claude/skills of the current project.

Files

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

NeedUse
Shared logic over the app's own tablesPlain TypeScript helper in convex/lib/
Its own tables with a schema the app cannot touchComponent
Isolated functions that run in their own transactionComponent
Reuse across apps with one install lineComponent
A React hook or client utility onlynpm 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 named users, not the app's. Accept parent ids as v.string().
  • Ids become strings. Every Id<"counters"> in the component is string in ComponentApi. Return them as strings on purpose.
  • No ctx.auth. Resolve the user in the app and pass userId.
  • No app process.env. Declare typed env in defineComponent("name", { env: { API_KEY: v.string() } }) and let the app supply it with app.use(c, { env: { API_KEY: ... } }), or pass the value as a function argument.
  • .paginate() does not work across the boundary. Use paginator from convex-helpers inside the component.
  • HTTP routes in a component's http.ts are mounted by the app with httpPrefix. Component HTTP actions have no ctx.auth.
  • Callbacks into the app travel as function handles: createFunctionHandle(internal.x.y) in the app, stored as v.string(), cast to FunctionHandle<"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

MistakeWhy it breaksDo instead
Functions missing from components.<name>app.use(...) not added, or npx convex dev has not run sinceRegister in convex.config.ts, run dev, check the reference path matches the file name
Importing mutation from the app's _generated/serverThe function registers on the app, not the componentImport from the component's own ./_generated/server.js
Calling api.counter.add from ReactComponent functions are internal references in the parentWrite an app mutation that calls ctx.runMutation(components.counter.public.add)
v.id("users") in component argsTable numbers differ per component, validation failsv.string() at the boundary
ctx.auth.getUserIdentity() inside the componentAlways returns no userAuthenticate in the app, pass userId
process.env.MY_KEY inside the componentUndefined at runtimeDeclare env in defineComponent or pass as an argument
Public function without returnsParent sees any, loses type safetyAdd validators to every public function
Reading component env at module scopeUndefined during deploy analysisRead env.X inside the handler

Checklist

  • Decided a component is needed rather than a helper function
  • convex.config.ts calls defineComponent("<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 args and returns validators
  • Parent registers with app.use(...) and npx convex dev runs clean
  • Client wrapper takes ComponentApi first, options second, uses Pick ctx 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

  • https://docs.convex.dev/llms.txt
  • https://docs.convex.dev/components/authoring
  • https://docs.convex.dev/components/using
  • https://www.convex.dev/components
SKILL.md on GitHub

Sponsors

SnugThe clipboard manager you and your agent can use, safelyAgent CaffeineClose your lid, come back to finished workWaxBlock anything with supervision, without erasing your iPhoneOpen StepsSponsoredFree and open-source skills that make AI coding agents easier to understand, verify, and control.

Details

Repository
github.com/waynesutton/convexskills
Categories
Frontend Development, Release Management, Databases

More from waynesutton/convexskills

All 14
  • Convex5.7K
  • Convex Best Practices3.6K
  • Convex Functions2.9K
  • Convex Schema Validator2.8K
  • Convex Realtime2.7K
  • Convex Cron Jobs2.6K
  • Convex Http Actions2.5K
  • Convex Security Check2.5K
  • Convex File Storage2.4K
  • Convex Migrations2.4K
  • Convex Security Audit1.4K
  • Convex Agents1.4K
  • Avoid Feature Creep1.2K

Recommended

More Frontend Development
1astronomer avatarsetting-up-astro-projectastronomer/agentsInitialize and configure Astro/Airflow projects. Use when the user wants to create a new project, set up dependencies, configure connections/variables, or under1.1Kinstalls
2mindrally avatarhtmlmindrally/skillsGuidelines for semantic HTML markup, document structure, forms, accessibility attributes, and modern HTML best practices1.1Kinstalls
3getsentry avatarsentry-react-setupgetsentry/sentry-agent-skillsSetup Sentry in React apps. Use when asked to add Sentry to React, install @sentry/react, or configure error monitoring, error boundaries, session replay, or br1.1Kinstalls
4patternsdev avatarreact-data-fetchingpatternsdev/skillsTeaches modern React data fetching patterns with TanStack Query, SWR, and Suspense. Use when implementing caching, deduplication, optimistic updates, or paralle1.1Kinstalls
5spences10 avatarsvelte-runesspences10/svelte-skills-kitSvelte runes guidance. Use for reactive state, props, effects, or migration. Covers $state, $derived, $effect, $props, $bindable. Prevents reactivity mistakes.1Kinstalls
6chrisbanes avatarcompose-modifier-and-layout-stylechrisbanes/skillsUse when writing or reviewing Jetpack Compose layout APIs, modifier parameters, modifier chain construction, hardcoded root layout decisions, or layout wrappers1Kinstalls