avoid-feature-creep
Prevent feature creep when building software, apps, and AI-powered products. Use this skill when planning features, reviewing scope, building MVPs, managing…
Guidelines for building production-ready Convex apps covering function organization, query patterns, validation, TypeScript usage, error handling, and the Zen of Convex design philosophy
$ npx -y skills add waynesutton/convexskills --skill convex-best-practices --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/convex-best-practicesContext preview
The summary Claude sees to decide when to auto-load this skill.
Guidelines for building production-ready Convex apps covering function organization, query patterns, validation, TypeScript usage, error handling, and the Zen of Convex design philosophy
name: convex-best-practices description: Guidelines for building production-ready Convex apps covering function organization, query patterns, validation, TypeScript usage, error handling, and the Zen of Convex design philosophy
Build production-ready Convex applications by following established patterns for function organization, query optimization, validation, TypeScript usage, and error handling.
All patterns in this skill comply with `@convex-dev/eslint-plugin`. Install it for build-time validation:
npm i @convex-dev/eslint-plugin --save-dev
// eslint.config.js
import { defineConfig } from "eslint/config";
import convexPlugin from "@convex-dev/eslint-plugin";
export default defineConfig([
...convexPlugin.configs.recommended,
]);The plugin enforces four rules:
| Rule | What it enforces | | ----------------------------------- | --------------------------------- | | `no-old-registered-function-syntax` | Object syntax with `handler` | | `require-argument-validators` | `args: {}` on all functions | | `explicit-table-ids` | Table name in db operations | | `import-wrong-runtime` | No Node imports in Convex runtime |
Docs: https://docs.convex.dev/eslint
Before implementing, do not assume; fetch the latest documentation:
1. **Convex manages the hard parts** - Let Convex handle caching, real-time sync, and consistency 2. **Functions are the API** - Design your functions as your application's interface 3. **Schema is truth** - Define your data model explicitly in schema.ts 4. **TypeScript everywhere** - Leverage end-to-end type safety 5. **Queries are reactive** - Think in terms of subscriptions, not requests
Organize your Convex functions by domain:
// convex/users.ts - User-related functions
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
export const get = query({
args: { userId: v.id("users") },
returns: v.union(
v.object({
_id: v.id("users"),
_creationTime: v.number(),
name: v.string(),
email: v.string(),
}),
v.null(),
),
handler: async (ctx, args) => {
return await ctx.db.get("users", args.userId);
},
});Always define validators for arguments AND return types:
export const createTask = mutation({
args: {
title: v.string(),
description: v.optional(v.string()),
priority: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
},
returns: v.id("tasks"),
handler: async (ctx, args) => {
return await ctx.db.insert("tasks", {
title: args.title,
description: args.description,
priority: args.priority,
completed: false,
createdAt: Date.now(),
});
},
});Use indexes instead of filters for efficient queries:
// Schema with index
export default defineSchema({
tasks: defineTable({
userId: v.id("users"),
status: v.string(),
createdAt: v.number(),
})
.index("by_user", ["userId"])
.index("by_user_and_status", ["userId", "status"]),
});
// Query using index
export const getTasksByUser = query({
args: { userId: v.id("users") },
returns: v.array(
v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
userId: v.id("users"),
status: v.string(),
createdAt: v.number(),
}),
),
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.order("desc")
.collect();
},
});Use ConvexError for user-facing errors:
import { ConvexError } from "convex/values";
export const updateTask = mutation({
args: {
taskId: v.id("tasks"),
title: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
const task = await ctx.db.get("tasks", args.taskId);
if (!task) {
throw new ConvexError({
code: "NOT_FOUND",
message: "Task not found",
});
}
await ctx.db.patch("tasks", args.taskId, { title: args.title });
return null;
},
});Convex uses OCC. Follow these patterns to minimize conflicts:
// GOOD: Make mutations idempotent
export const completeTask = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
const task = await ctx.db.get("tasks", args.taskId);
// Early return if already complete (idempotent)
if (!task || task.status === "completed") {
return null;
}
await ctx.db.patch("tasks", args.taskId, {
status: "completed",
completedAt: Date.now(),
});
return null;
},
});
// GOOD: Patch directly without reading first when possible
export const updateNote = mutation({
args: { id: v.id("notes"), content: v.string() },
returns: v.null(),
handler: async (ctx, args) => {
// Patch directly - ctx.db.patch throws if document doesn't exist
await ctx.db.patch("notes", args.id, { content: args.content });
return null;
},
});
// GOOD: Use Promise.all for parallel independent updates
export const reorderItems = mutation({
args: { itemIds: v.array(v.id("items")) },
returns: v.null(),
handler: async (ctx, args) => {
const updates = args.itemIds.map((id, index) =>
ctx.db.patch("items", id, { order: index }),
);
await Promise.all(updates);
return null;
},
});#
Official Convex plugins for AI coding agents, providing development tools for building reactive backends with TypeScript.
Repo: waynesutton/convexskills
Prevent feature creep when building software, apps, and AI-powered products. Use this skill when planning features, reviewing scope, building MVPs, managing…
Building AI agents with the Convex Agent component including thread management, tool integration, streaming responses, RAG patterns, and workflow orchestration
How to create, structure, and publish self-contained Convex components with proper isolation, exports, and dependency management
Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for…
Complete file handling including upload flows, serving files via URL, storing generated files from actions, deletion, and accessing file metadata from system…
Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations