Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
waynesutton avatar

Convex Component Authoring

  • 1.1k installs
  • 401 repo stars
  • Updated February 6, 2026
  • waynesutton/convexskills

convex-component-authoring is an agent skill for building reusable Convex components with schemas, functions, and packaging patterns.

About

The convex-component-authoring skill guides developers building reusable Convex components that package schemas, queries, mutations, actions, and internal helpers for distribution across apps. It covers component directory layout, export surfaces, versioning expectations, and integration patterns so consuming projects can mount the component without copying implementation details. Agents help define validation with Convex validators, separate public APIs from internal tables, document install steps, and ensure auth or environment assumptions are explicit. The skill is used when extracting shared backend logic such as billing, chat, or analytics into a component library rather than monolithic convex folders. It complements general Convex app skills by focusing on packaging boundaries, migration notes, and consumer configuration.

  • Covers reusable Convex component packaging and export surfaces.
  • Separates public APIs from internal tables and helpers.
  • Documents install and integration steps for consuming apps.
  • Uses Convex validators and schema boundaries for shared modules.
  • Targets extracting shared backend logic into component libraries.

Convex Component Authoring by the numbers

  • 1,056 all-time installs (skills.sh)
  • +8 installs in the week ending Aug 2, 2026 (Skillselion tracking)
  • Ranked #379 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
At a glance

convex-component-authoring capabilities & compatibility

Capabilities
convex component directory layout · public versus internal api boundaries · validator and schema packaging · consumer integration documentation · shared backend module extraction
Use cases
api development · orchestration
From the docs

What convex-component-authoring says it does

convex-component-authoring
SKILL.md
npx skills add https://github.com/waynesutton/convexskills --skill convex-component-authoring

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1.1k
repo stars401
Security audit3 / 3 scanners passed
Last updatedFebruary 6, 2026
Repositorywaynesutton/convexskills

How do I package shared Convex backend logic as a reusable component other apps can install?

Author reusable Convex components with schemas, functions, and packaging patterns for shared backend modules.

Who is it for?

Developers extracting shared Convex logic into installable component packages.

Skip if: Skip for one-off Convex app features that will not be reused across projects.

When should I use this skill?

User authors or refactors a reusable Convex component library with shared schemas and functions.

What you get

A structured Convex component with documented exports, validators, and consumer integration steps.

  • Convex component package
  • isolated export surface
  • publish-ready module structure

Files

SKILL.mdMarkdownGitHub ↗

Convex Component Authoring

Create self-contained, reusable Convex components with proper isolation, exports, and dependency management for sharing across projects.

Documentation Sources

Before implementing, do not assume; fetch the latest documentation:

  • Primary: https://docs.convex.dev/components
  • Component Authoring: https://docs.convex.dev/components/authoring
  • For broader context: https://docs.convex.dev/llms.txt

Instructions

What Are Convex Components?

Convex components are self-contained packages that include:

  • Database tables (isolated from the main app)
  • Functions (queries, mutations, actions)
  • TypeScript types and validators
  • Optional frontend hooks

Component Structure

my-convex-component/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│   ├── index.ts           # Main exports
│   ├── component.ts       # Component definition
│   ├── schema.ts          # Component schema
│   └── functions/
│       ├── queries.ts
│       ├── mutations.ts
│       └── actions.ts
└── convex.config.ts       # Component configuration

Creating a Component

1. Component Configuration
// convex.config.ts
import { defineComponent } from "convex/server";

export default defineComponent("myComponent");
2. Component Schema
// src/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  // Tables are isolated to this component
  items: defineTable({
    name: v.string(),
    data: v.any(),
    createdAt: v.number(),
  }).index("by_name", ["name"]),
  
  config: defineTable({
    key: v.string(),
    value: v.any(),
  }).index("by_key", ["key"]),
});
3. Component Definition
// src/component.ts
import { defineComponent, ComponentDefinition } from "convex/server";
import schema from "./schema";
import * as queries from "./functions/queries";
import * as mutations from "./functions/mutations";

const component = defineComponent("myComponent", {
  schema,
  functions: {
    ...queries,
    ...mutations,
  },
});

export default component;
4. Component Functions
// src/functions/queries.ts
import { query } from "../_generated/server";
import { v } from "convex/values";

export const list = query({
  args: {
    limit: v.optional(v.number()),
  },
  returns: v.array(v.object({
    _id: v.id("items"),
    name: v.string(),
    data: v.any(),
    createdAt: v.number(),
  })),
  handler: async (ctx, args) => {
    return await ctx.db
      .query("items")
      .order("desc")
      .take(args.limit ?? 10);
  },
});

export const get = query({
  args: { name: v.string() },
  returns: v.union(v.object({
    _id: v.id("items"),
    name: v.string(),
    data: v.any(),
  }), v.null()),
  handler: async (ctx, args) => {
    return await ctx.db
      .query("items")
      .withIndex("by_name", (q) => q.eq("name", args.name))
      .unique();
  },
});
// src/functions/mutations.ts
import { mutation } from "../_generated/server";
import { v } from "convex/values";

export const create = mutation({
  args: {
    name: v.string(),
    data: v.any(),
  },
  returns: v.id("items"),
  handler: async (ctx, args) => {
    return await ctx.db.insert("items", {
      name: args.name,
      data: args.data,
      createdAt: Date.now(),
    });
  },
});

export const update = mutation({
  args: {
    id: v.id("items"),
    data: v.any(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    await ctx.db.patch(args.id, { data: args.data });
    return null;
  },
});

export const remove = mutation({
  args: { id: v.id("items") },
  returns: v.null(),
  handler: async (ctx, args) => {
    await ctx.db.delete(args.id);
    return null;
  },
});
5. Main Exports
// src/index.ts
export { default as component } from "./component";
export * from "./functions/queries";
export * from "./functions/mutations";

// Export types for consumers
export type { Id } from "./_generated/dataModel";

Using a Component

// In the consuming app's convex/convex.config.ts
import { defineApp } from "convex/server";
import myComponent from "my-convex-component";

const app = defineApp();

app.use(myComponent, { name: "myComponent" });

export default app;
// In the consuming app's code
import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

function MyApp() {
  // Access component functions through the app's API
  const items = useQuery(api.myComponent.list, { limit: 10 });
  const createItem = useMutation(api.myComponent.create);
  
  return (
    <div>
      {items?.map((item) => (
        <div key={item._id}>{item.name}</div>
      ))}
      <button onClick={() => createItem({ name: "New", data: {} })}>
        Add Item
      </button>
    </div>
  );
}

Component Configuration Options

// convex/convex.config.ts
import { defineApp } from "convex/server";
import myComponent from "my-convex-component";

const app = defineApp();

// Basic usage
app.use(myComponent);

// With custom name
app.use(myComponent, { name: "customName" });

// Multiple instances
app.use(myComponent, { name: "instance1" });
app.use(myComponent, { name: "instance2" });

export default app;

Providing Component Hooks

// src/hooks.ts
import { useQuery, useMutation } from "convex/react";
import { FunctionReference } from "convex/server";

// Type-safe hooks for component consumers
export function useMyComponent(api: {
  list: FunctionReference<"query">;
  create: FunctionReference<"mutation">;
}) {
  const items = useQuery(api.list, {});
  const createItem = useMutation(api.create);
  
  return {
    items,
    createItem,
    isLoading: items === undefined,
  };
}

Publishing a Component

package.json
{
  "name": "my-convex-component",
  "version": "1.0.0",
  "description": "A reusable Convex component",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist",
    "convex.config.ts"
  ],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "peerDependencies": {
    "convex": "^1.0.0"
  },
  "devDependencies": {
    "convex": "^1.17.0",
    "typescript": "^5.0.0"
  },
  "keywords": [
    "convex",
    "component"
  ]
}
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "declaration": true,
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Examples

Rate Limiter Component

// rate-limiter/src/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  requests: defineTable({
    key: v.string(),
    timestamp: v.number(),
  })
    .index("by_key", ["key"])
    .index("by_key_and_time", ["key", "timestamp"]),
});
// rate-limiter/src/functions/mutations.ts
import { mutation } from "../_generated/server";
import { v } from "convex/values";

export const checkLimit = mutation({
  args: {
    key: v.string(),
    limit: v.number(),
    windowMs: v.number(),
  },
  returns: v.object({
    allowed: v.boolean(),
    remaining: v.number(),
    resetAt: v.number(),
  }),
  handler: async (ctx, args) => {
    const now = Date.now();
    const windowStart = now - args.windowMs;
    
    // Clean old entries
    const oldEntries = await ctx.db
      .query("requests")
      .withIndex("by_key_and_time", (q) => 
        q.eq("key", args.key).lt("timestamp", windowStart)
      )
      .collect();
    
    for (const entry of oldEntries) {
      await ctx.db.delete(entry._id);
    }
    
    // Count current window
    const currentRequests = await ctx.db
      .query("requests")
      .withIndex("by_key", (q) => q.eq("key", args.key))
      .collect();
    
    const remaining = Math.max(0, args.limit - currentRequests.length);
    const allowed = remaining > 0;
    
    if (allowed) {
      await ctx.db.insert("requests", {
        key: args.key,
        timestamp: now,
      });
    }
    
    const oldestRequest = currentRequests[0];
    const resetAt = oldestRequest 
      ? oldestRequest.timestamp + args.windowMs 
      : now + args.windowMs;
    
    return { allowed, remaining: remaining - (allowed ? 1 : 0), resetAt };
  },
});
// Usage in consuming app
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

function useRateLimitedAction() {
  const checkLimit = useMutation(api.rateLimiter.checkLimit);
  
  return async (action: () => Promise<void>) => {
    const result = await checkLimit({
      key: "user-action",
      limit: 10,
      windowMs: 60000,
    });
    
    if (!result.allowed) {
      throw new Error(`Rate limited. Try again at ${new Date(result.resetAt)}`);
    }
    
    await action();
  };
}

Best Practices

  • Never run npx convex deploy unless explicitly instructed
  • Never run any git commands unless explicitly instructed
  • Keep component tables isolated (don't reference main app tables)
  • Export clear TypeScript types for consumers
  • Document all public functions and their arguments
  • Use semantic versioning for component releases
  • Include comprehensive README with examples
  • Test components in isolation before publishing

Common Pitfalls

1. Cross-referencing tables - Component tables should be self-contained 2. Missing type exports - Export all necessary types 3. Hardcoded configuration - Use component options for customization 4. No versioning - Follow semantic versioning 5. Poor documentation - Document all public APIs

References

  • Convex Documentation: https://docs.convex.dev/
  • Convex LLMs.txt: https://docs.convex.dev/llms.txt
  • Components: https://docs.convex.dev/components
  • Component Authoring: https://docs.convex.dev/components/authoring

Related skills

FAQ

What does convex-component-authoring help build?

Reusable Convex components packaging schemas, queries, mutations, and internal helpers.

Why separate public and internal APIs?

So consuming apps integrate through stable exports without depending on private tables.

When should I use this instead of general Convex app skills?

When extracting shared backend modules for distribution rather than editing a single app convex folder.

Is Convex Component Authoring safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.