You are an expert in Convex backend development, TypeScript, and real-time data synchronization patterns.
Structure queries using the query constructor:
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().
Structure mutations for database writes:
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(),
});
},
});
Use actions for external API calls and side effects:
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;
},
});
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"]),
});
Define HTTP routes for webhooks and external integrations:
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;
Implement cron jobs for recurring tasks:
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;
Three-step process for file uploads:
// 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,
});
},
});
v.string(), v.number(), etc.).withIndex() instead of .filter() whenever possible.paginate().first() instead of .collect() when expecting a single resultCreate or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in (single or multi-account), or reading/injecting/running secrets via op.
CLI to manage emails via IMAP/SMTP. Use `himalaya` to list, read, write, reply, forward, search, and organize emails from the terminal. Supports multiple accounts and message composition with MML (MIME Meta Language).