Skip to content
Back to skills

Convex

ASecurity

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.

  • 57 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 17, 2026
developmenttypescriptgobashreactvuenextjstestingapidatabasefrontend

Works with

  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill convex --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convex?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Convex
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-convex-awesome-omni-skill/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-convex-awesome-omni-skill)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: convex
description: 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.
---

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:**
   ```bash
   npm create convex@latest
   npx convex dev
   ```

2. **Add to existing project:**
   ```bash
   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:
```typescript
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:
```typescript
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:
```typescript
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

```typescript
// 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`

```typescript
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

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

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

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

**Usage:**
```typescript
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:**
```typescript
import { preloadQuery } from "convex/nextjs";

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

**Client component:**
```typescript
"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`
```typescript
import { AuthConfig } from "convex/server";

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

**Frontend:**
```typescript
import { ConvexProviderWithClerk } from "convex/react-clerk";

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

**In functions:**
```typescript
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Unauthorized");
```

## File Storage

```typescript
// 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`

```typescript
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`):**
```typescript
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):**
```typescript
// 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

```typescript
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:**
```bash
npx convex deploy
```

**With frontend build:**
```bash
npx convex deploy --cmd 'npm run build'
```

**Environment:** Set `CONVEX_DEPLOY_KEY` for production

## Testing

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

```typescript
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

| Problem | Solution |
|---------|----------|
| Function not found | Run `npx convex dev` to regenerate types |
| Real-time not working | Check ConvexProvider wraps app |
| Type errors | Restart TypeScript server, regenerate types |
| Deploy fails | Verify `CONVEX_DEPLOY_KEY` is set |

## Progressive Disclosure

**Advanced topics in references:**
- **Complete setup**: [QUICK_START.md](references/QUICK_START.md)
- **Deep dive on functions**: [CORE_CONCEPTS.md](references/CORE_CONCEPTS.md)
- **Auth patterns**: [AUTH.md](references/AUTH.md)
- **Framework integration**: [FRONTEND.md](references/FRONTEND.md)
- **Performance patterns**: [BEST_PRACTICES.md](references/BEST_PRACTICES.md)
- **Production deployment**: [DEPLOYMENT.md](references/DEPLOYMENT.md)
- **Troubleshooting**: [TROUBLESHOOTING.md](references/TROUBLESHOOTING.md)
- **HTTP endpoints, cron, vector search, Convex Auth**: [ADVANCED.md](references/ADVANCED.md)

## 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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.