convex

Expert Convex development assistant. Use when users want to build full-stack TypeScript apps with Convex - including setup, server functions (queries/mutations/actions), database schema, auth, file storage, real-time features, frontend integration (React, Next.js, Vue), testing, or deployment.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "convex" with this command: npx skills add dimitrigilbert/ai-skills/dimitrigilbert-ai-skills-convex

This skill provides expert guidance for building applications with Convex - the full-stack reactive database platform.

Quick Start

When a user wants to start with Convex:

  1. New project:

    npm create convex@latest
    npx convex dev
    
  2. Add to existing project:

    npm install convex
    npx convex dev
    
  3. Verify: Check dashboard at https://dashboard.convex.dev

Core Concepts

Three Function Types

Queries - Read-only, auto-reactive:

import { query } from "./_generated/server";

export const list = query({
  args: { channelId: v.id("channels") },
  handler: async ({ db }, { channelId }) => {
    return await db.query("messages")
      .withIndex("by_channel", (q) => q.eq("channel", channelId))
      .collect();
  }
});

Mutations - Write operations, transactions:

import { mutation } from "./_generated/server";

export const send = mutation({
  args: { body: v.string(), channel: v.id("channels") },
  handler: async ({ db }, { body, channel }) => {
    await db.insert("messages", { body, channel, createdAt: Date.now() });
  }
});

Actions - External API calls:

import { action } from "./_generated/server";

export const summarize = action({
  args: { postId: v.id("posts") },
  handler: async (ctx, { postId }) => {
    const post = await ctx.runQuery(api.posts.get, { postId });
    const summary = await fetchLLM(post);
    await ctx.runMutation(api.posts.update, { postId, summary });
  }
});

Database Operations

// CRUD
await db.insert("table", { field: value });
const doc = await db.get(id);
await db.patch(id, { field: newValue });
await db.delete(id);

// Query methods
.collect()  // All results
.first()   // First or null
.unique()  // Exactly one (throws if not)
.paginate({ numItems: 50 })  // Pagination

Schema Definition

File: convex/schema.ts

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  messages: defineTable({
    body: v.string(),
    author: v.id("users"),
    channel: v.id("channels"),
    createdAt: v.number()
  })
  .index("by_channel", ["channel"])
  .index("by_channel_created", ["channel", "createdAt"])
});

Common types:

  • v.string(), v.number(), v.boolean()
  • v.id("table") - Foreign key
  • v.array(T), v.object({})
  • v.optional(T), v.union(...)

Frontend Integration

React

import { ConvexProvider, ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

<ConvexProvider client={convex}>
  <App />
</ConvexProvider>

Usage:

import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

const messages = useQuery(api.messages.list, { channelId });
const sendMessage = useMutation(api.messages.send);

Next.js App Router

Server-side preloading:

import { preloadQuery } from "convex/nextjs";

const preloaded = await preloadQuery(api.posts.list);
return <ClientPage preloaded={preloaded} />;

Client component:

"use client";
import { usePreloadedQuery } from "convex/react";

const posts = usePreloadedQuery(preloaded);

Authentication

Clerk (Recommended)

Install: npm install @clerk/clerk-react

File: convex/auth.config.ts

import { AuthConfig } from "convex/server";

export default {
  providers: [{ domain: process.env.CLERK_JWT_ISSUER_DOMAIN!, applicationID: "convex" }]
} satisfies AuthConfig;

Frontend:

import { ConvexProviderWithClerk } from "convex/react-clerk";

<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
  <App />
</ConvexProviderWithClerk>

In functions:

const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");

File Storage

// Upload
const storageId = await ctx.storage.store(fileBlob);
await db.insert("files", { storageId, name });

// Download
const url = await ctx.storage.getUrl(storageId);

// Delete
await ctx.storage.delete(storageId);

HTTP Endpoints

File: convex/http.ts

import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";

const http = httpRouter();

http.route({
  path: "/webhooks/stripe",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    const payload = await request.json();
    await ctx.runMutation(internal.payments.process, { payload });
    return new Response(null, { status: 200 });
  }),
});

export default http;

URL: https://<deployment>.convex.site

Scheduled Tasks

Cron Jobs (File: convex/crons.ts):

import { cronJobs } from "convex/server";

const crons = cronJobs();
crons.interval("cleanup", { hours: 1 }, internal.tasks.cleanup);
crons.daily("digest", { hourUTC: 9 }, internal.notifications.send);
export default crons;

Scheduler API (in mutations/actions):

// Schedule for later
await ctx.scheduler.runAfter(delayMs, internal.tasks.process, { id });

// Schedule at specific time
await ctx.scheduler.runAt(timestamp, internal.tasks.remind, { userId });

Best Practices

✅ DO

  • Use indexes for queried fields
  • Filter in application code, not with .filter() on queries
  • Validate auth in protected functions
  • Use pagination for large datasets
  • Use ConvexError for structured errors clients can handle
  • Direct function calls, not multiple sequential ctx.runQuery

❌ DON'T

  • Use .filter() on queries (use indexes or filter in code)
  • Make multiple sequential runQuery/runMutation calls (consolidate)
  • Forget to validate authentication
  • Fetch entire tables without pagination

Error Handling

import { ConvexError } from "convex/values";

// Throw structured errors
if (!identity) {
  throw new ConvexError({ code: "UNAUTHORIZED", message: "Login required" });
}

// Client catches and handles
if (error instanceof ConvexError) {
  console.error(error.data.code, error.data.message);
}

Deployment

Production:

npx convex deploy

With frontend build:

npx convex deploy --cmd 'npm run build'

Environment: Set CONVEX_DEPLOY_KEY for production

Testing

Install: npm install convex-test vitest --save-dev

import { convexTest } from "convex-test";

const t = convexTest(schema);

// Test mutations
await t.mutation(api.posts.create, { title: "Test" });

// Test queries
const posts = await t.query(api.posts.list);
expect(posts).toHaveLength(1);

Common Issues

ProblemSolution
Function not foundRun npx convex dev to regenerate types
Real-time not workingCheck ConvexProvider wraps app
Type errorsRestart TypeScript server, regenerate types
Deploy failsVerify CONVEX_DEPLOY_KEY is set

Progressive Disclosure

Advanced topics in references:

Your Approach

  1. Assess first: New project or existing? Framework? Goals?
  2. Start simple: Quick start → add complexity progressively
  3. Embrace reactivity: Leverage automatic real-time updates
  4. Type safety: Use TypeScript validation throughout
  5. Index for performance: Always use indexes on queried fields
  6. Validate auth: Check authentication in protected functions
  7. Test before deploying: Use convex-test for unit tests

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

opencode

No summary provided by upstream source.

Repository SourceNeeds Review
General

not-ai-writer

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

agents-md

No summary provided by upstream source.

Repository SourceNeeds Review
General

gh-profile

No summary provided by upstream source.

Repository SourceNeeds Review