components-guide
Guide to using Convex components for feature encapsulation. Learn about sibling components, creating your own, and when to use components vs monolithic code.
Best use case
components-guide is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Guide to using Convex components for feature encapsulation. Learn about sibling components, creating your own, and when to use components vs monolithic code.
Teams using components-guide should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/components-guide/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How components-guide Compares
| Feature / Agent | components-guide | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Guide to using Convex components for feature encapsulation. Learn about sibling components, creating your own, and when to use components vs monolithic code.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 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
```bash
# Official components from npm
npm install @convex-dev/ratelimiter
```
### 2. Configure in convex.config.ts
```typescript
import { defineApp } from "convex/server";
import ratelimiter from "@convex-dev/ratelimiter/convex.config";
export default defineApp({
components: {
ratelimiter,
},
});
```
### 3. Use in Your Code
```typescript
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:
```typescript
// 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
```typescript
// 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;
},
});
```
**What this achieves:**
- ✅ Each component is single-purpose
- ✅ Components are reusable across features
- ✅ Easy to swap implementations (change email provider)
- ✅ Can update components independently
- ✅ Clear separation of concerns
## Official Components
Browse [Component Directory](https://www.convex.dev/components):
### 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
```bash
mkdir -p convex/components/notifications
```
```typescript
// convex/components/notifications/convex.config.ts
import { defineComponent } from "convex/server";
export default defineComponent("notifications");
```
```typescript
// 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"]),
});
```
```typescript
// 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 });
},
});
```
```typescript
// 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;
},
});
```
### Use Your Component
```typescript
// convex.config.ts
import { defineApp } from "convex/server";
import notifications from "./components/notifications/convex.config";
export default defineApp({
components: {
notifications, // Your local component
},
});
```
```typescript
// convex/tasks.ts - main app code
import { components } from "./_generated/api";
export const completeTask = mutation({
args: { taskId: v.id("tasks") },
handler: async (ctx, args) => {
const task = await ctx.db.get(args.taskId);
await ctx.db.patch(args.taskId, { completed: true });
// Use your component
await components.notifications.send(ctx, {
userId: task.userId,
message: `Task "${task.title}" completed!`,
});
},
});
```
## Component Communication Patterns
### ✅ Parent → Component (Good)
```typescript
// Main app calls component
await components.storage.upload(ctx, file);
await components.analytics.track(ctx, event);
```
### ✅ Parent → Multiple Siblings (Good)
```typescript
// 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)
```typescript
// 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 → Parent Tables (Bad)
```typescript
// Inside component code - DON'T DO THIS
const user = await ctx.db.get(userId); // Error! Can't access parent tables
```
### ❌ Sibling → Sibling (Bad)
Components can't call each other directly. If you need this, they should be in the main app or refactor the design.
## Real-World Examples
### Multi-Tenant SaaS
```typescript
// convex.config.ts
export default defineApp({
components: {
auth: "@convex-dev/better-auth",
organizations: "./components/organizations",
billing: "./components/billing",
storage: "@convex-dev/r2",
analytics: "./components/analytics",
emails: "./components/emails",
},
});
```
Each component:
- `auth` - User authentication & sessions
- `organizations` - Tenant isolation & permissions
- `billing` - Stripe integration & subscriptions
- `storage` - File uploads to R2
- `analytics` - Event tracking & metrics
- `emails` - Email sending via SendGrid
### E-Commerce Platform
```typescript
export default defineApp({
components: {
cart: "./components/cart",
inventory: "./components/inventory",
orders: "./components/orders",
payments: "@convex-dev/polar",
shipping: "./components/shipping",
recommendations: "./components/recommendations",
},
});
```
### AI Application
```typescript
export default defineApp({
components: {
agent: "@convex-dev/agent",
embeddings: "./components/embeddings",
documents: "./components/documents",
chat: "./components/chat",
workflow: "@convex-dev/workflow",
},
});
```
## Migration from Monolithic
**Step 1: Identify Features**
```
Current monolith:
- File uploads (mixed with main app)
- Rate limiting (scattered everywhere)
- Analytics (embedded in functions)
```
**Step 2: Extract One Feature**
```bash
# Create component
mkdir -p convex/components/storage
# Move storage code to component
# Update imports in main app
```
**Step 3: Test Independently**
```bash
# Component has its own tests
# No coupling to main app
```
**Step 4: Repeat**
Extract other features incrementally.
## 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
```typescript
// Export only what's needed
export { upload, download, delete } from "./storage";
// Keep internals private
// (Don't export helper functions)
```
### 3. Minimal Coupling
```typescript
// ✅ 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
```json
{
"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
## Troubleshooting
### Component not found
```bash
# Make sure component is in convex.config.ts
# Run: npx convex dev
```
### Can't access parent tables
```
This is by design! Components are sandboxed.
Pass data as arguments instead.
```
### Component conflicts
```
Each component has isolated tables.
Components can't see each other's data.
```
## Learn More
- [Components Documentation](https://docs.convex.dev/components)
- [Component Directory](https://www.convex.dev/components)
- [Using Components](https://docs.convex.dev/components/using)
- [Authoring Components](https://docs.convex.dev/components/authoring)
- [Stack: Backend Components](https://stack.convex.dev/backend-components)
## Checklist
- [ ] Browse [Component Directory](https://www.convex.dev/components) 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
**Remember:** Components are about encapsulation and reusability. When in doubt, prefer components over monolithic code!Related Skills
cowork-guide
CRITICAL: Comprehensive guide for CoWork Skills CLI tool. Triggers on: cowork, Skills.toml, skill management, plugin configuration, cowork init, cowork install, cowork config, cowork generate
clack-guidelines
Comprehensive guide for building beautiful interactive command-line interfaces using Clack. Use when creating CLI tools with text input, selections, autocomplete, progress tracking, and streaming output.
astrology-interpretation-guide
Comprehensive astrology expert covering natal charts, transits, houses, aspects, and astrological traditions from Western to Vedic
Arcanea Voice Guide
Brand voice and terminology guide for all Arcanea content - ensures consistent, magical communication across UI, marketing, and narrative
analytic-skills-guide
Guide for AI agent to use the tools offered by this library to perform analytic tasks.
ai-engineering-guide
Practical guide for building production ML systems based on Chip Huyen's AI Engineering book. Use when users ask about model evaluation, deployment strategies, monitoring, data pipelines, feature engineering, cost optimization, or MLOps. Covers metrics, A/B testing, serving patterns, drift detection, and production best practices.
agents-md-guidelines
Guidelines for writing small, stable AGENTS.md files. Use when creating, refactoring, or reviewing AGENTS.md.
agent-ops-guide
Interactive workflow guide. Use when user is unsure what to do next, needs help navigating AgentOps, or wants to understand available tools.
agent-guidelines
When you need to understand the project's core mandate, operational rules, or "Constitution". Use this skill to align with the project's identity and strict coding standards.
ADAPTATION_GUIDE
Use when adapting Droidz framework or creating custom workflows. Guide for customizing droids, skills, and commands for specific project needs.
5-styleguide-generation
Fifth step in building instruction context for codebase
security-skills-guide
Guide for security-related Agent Skills including penetration testing, code auditing, threat hunting, and forensics skills.