Files
SuperCharged-Claude-Code-Up…/convex-component-authoring/skill.md
admin 07242683bf Add 260+ Claude Code skills from skills.sh
Complete collection of AI agent skills including:
- Frontend Development (Vue, React, Next.js, Three.js)
- Backend Development (NestJS, FastAPI, Node.js)
- Mobile Development (React Native, Expo)
- Testing (E2E, frontend, webapp)
- DevOps (GitHub Actions, CI/CD)
- Marketing (SEO, copywriting, analytics)
- Security (binary analysis, vulnerability scanning)
- And many more...

Synchronized from: https://skills.sh/

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-23 18:02:28 +00:00

10 KiB

name, description, version, author, tags
name description version author tags
Convex Component Authoring How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management 1.0.0 Convex
convex
components
reusable
packages
npm

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:

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