components-guide

get-convex/agent-skills · updated Apr 8, 2026

$npx skills add https://github.com/get-convex/agent-skills --skill components-guide
0 commentsdiscussion
summary

Self-contained mini-backends that bundle schema, functions, and data with clear API boundaries.

  • Components encapsulate features like storage, payments, and notifications as reusable backend modules, reducing monolithic code and improving maintainability
  • Sibling components pattern allows multiple components to work together at the same level, with the main app orchestrating calls across them
  • Official component library includes authentication, storage, payments, AI, and utility compone
skill.md

Convex Components Guide

Use components to encapsulate features and build maintainable, reusable backends.

What Are Convex Components?

Components are self-contained mini-backends that bundle:

  • Their own database schema
  • Their own functions (queries, mutations, actions)
  • Their own data (isolated tables)
  • Clear API boundaries

Think of them as: npm packages for your backend, or microservices without the deployment complexity.

Why Use Components?

Traditional Approach (Monolithic)

convex/
  users.ts (500 lines)
  files.ts (600 lines - upload, storage, permissions, rate limiting)
  payments.ts (400 lines - Stripe, webhooks, billing)
  notifications.ts (300 lines)
  analytics.ts (200 lines)

Total: One big codebase, everything mixed together

Component Approach (Encapsulated)

convex/
  components/
    storage/ (File uploads - reusable)
    billing/ (Payments - reusable)
    notifications/ (Alerts - reusable)
    analytics/ (Tracking - reusable)
  convex.config.ts (Wire components together)
  domain/ (Your actual business logic)
    users.ts (50 lines - uses components)
    projects.ts (75 lines - uses components)

Total: Clean, focused, reusable

Quick Start

1. Install a Component

# Official components from npm
npm install @convex-dev/ratelimiter

2. Configure in convex.config.ts

import { defineApp } from "convex/server";
import ratelimiter from "@convex-dev/ratelimiter/convex.config";

export default defineApp({
  components: {
    ratelimiter,
  },
});

3. Use in Your Code

import { components } from "./_generated/api";

export const createPost = mutation({
  handler: async (ctx, args) => {
    // Use the component
    await components.ratelimiter.check(ctx, {
      key: `user:${ctx.user._id}`,
      limit: 10,
      period: 60000, // 10 requests per minute
    });

    return await ctx.db.insert("posts", args);
  },
});

Sibling Components Pattern

Multiple components working together at the same level:

// convex.config.ts
export default defineApp({
  components: {
    // Sibling components - each handles one concern
    auth: authComponent,
    storage: storageComponent,
    payments: paymentsComponent,
    emails: emailComponent,
    analytics: analyticsComponent,
  },
});

Example: Complete Feature Using Siblings

// convex/subscriptions.ts
import { components } from "./_generated/api";

export const subscribe = mutation({
  args: { plan: v.string() },
  handler: async (ctx, args) => {
    // 1. Verify authentication (auth component)
    const user = await components.auth.getCurrentUser(ctx);

    // 2. Create payment (payments component)
    const subscription = await components.payments.createSubscription(ctx, {
      userId: user._id,
      plan: args.plan,
      amount: getPlanAmount(args.plan),
    });

    // 3. Track conversion (analytics component)
    await components.analytics.track(ctx, {
      event: "subscription_created",
      userId: user._id,
      plan: args.plan,
    });

    // 4. Send confirmation (emails component)
    await components.emails.send(ctx, {
      to: user.email,
      template: "subscription_welcome",
      data: { plan: args.plan },
    });

    // 5. Store subscription in main app
    await ctx.db.insert("subscriptions", {
      userId: user._id,
      paymentId: subscription.id,
      plan: args.plan,
      status: "active",
    });

    return subscription;
  },
});

Official Components

Browse Component Directory:

Authentication

  • @convex-dev/better-auth - Better Auth integration

Storage

  • @convex-dev/r2 - Cloudflare R2 file storage
  • @convex-dev/storage - File upload/download

Payments

  • @convex-dev/polar - Polar billing & subscriptions

AI

  • @convex-dev/agent - AI agent workflows
  • @convex-dev/embeddings - Vector storage & search

Backend Utilities

  • @convex-dev/ratelimiter - Rate limiting
  • @convex-dev/aggregate - Data aggregations
  • @convex-dev/action-cache - Cache action results
  • @convex-dev/sharded-counter - Distributed counters
  • @convex-dev/migrations - Schema migrations
  • @convex-dev/workflow - Workflow orchestration

Creating Your Own Component

When to Create a Component

Good reasons:

  • Feature is self-contained
  • You'll reuse it across projects
  • Want to share with team/community
  • Complex feature with its own data model
  • Third-party integration wrapper

Not good reasons:

  • One-off business logic
  • Tightly coupled to main app
  • Simple utility functions

Structure

mkdir -p convex/components/notifications
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";

export default defineComponent("notifications");
// convex/components/notifications/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  notifications: defineTable({
    userId: v.id("users"),
    message: v.string(),
    read: v.boolean(),
    createdAt: v.number(),
  })
    .index("by_user", ["userId"])
    .index("by_user_and_read", ["userId", "read"]),
});
// convex/components/notifications/send.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
  args: {
    userId: v.id("users"),
    message: v.string(),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert("notifications", {
      userId: args.userId,
      message: args.message,
      read: false,
      createdAt: Date.now(),
    });
  },
});

export const markRead = mutation({
  args: { notificationId: v.id("notifications") },
  handler: async (ctx, args) => {
    await ctx.db.patch(args.notificationId, { read: true });
  },
});
// convex/components/notifications/read.ts
import { query } from "./_generated/server";
import { v } from "convex/values";

export const list = query({
  args: { userId: v.id("users") },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("notifications")
      .withIndex("by_user", q => q.eq("userId", args.userId))
      .order("desc")
      .collect();
  },
});

export const unreadCount = query({
  args: { userId: v.id("users") },
  handler: async (ctx, args) => {
    const unread = await ctx.db
      .query("notifications")
      .withIndex("by_user_and_read", q =>
        q.eq("userId", args.userId).eq("read", false)
      )
      .collect();

    return unread.length;
  },
});

Component Communication Patterns

Parent to Component (Good)

// Main app calls component
await components.storage.upload(ctx, file);
await components.analytics.track(ctx, event);

Parent to Multiple Siblings (Good)

// Main app orchestrates multiple components
await components.auth.verify(ctx);
const file = await components.storage.upload(ctx, data);
await components.notifications.send(ctx, message);

Component Receives Parent Data (Good)

// Pass IDs from parent's tables to component
await components.audit.log(ctx, {
  userId: user._id, // From parent's users table
  action: "delete",
  resourceId: task._id, // From parent's tasks table
});

// Component stores these as strings/IDs
// but doesn't access parent tables directly

Component to Parent Tables (Bad)

// Inside component code - DON'T DO THIS
const user = await ctx.db.get(userId); // Error! Can't access parent tables

Sibling to Sibling (Bad)

Components can't call each other directly. If you need this, they should be in the main app or refactor the design.

Best Practices

1. Single Responsibility

Each component does ONE thing well:

  • Storage component handles files
  • Auth component handles authentication
  • Don't create "utils" component with everything

2. Clear API Surface

// Export only what's needed
export { upload, download, delete } from "./storage";

// Keep internals private
// (Don't export helper functions)

3. Minimal Coupling

// Good: Pass data as arguments
await components.audit.log(ctx, {
  userId: user._id,
  action: "delete"
});

// Bad: Component accesses parent tables
// (Not even possible, but shows the principle)

4. Version Your Components

{
  "name": "@yourteam/notifications-component",
  "version": "1.0.0"
}

5. Document Your Components

Include README with:

  • What the component does
  • How to install
  • How to use
  • API reference
  • Examples

Checklist

  • Browse Component Directory for existing solutions
  • Install components via npm: npm install @convex-dev/component-name
  • Configure in convex.config.ts
  • Use sibling components for feature encapsulation
  • Create your own components for reusable features
  • Keep components focused (single responsibility)
  • Test components in isolation
  • Document component APIs
  • Version your components properly

Discussion

Product Hunt–style comments (not star reviews)
  • No comments yet — start the thread.
general reviews

Ratings

4.765 reviews
  • Henry Thompson· Dec 28, 2024

    Useful defaults in components-guide — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Advait Nasser· Dec 24, 2024

    Solid pick for teams standardizing on skills: components-guide is focused, and the summary matches what you get after install.

  • Henry Bhatia· Dec 16, 2024

    components-guide is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

  • Olivia Johnson· Dec 8, 2024

    Keeps context tight: components-guide is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Charlotte Sethi· Nov 27, 2024

    I recommend components-guide for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Layla Ndlovu· Nov 19, 2024

    components-guide is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

  • James Iyer· Nov 7, 2024

    Useful defaults in components-guide — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • James Robinson· Oct 26, 2024

    I recommend components-guide for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Charlotte Taylor· Oct 18, 2024

    Useful defaults in components-guide — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Kofi Shah· Oct 10, 2024

    Keeps context tight: components-guide is the kind of skill you can hand to a new teammate without a long onboarding doc.

showing 1-10 of 65

1 / 7