convex-functions
Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | convex-functions |
| displayName | Convex Functions |
| description | Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations |
| version | 1.0.0 |
| author | Convex |
| tags | ["convex","functions","queries","mutations","actions","http"] |
Master Convex functions including queries, mutations, actions, and HTTP endpoints with proper validation, error handling, and runtime considerations.
All examples in this skill comply with @convex-dev/eslint-plugin rules:
handler propertySee the Code Quality section in convex-best-practices for linting setup.
Before implementing, do not assume; fetch the latest documentation:
| Type | Database Access | External APIs | Caching | Use Case |
|---|---|---|---|---|
| Query | Read-only | No | Yes, reactive | Fetching data |
| Mutation | Read/Write | No | No | Modifying data |
| Action | Via runQuery/runMutation | Yes | No | External integrations |
| HTTP Action | Via runQuery/runMutation | Yes | No | Webhooks, APIs |
Queries are reactive, cached, and read-only:
import { query } from "./_generated/server";
import { v } from "convex/values";
export const getUser = 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);
},
});
// Query with index
export const listUserTasks = query({
args: { userId: v.id("users") },
returns: v.array(
v.object({
_id: v.id("tasks"),
_creationTime: v.number(),
title: v.string(),
completed: v.boolean(),
}),
),
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.order("desc")
.collect();
},
});
Mutations modify the database and are transactional:
import { mutation } from "./_generated/server";
import { v } from "convex/values";
import { ConvexError } from "convex/values";
export const createTask = mutation({
args: {
title: v.string(),
userId: v.id("users"),
},
returns: v.id("tasks"),
handler: async (ctx, args) => {
// Validate user exists
const user = await ctx.db.get("users", args.userId);
if (!user) {
throw new ConvexError("User not found");
}
return await ctx.db.insert("tasks", {
title: args.title,
userId: args.userId,
completed: false,
createdAt: Date.now(),
});
},
});
export const deleteTask = mutation({
args: { taskId: v.id("tasks") },
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.delete("tasks", args.taskId);
return null;
},
});
Actions can call external APIs but have no direct database access:
"use node";
import { action } from "./_generated/server";
import { v } from "convex/values";
import { api, internal } from "./_generated/api";
export const sendEmail = action({
args: {
to: v.string(),
subject: v.string(),
body: v.string(),
},
returns: v.object({ success: v.boolean() }),
handler: async (ctx, args) => {
// Call external API
const response = await fetch("https://api.email.com/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args),
});
return { success: response.ok };
},
});
// Action calling queries and mutations
export const processOrder = action({
args: { orderId: v.id("orders") },
returns: v.null(),
handler: async (ctx, args) => {
// Read data via query
const order = await ctx.runQuery(api.orders.get, { orderId: args.orderId });
if (!order) {
throw new Error("Order not found");
}
// Call external payment API
const paymentResult = await processPayment(order);
// Update database via mutation
await ctx.runMutation(internal.orders.updateStatus, {
orderId: args.orderId,
status: paymentResult.success ? "paid" : "failed",
});
return null;
},
});
HTTP actions handle webhooks and external requests:
// convex/http.ts
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
import { api, internal } from "./_generated/api";
const http = httpRouter();
// Webhook endpoint
http.route({
path: "/webhooks/stripe",
method: "POST",
handler: httpAction(async (ctx, request) => {
const signature = request.headers.get("stripe-signature");
const body = await request.text();
// Verify webhook signature
if (!verifyStripeSignature(body, signature)) {
return new Response("Invalid signature", { status: 401 });
}
const event = JSON.parse(body);
// Process webhook
await ctx.runMutation(internal.payments.handleWebhook, {
eventType: event.type,
data: event.data,
});
return new Response("OK", { status: 200 });
}),
});
// API endpoint
http.route({
path: "/api/users/:userId",
method: "GET",
handler: httpAction(async (ctx, request) => {
const url = new URL(request.url);
const userId = url.pathname.split("/").pop();
const user = await ctx.runQuery(api.users.get, {
userId: userId as Id<"users">,
});
if (!user) {
return new Response("Not found", { status: 404 });
}
return Response.json(user);
}),
});
export default http;
Use internal functions for sensitive operations:
import { internalMutation, internalQuery, internalAction } from "./_generated/server";
import { v } from "convex/values";
// Only callable from other Convex functions
export const _updateUserCredits = internalMutation({
args: {
userId: v.id("users"),
amount: v.number(),
},
returns: v.null(),
handler: async (ctx, args) => {
const user = await ctx.db.get("users", args.userId);
if (!user) return null;
await ctx.db.patch("users", args.userId, {
credits: (user.credits || 0) + args.amount,
});
return null;
},
});
// Call internal function from action
export const purchaseCredits = action({
args: { userId: v.id("users"), amount: v.number() },
returns: v.null(),
handler: async (ctx, args) => {
// Process payment externally
await processPayment(args.amount);
// Update credits via internal mutation
await ctx.runMutation(internal.users._updateUserCredits, {
userId: args.userId,
amount: args.amount,
});
return null;
},
});
Schedule functions to run later:
import { mutation, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { internal } from "./_generated/api";
export const scheduleReminder = mutation({
args: {
userId: v.id("users"),
message: v.string(),
delayMs: v.number(),
},
returns: v.id("_scheduled_functions"),
handler: async (ctx, args) => {
return await ctx.scheduler.runAfter(args.delayMs, internal.notifications.sendReminder, {
userId: args.userId,
message: args.message,
});
},
});
export const sendReminder = internalMutation({
args: {
userId: v.id("users"),
message: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
await ctx.db.insert("notifications", {
userId: args.userId,
message: args.message,
sentAt: Date.now(),
});
return null;
},
});
// convex/messages.ts
import { query, mutation, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { ConvexError } from "convex/values";
import { internal } from "./_generated/api";
const messageValidator = v.object({
_id: v.id("messages"),
_creationTime: v.number(),
channelId: v.id("channels"),
authorId: v.id("users"),
content: v.string(),
editedAt: v.optional(v.number()),
});
// Public query
export const list = query({
args: {
channelId: v.id("channels"),
limit: v.optional(v.number()),
},
returns: v.array(messageValidator),
handler: async (ctx, args) => {
const limit = args.limit ?? 50;
return await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.order("desc")
.take(limit);
},
});
// Public mutation
export const send = mutation({
args: {
channelId: v.id("channels"),
authorId: v.id("users"),
content: v.string(),
},
returns: v.id("messages"),
handler: async (ctx, args) => {
if (args.content.trim().length === 0) {
throw new ConvexError("Message cannot be empty");
}
const messageId = await ctx.db.insert("messages", {
channelId: args.channelId,
authorId: args.authorId,
content: args.content.trim(),
});
// Schedule notification
await ctx.scheduler.runAfter(0, internal.messages.notifySubscribers, {
channelId: args.channelId,
messageId,
});
return messageId;
},
});
// Internal mutation
export const notifySubscribers = internalMutation({
args: {
channelId: v.id("channels"),
messageId: v.id("messages"),
},
returns: v.null(),
handler: async (ctx, args) => {
// Get channel subscribers and notify them
const subscribers = await ctx.db
.query("subscriptions")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.collect();
for (const sub of subscribers) {
await ctx.db.insert("notifications", {
userId: sub.userId,
messageId: args.messageId,
read: false,
});
}
return null;
},
});
npx convex deploy unless explicitly instructed"use node"; at the top of action files using Node.js APIsAudits and optimizes Convex application performance across hot-path reads, write contention, subscription cost, and function limits. Use this skill when a Convex feature is slow or expensive, npx convex insights shows high bytes or documents read, OCC conflict errors or mutation retries appear, subscriptions or UI updates are costly, functions hit execution or transaction limits, or the user mentions performance, latency, read amplification, or invalidation problems in a Convex app.
Initializes a new Convex project from scratch or adds Convex to an existing app. Use this skill when starting a new project with Convex, scaffolding with npm create convex@latest, adding Convex to an existing React, Next.js, Vue, Svelte, or other frontend, wiring up ConvexProvider, configuring environment variables for the deployment URL, or running npx convex dev for the first time, even if the user just says "set up Convex" or "add a backend."
Sets up Convex authentication with user management, identity mapping, and access control. Use this skill when adding login or signup to a Convex app, configuring Convex Auth, Clerk, WorkOS AuthKit, Auth0, or custom JWT providers, wiring auth.config.ts, protecting queries and mutations with ctx.auth.getUserIdentity(), creating a users table with identity mapping, or setting up role-based access control, even if the user just says "add auth" or "make it require login."
Guidelines for building production-ready Convex apps covering function organization, query patterns, validation, TypeScript usage, error handling, and the Zen of Convex design philosophy
Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for long-running tasks
Complete file handling including upload flows, serving files via URL, storing generated files from actions, deletion, and accessing file metadata from system tables