
Convex Best Practices
- 3.5k installs
- 401 repo stars
- Updated February 6, 2026
- waynesutton/convexskills
convex-best-practices is an agent skill for apply convex production patterns for functions, queries, validation, typescript, and zen of convex design.
About
The convex-best-practices 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. 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: ``bash npm i @convex-dev/eslint-plugin --save-dev `` ```js // eslint.config.js import { defineConfig } from "eslint/config"; import convexPlugin from "@convex-dev/eslint-plugin"; js // eslint.config.js import { defineConfig } from "eslint/config"; import convexPlugin from "@convex-dev/eslint-plugin"; export default defineConfig([ ...convexPlugin.configs.recommended, ]); 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 Before implementing.
- Primary: https://docs.convex.dev/understanding/best-practices/
- Error Handling: https://docs.convex.dev/functions/error-handling
- Write Conflicts: https://docs.convex.dev/error 1
- For broader context: https://docs.convex.dev/llms.txt
- Convex manages the hard parts - Let Convex handle caching, real-time sync, and consistency
Convex Best Practices by the numbers
- 3,547 all-time installs (skills.sh)
- +43 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #170 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 3, 2026 (Skillselion catalog sync)
convex-best-practices capabilities & compatibility
- Capabilities
- primary: https://docs.convex.dev/understanding/b · error handling: https://docs.convex.dev/function · write conflicts: https://docs.convex.dev/error 1 · for broader context: https://docs.convex.dev/llm · convex manages the hard parts let convex handl
- Use cases
- api development · database
What convex-best-practices says it does
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:
import { defineConfig } from "eslint/config";
npx skills add https://github.com/waynesutton/convexskills --skill convex-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.5k |
|---|---|
| repo stars | ★ 401 |
| Security audit | 3 / 3 scanners passed |
| Last updated | February 6, 2026 |
| Repository | waynesutton/convexskills ↗ |
How do I apply convex production patterns for functions, queries, validation, typescript, and zen of convex design with documented agent guidance?
Apply Convex production patterns for functions, queries, validation, TypeScript, and Zen of Convex design.
Who is it for?
Developers who need backend & apis help during build work.
Skip if: Skip when the task falls outside Backend & APIs scope described in SKILL.md.
When should I use this skill?
Apply Convex production patterns for functions, queries, validation, TypeScript, and Zen of Convex design.
What you get
Completed backend & apis workflow aligned with SKILL.md steps and validation.
- Convex schema conventions
- Query and mutation patterns
- Indexing recommendations
By the numbers
- Primary: https://docs.convex.dev/understanding/best-practices/
- Error Handling: https://docs.convex.dev/functions/error-handling
- Write Conflicts: https://docs.convex.dev/error 1
Files
Convex Best Practices
Build production-ready Convex applications by following established patterns for function organization, query optimization, validation, TypeScript usage, and error handling.
Code Quality
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
Documentation Sources
Before implementing, do not assume; fetch the latest documentation:
- Primary: https://docs.convex.dev/understanding/best-practices/
- Error Handling: https://docs.convex.dev/functions/error-handling
- Write Conflicts: https://docs.convex.dev/error#1
- For broader context: https://docs.convex.dev/llms.txt
Instructions
The Zen of Convex
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
Function Organization
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);
},
});Argument and Return Validation
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(),
});
},
});Query Patterns
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();
},
});Error Handling
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;
},
});Avoiding Write Conflicts (Optimistic Concurrency Control)
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;
},
});TypeScript Best Practices
import { Id, Doc } from "./_generated/dataModel";
// Use Id type for document references
type UserId = Id<"users">;
// Use Doc type for full documents
type User = Doc<"users">;
// Define Record types properly
const userScores: Record<Id<"users">, number> = {};Internal vs Public Functions
// Public function - exposed to clients
export const getUser = query({
args: { userId: v.id("users") },
returns: v.union(
v.null(),
v.object({
/* ... */
}),
),
handler: async (ctx, args) => {
// ...
},
});
// Internal function - only callable from other Convex functions
export const _updateUserStats = internalMutation({
args: { userId: v.id("users") },
returns: v.null(),
handler: async (ctx, args) => {
// ...
},
});Examples
Complete CRUD Pattern
// convex/tasks.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { ConvexError } from "convex/values";
const taskValidator = v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
title: v.string(),
completed: v.boolean(),
userId: v.id("users"),
});
export const list = query({
args: { userId: v.id("users") },
returns: v.array(taskValidator),
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.collect();
},
});
export const create = mutation({
args: {
title: v.string(),
userId: v.id("users"),
},
returns: v.id("tasks"),
handler: async (ctx, args) => {
return await ctx.db.insert("tasks", {
title: args.title,
completed: false,
userId: args.userId,
});
},
});
export const update = mutation({
args: {
taskId: v.id("tasks"),
title: v.optional(v.string()),
completed: v.optional(v.boolean()),
},
returns: v.null(),
handler: async (ctx, args) => {
const { taskId, ...updates } = args;
// Remove undefined values
const cleanUpdates = Object.fromEntries(
Object.entries(updates).filter(([_, v]) => v !== undefined),
);
if (Object.keys(cleanUpdates).length > 0) {
await ctx.db.patch("tasks", taskId, cleanUpdates);
}
return null;
},
});
export const remove = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.delete("tasks", args.taskId);
return null;
},
});Best Practices
- Never run
npx convex deployunless explicitly instructed - Never run any git commands unless explicitly instructed
- Always define return validators for functions
- Use indexes for all queries that filter data
- Make mutations idempotent to handle retries gracefully
- Use ConvexError for user-facing error messages
- Organize functions by domain (users.ts, tasks.ts, etc.)
- Use internal functions for sensitive operations
- Leverage TypeScript's Id and Doc types
Common Pitfalls
1. Using filter instead of withIndex - Always define indexes and use withIndex 2. Missing return validators - Always specify the returns field 3. Non-idempotent mutations - Check current state before updating 4. Reading before patching unnecessarily - Patch directly when possible 5. Not handling null returns - Document IDs might not exist
References
- Convex Documentation: https://docs.convex.dev/
- Convex LLMs.txt: https://docs.convex.dev/llms.txt
- Best Practices: https://docs.convex.dev/understanding/best-practices/
- Error Handling: https://docs.convex.dev/functions/error-handling
- Write Conflicts: https://docs.convex.dev/error#1
interface:
icon_small: "./assets/small-logo.svg"
icon_large: "./assets/large-logo.png"
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_23)">
<g clip-path="url(#clip1_3_23)">
<path d="M10.0643 12.5735C12.3769 12.3166 14.5572 11.0843 15.7577 9.02756C15.1892 14.1148 9.62646 17.3302 5.08583 15.356C4.66743 15.1746 4.30728 14.8728 4.06013 14.4848C3.03973 12.8825 2.7043 10.8437 3.18626 8.99344C4.56327 11.37 7.3632 12.8267 10.0643 12.5735Z" fill="#F3B01C"/>
<path d="M3.1018 7.50072C2.16436 9.66714 2.12376 12.2034 3.27303 14.2907C-0.771507 11.2479 -0.72737 4.7362 3.2236 1.72378C3.58904 1.44535 4.02333 1.2801 4.47881 1.25494C6.3519 1.15614 8.25501 1.88006 9.58963 3.22909C6.87799 3.25604 4.23695 4.99308 3.1018 7.50072Z" fill="#8D2676"/>
<path d="M10.8974 3.89562C9.52924 1.98794 7.38779 0.68921 5.04156 0.649695C9.57686 -1.40888 15.1555 1.92867 15.7629 6.86314C15.8194 7.32119 15.7452 7.78824 15.5421 8.20138C14.6948 9.92223 13.1236 11.2569 11.2876 11.7508C12.6328 9.25579 12.4668 6.20748 10.8974 3.89562Z" fill="#EE342F"/>
</g>
</g>
<defs>
<clipPath id="clip0_3_23">
<rect width="16" height="16" fill="white"/>
</clipPath>
<clipPath id="clip1_3_23">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
Related skills
Forks & variants (1)
Convex Best Practices has 1 known copy in the catalog totaling 30 installs. They canonicalize to this original listing.
- julianromli - 30 installs
How it compares
convex-best-practices is an agent skill for apply convex production patterns for functions, queries, validation, typescript, and zen of convex design, not a generic alternative.
FAQ
Who is convex-best-practices for?
Developers using Backend & APIs workflows with agent-guided SKILL.md steps.
When should I use convex-best-practices?
Apply Convex production patterns for functions, queries, validation, TypeScript, and Zen of Convex design.
Is convex-best-practices safe to install?
Review the Security Audits panel on this page before installing in production.