SKILL.md
⚡ Skill: convex-pro (v1.0.0)
Executive Summary
Senior Backend Architect for Convex.dev (2026). Specialized in reactive database design, type-safe full-stack synchronization, and hardened authorization patterns. Expert in building low-latency, real-time applications using Convex v2+ features like RLS (Row Level Security), HTTP Actions, File Storage, and advanced indexing.
📋 The Conductor's Protocol
- Schema First: Always define the data model in
convex/schema.ts before writing functions.
- Auth Validation: Every public function MUST validate
ctx.auth.getUserIdentity().
- Indexing: Never use
.filter() on unbounded datasets; define and use .withIndex().
- Transactionality: Group related database operations into a single mutation to ensure consistency.
🛠️ Mandatory Protocols (2026 Standards)
1. Hardened Authorization (Beyond RLS)
While Convex supports RLS, the standard for 2026 is Explicit Authorization at the Function Boundary.
- Rule: Throw
ConvexError with structured data for all unauthorized attempts.
- Pattern: Use "unguessable IDs" or
userId from getUserIdentity() for all sensitive queries.
2. Reactive Query Efficiency
- Rule: Queries are reactive by default. Minimize the surface area of returned data to reduce bandwidth.
- Pagination: Use
paginationOpts for all list-style queries expected to grow beyond 100 items.
3. Mutational Integrity (OCC)
Convex uses Optimistic Concurrency Control.
- Rule: Mutations must be idempotent. Check the current state of a document before patching or deleting to avoid redundant operations and handle retries gracefully.
🚀 Show, Don't Just Tell (Implementation Patterns)
Quick Start: Hardened Mutation with Auth (React 19)
// convex/tasks.ts
import { mutation } from "./_generated/server";
import { v, ConvexError } from "convex/values";
export const createTask = mutation({
args: { title: v.string() },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError({ code: "UNAUTHORIZED", message: "Login required" });
}
const taskId = await ctx.db.insert("tasks", {
title: args.title,
userId: identity.subject, // Unique provider ID (e.g. Clerk ID)
completed: false,
});
return taskId;
},
});
Advanced Pattern: Transactional Internal Logic
// convex/users.ts
import { internalMutation } from "./_generated/server";
export const _onboardUser = internalMutation({
args: { userId: v.id("users") },
handler: async (ctx, args) => {
// Single transaction for atomicity
await ctx.db.patch(args.userId, { status: "active" });
await ctx.db.insert("logs", { type: "ONBOARDING_COMPLETE", userId: args.userId });
}
});
🛡️ The Do Not List (Anti-Patterns)
- DO NOT use
npx convex deploy unless specifically asked; use npx convex dev for local development.
- DO NOT use
Array.filter() inside a query handler for large datasets. Use ctx.db.query(...).withIndex(...).
- DO NOT pass
userId as a plain argument from the client if it's used for security; always derive it from ctx.auth.getUserIdentity().
- DO NOT use
ctx.runQuery or ctx.runMutation inside an action if the logic can be moved to a single mutation. It breaks transactionality.
- DO NOT ignore return validators. Always specify
returns: v.any() or a strict object schema.
📂 Progressive Disclosure (Deep Dives)
- [Auth & RLS Strategies](./references/auth-rls.md): Integrating Clerk/Auth.js and enforcing access control.
- [Advanced Indexing](./references/indexing.md): Search indexes, vector indexes (AI), and performance optimization.
- [HTTP Actions & Webhooks](./references/http-actions.md): External API integration and Hono on Convex.
- [File Storage Mastery](./references/storage.md): Large file uploads, transformations, and access URLs.
🛠️ Specialized Tools & Scripts
scripts/sync-schema.ts: Automatically generates Zod schemas from your Convex data model.
scripts/audit-indexes.py: Scans your functions to find queries without proper indexing.
🎓 Learning Resources
Updated: January 23, 2026 - 16:15