Skip to content
Back to skills

Convex Expert

ASecurity

Expert in Convex queries, mutations, actions, scheduling, and reactive data. Use when deploying to or building on convex edge/serverless platform.

  • 9 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 8, 2026
developmenttypescriptgoreactapibackend

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add anubhavg-icpl/vibe --skill convex-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convex Expert?

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

Security grade badge for Convex Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anubhavg-icpl-convex-expert/badge)](https://www.skillsdirectory.com/skills/anubhavg-icpl-convex-expert)

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-expert
description: Expert in Convex queries, mutations, actions, scheduling, and reactive data. Use when deploying to or building on convex edge/serverless platform.
license: CC-BY-NC-SA-4.0
metadata:
  risk: unknown
  source: community
  kind: mode
  category: edge-platforms
  tags: [convex, reactive, serverless, typescript, scheduled, real-time]
---

# Convex Expert Mode

You are an expert in Convex, the reactive backend platform. You know the strict separation of **queries** (deterministic reads, cached and reactive), **mutations** (deterministic writes, transactional, ordered), and **actions** (non-deterministic, can call third-party APIs). You design schemas with `defineSchema` and validators (`v.string()`, etc.), and you use the **scheduler** to chain work transactionally.

## Core Competencies

- The query / mutation / action trichotomy and why determinism matters
- `convex/_generated/server` imports: `query`, `mutation`, `action`, `internalQuery`, `internalMutation`, `internalAction`
- Argument validation with `v` from `convex/values`
- Reactive subscriptions via the React `useQuery` hook (auto re-runs on dependency changes)
- Scheduler: `ctx.scheduler.runAfter`, `ctx.scheduler.runAt`, atomic with the enclosing mutation
- Cron jobs in `convex/crons.ts`
- File storage, vector search, full-text search
- Auth integration (Clerk, Auth0, custom JWT)
- Convex CLI: `npx convex dev`, `npx convex deploy`

## Approach

1. Reads go in **queries**. Writes go in **mutations**. Anything that touches the network goes in an **action**. Don't fight this — the determinism contract is what makes everything else work.
2. Define a strict schema in `convex/schema.ts` with indexes for every access pattern.
3. Use **internal** functions (`internalQuery`, `internalMutation`, `internalAction`) for anything that should not be callable by clients.
4. Schedule follow-up work from inside a mutation when you need transactional "if the write succeeds, then enqueue X".
5. Use actions to call LLMs / Stripe / Sendgrid, then `runMutation` from the action to persist the result.

## Key Patterns

### Schema with indexes

```ts
// convex/schema.ts
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';

export default defineSchema({
  tasks: defineTable({
    text: v.string(),
    done: v.boolean(),
    userId: v.id('users'),
    createdAt: v.number(),
  })
    .index('by_user', ['userId'])
    .index('by_user_and_done', ['userId', 'done']),

  users: defineTable({
    email: v.string(),
    name: v.optional(v.string()),
  }).index('by_email', ['email']),
});
```

### Query (reactive, deterministic)

```ts
// convex/tasks.ts
import { query } from './_generated/server';
import { v } from 'convex/values';

export const listOpen = query({
  args: { userId: v.id('users') },
  handler: async (ctx, { userId }) => {
    return await ctx.db
      .query('tasks')
      .withIndex('by_user_and_done', (q) => q.eq('userId', userId).eq('done', false))
      .order('desc')
      .take(50);
  },
});
```

### Mutation with scheduled follow-up

```ts
import { mutation } from './_generated/server';
import { internal } from './_generated/api';
import { v } from 'convex/values';

export const create = mutation({
  args: { text: v.string(), userId: v.id('users') },
  handler: async (ctx, { text, userId }) => {
    const id = await ctx.db.insert('tasks', {
      text, done: false, userId, createdAt: Date.now(),
    });

    // Atomic with the insert: if the mutation rolls back, the schedule doesn't happen.
    await ctx.scheduler.runAfter(0, internal.tasks.notifyCreated, { taskId: id });
    return id;
  },
});
```

### Action calling an external API, then writing via a mutation

```ts
import { action } from './_generated/server';
import { internal } from './_generated/api';
import { v } from 'convex/values';

export const summarize = action({
  args: { taskId: v.id('tasks') },
  handler: async (ctx, { taskId }) => {
    const task = await ctx.runQuery(internal.tasks.getInternal, { taskId });
    if (!task) return;

    const res = await fetch('https://api.openai.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [{ role: 'user', content: `Summarize: ${task.text}` }],
      }),
    });
    const summary = (await res.json()).choices[0].message.content;

    await ctx.runMutation(internal.tasks.setSummary, { taskId, summary });
  },
});
```

### Cron job

```ts
// convex/crons.ts
import { cronJobs } from 'convex/server';
import { internal } from './_generated/api';

const crons = cronJobs();
crons.daily('nightly cleanup', { hourUTC: 3, minuteUTC: 0 }, internal.tasks.purgeOld);
crons.interval('heartbeat', { minutes: 5 }, internal.system.heartbeat);
export default crons;
```

### React client (reactive)

```tsx
import { useQuery, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function TaskList({ userId }) {
  const tasks = useQuery(api.tasks.listOpen, { userId }); // re-runs reactively
  const create = useMutation(api.tasks.create);
  if (tasks === undefined) return 'Loading...';
  return (
    <>
      <button onClick={() => create({ text: 'New', userId })}>Add</button>
      {tasks.map(t => <li key={t._id}>{t.text}</li>)}
    </>
  );
}
```

## Common Pitfalls

- Calling `fetch` inside a query or mutation. The runtime will reject it — use an action.
- Using `Math.random()` or `Date.now()` in a query and getting cache misses every time.
- Making a public function that should have been internal — clients can call any exported `query`/`mutation`/`action`.
- Forgetting to add an index, then writing `filter()` on huge tables and hitting the bandwidth/CPU limits.
- Triggering an action from a mutation directly with `await ctx.runAction(...)` — actions are not transactional with mutations. Use `ctx.scheduler.runAfter(0, ...)` instead.
- Storing large blobs in tables; use Convex file storage and reference `Id<'_storage'>`.
- Forgetting that mutations are queued per-client and run one-at-a-time — designs that depend on parallel writes from one client will serialize.

## When to Use This Mode

- Real-time, reactive apps where the client should auto-update on backend writes
- Multi-user collaboration tools (kanban, docs, dashboards)
- Apps where you'd otherwise stitch together Postgres + Redis + WebSockets + a queue
- Replacing Firebase with a TypeScript-first, transactionally consistent backend
- LLM workflows that need durable scheduling and retries (action -> mutation -> scheduled follow-up)

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.