convex · mindrally — Claude Code skill: Convex backend guidelines
Tells the agent how to write Convex code: queries, mutations, actions, indexes and file uploads. Useful once Convex is your backend.
npx skills add Mindrally/skills --skill convex -a claude-codeWhat convex does
The skill loads a set of Convex conventions into your agent. Convex is a TypeScript backend-as-a-service, and the file covers its four main primitives: query, mutation, action and the HTTP router, plus schemas with indexes, cron jobs and file uploads through ctx.storage.generateUploadUrl().
Code style is spelled out too. Functional declarations, interfaces over types, union types instead of enums, variable names like isLoading and hasError. For the UI layer it points at Shadcn UI, Radix UI and Tailwind CSS, and asks you to keep useClient, useEffect and useState to a minimum. Errors get guard clauses at the top of a function, forms get Zod validation.
Performance has its own section. Filters give way to indexes: declare them in schema.ts with .index(), then call .withIndex() in the query. Large result sets go through .paginate(), single results through .first() instead of .collect(). External APIs belong in actions only — never in queries or mutations. If your codebase already calls fetch inside a query, that is worth a look.
What it helps with
- define a Convex table schema with indexes on status and user
- write a mutation that checks auth via
ctx.auth.getUserIdentity() - move an external API call out of a query into an action
- set up a cron job that cleans up old records
- upload a file to Convex storage and save its
storageId
Example prompts for the agent
- add an items table to the Convex schema with by_status and by_user indexes
- rewrite this query to use
.withIndex()instead of.filter() - create an action that sends an email through an external API
How to install convex
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill convex -a claude-code2Manually: put the files in place
~/.claude/skills/convex/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/convex/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill convex -a codex2Manually: put the files in place
.agents/skills/convex/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill convex -a cursor2Manually: put the files in place
.cursor/skills/convex/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Mindrally/skills convex2Manually: put the files in place
.github/skills/convex/SKILL.md1Skills CLI (Vercel)
npx skills add Mindrally/skills --skill convex -a gemini-cli2Platform CLI
gemini skills install https://github.com/Mindrally/skills --path convexManually: put the files in place
.devin/skills/convex/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Mindrally/skills --skill convex -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about convex
How do I install the convex skill in Claude?
Run npx skills add Mindrally/skills --skill convex -a claude-code. The skill lands at ~/.claude/skills/convex/SKILL.md.
What does the convex skill do?
It gives the agent Convex development rules: how to write queries, mutations and actions, how to declare a schema with indexes, and how to handle files and cron jobs.
Can a Convex query call an external API?
No. The skill pushes side effects into actions and keeps queries and mutations clean.
Source text
SKILL.md · Apache-2.0
---
name: convex
description: Guidelines for developing with Convex backend-as-a-service platform, covering queries, mutations, actions, and real-time data patterns
metadata:
maintainer: Mindrally
source: https://github.com/Mindrally/skills
---
# Convex Development Guidelines
You are an expert in Convex backend development, TypeScript, and real-time data synchronization patterns.
## General Development Specifications
### Code Style and Structure
- Write concise TypeScript using functional declarations, iterators, and modules
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
- Structure code with exported components, subcomponents, helpers, and static types
- Use dash-case for directories with named exports
- Prefer interfaces over types; avoid enums in favor of union types
- Use functional components with declarative JSX patterns
### Error Handling
- Handle errors early in functions with guard clauses
- Log errors appropriately for debugging
- Provide user-friendly error messages
- Use Zod for form validation
- Implement proper error boundaries in React components
### UI Framework Integration
- Use Shadcn UI and Radix UI for component primitives
- Style with Tailwind CSS using responsive, mobile-first design
- Minimize useClient, useEffect, and useState usage
- Leverage React Server Components where applicable
- Use Suspense for loading states and dynamic loading for code splitting
## Convex-Specific Patterns
### Queries
Structure queries using the `query` constructor:
```typescript
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getItems = query({
args: {
status: v.optional(v.string()),
},
handler: async (ctx, args) => {
// ctx provides: db, storage, auth
const identity = await ctx.auth.getUserIdentity();
if (args.status) {
return await ctx.db
.query("items")
.withIndex("by_status", (q) => q.eq("status", args.status))
.collect();
}
return await ctx.db.query("items").collect();
},
});
```
**Important**: Prefer Convex indexes over filters for better performance. Define indexes in schema.ts using the `.index()` method, then query with `.withIndex()`.
### Mutations
Structure mutations for database writes:
```typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";
export const createItem = mutation({
args: {
title: v.string(),
description: v.optional(v.string()),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new Error("Not authenticated");
}
return await ctx.db.insert("items", {
title: args.title,
description: args.description,
userId: identity.subject,
createdAt: Date.now(),
});
},
});
```
### Actions
Use actions for external API calls and side effects:
```typescript
import { action } from "./_generated/server";
import { v } from "convex/values";
export const sendEmail = action({
args: {
to: v.string(),
subject: v.string(),
body: v.string(),
},
handler: async (ctx, args) => {
// Actions can call external APIs
const response = await fetch("https://api.email-service.com/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
return response.ok;
},
});
```
### Schema Definition with Indexes
```typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
items: defineTable({
title: v.string(),
description: v.optional(v.string()),
status: v.string(),
userId: v.string(),
createdAt: v.number(),
})
.index("by_status", ["status"])
.index("by_user", ["userId"])
.index("by_user_and_status", ["userId", "status"]),
});
```
### HTTP Router
Define HTTP routes for webhooks and external integrations:
```typescript
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
const http = httpRouter();
http.route({
path: "/webhook",
method: "POST",
handler: httpAction(async (ctx, request) => {
const body = await request.json();
// Process webhook
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}),
});
export default http;
```
### Scheduled Jobs
Implement cron jobs for recurring tasks:
```typescript
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
// Run every hour
crons.interval(
"cleanup-old-items",
{ hours: 1 },
internal.tasks.cleanupOldItems
);
// Run at specific time (daily at midnight UTC)
crons.monthly(
"monthly-report",
{ day: 1, hourUTC: 0, minuteUTC: 0 },
internal.reports.generateMonthlyReport
);
export default crons;
```
### File Handling
Three-step process for file uploads:
```typescript
// 1. Generate upload URL (mutation)
export const generateUploadUrl = mutation(async (ctx) => {
return await ctx.storage.generateUploadUrl();
});
// 2. Client POSTs file to the URL
// const uploadUrl = await generateUploadUrl();
// const response = await fetch(uploadUrl, { method: "POST", body: file });
// const { storageId } = await response.json();
// 3. Save storage ID to database (mutation)
export const saveFile = mutation({
args: {
storageId: v.id("_storage"),
filename: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.insert("files", {
storageId: args.storageId,
filename: args.filename,
});
},
});
```
## Best Practices
1. **Always use indexes** for queries that filter or sort data
2. **Validate arguments** using Convex validators (`v.string()`, `v.number()`, etc.)
3. **Check authentication** early in handlers that require it
4. **Use internal functions** for operations that should not be exposed to clients
5. **Leverage real-time subscriptions** - Convex queries automatically update when data changes
6. **Keep mutations small** and focused on single operations
7. **Use actions for side effects** - never call external APIs from queries or mutations
8. **Handle errors gracefully** with proper error messages for users
## Performance Considerations
- Use `.withIndex()` instead of `.filter()` whenever possible
- Paginate large result sets using `.paginate()`
- Use `.first()` instead of `.collect()` when expecting a single result
- Consider data denormalization for frequently accessed data
- Use Convex's built-in caching - avoid implementing your own
© Mindrally · Apache-2.0 · Mindrally/skills
Similar skills
Everything for Backend and APIs →convex by other authors comes first.