[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6AEG4JYXK2K7GR59DFC1P2C":3,"skill-same:01M6AEG4JYXK2K7GR59DFC1P2C":77},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":45,"linkOnly":46,"markdownContent":47,"markdownBody":48,"frontMatter":49,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":56,"repositoryHtmlUrl":57,"repositoryHomepageUrl":58,"ownerHtmlUrl":59,"ownerBlogUrl":60,"ownerDescription":61,"updatedAt":62,"install":63,"resources":76},"01M6AEG4JYXK2K7GR59DFC1P2C","convex",null,"Convex reactive backend expert: schema design, TypeScript functions, real-time subscriptions, auth, file storage, scheduling, and deployment.","davila7\u002Fclaude-code-templates","davila7","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6216945?v=4","01M6ADEWWFPS191NFM0Y1EX1JH","Daniel Avila","01M6ADJHGM78PHQRD3XZTFDVKK",33481,"MIT","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fdevelopment\u002Fconvex\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",[5,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44],"2026","design","reactive","time","expert","subscriptions","functions","scheduling","schema design","schema","backend","real-time","file","https","dev","deployment","auth","storage","typescript","safe","docs","2026-11-10 10:12:30",false,"---\nname: convex\ndescription: \"Convex reactive backend expert: schema design, TypeScript functions, real-time subscriptions, auth, file storage, scheduling, and deployment.\"\nmetadata:\n  risk: safe\n  source: \"https:\u002F\u002Fdocs.convex.dev\"\n  date_added: \"2026-02-27\"\n---\n\n# Convex\n\nYou are an expert in Convex — the open-source, reactive backend platform where queries are TypeScript code. You have deep knowledge of schema design, function authoring (queries, mutations, actions), real-time data subscriptions, authentication, file storage, scheduling, and deployment workflows across React, Next.js, Angular, Vue, Svelte, React Native, and server-side environments.\n\n## When to Use\n- Use when building a new project with Convex as the backend\n- Use when adding Convex to an existing React, Next.js, Angular, Vue, Svelte, or React Native app\n- Use when designing schemas for a Convex document-relational database\n- Use when writing or debugging Convex functions (queries, mutations, actions)\n- Use when implementing real-time\u002Freactive data patterns\n- Use when setting up authentication with Convex Auth or third-party providers (Clerk, Auth0, etc.)\n- Use when working with Convex file storage, scheduled functions, or cron jobs\n- Use when deploying or managing Convex projects\n\n## Core Concepts\n\nConvex is a **document-relational** database with a fully managed backend. Key differentiators:\n\n- **Reactive by default**: Queries automatically re-run and push updates to all connected clients when underlying data changes\n- **TypeScript-first**: All backend logic — queries, mutations, actions, schemas — is written in TypeScript\n- **ACID transactions**: Serializable isolation with optimistic concurrency control\n- **No infrastructure to manage**: Serverless, scales automatically, zero config\n- **End-to-end type safety**: Types flow from schema → backend functions → client hooks\n\n### Function Types\n\n| Type            | Purpose                   | Can Read DB    | Can Write DB      | Can Call External APIs | Cached\u002FReactive |\n| :-------------- | :------------------------ | :------------- | :---------------- | :--------------------- | :-------------- |\n| **Query**       | Read data                 | ✅             | ❌                | ❌                     | ✅              |\n| **Mutation**    | Write data                | ✅             | ✅                | ❌                     | ❌              |\n| **Action**      | Side effects              | via `runQuery` | via `runMutation` | ✅                     | ❌              |\n| **HTTP Action** | Webhooks\u002Fcustom endpoints | via `runQuery` | via `runMutation` | ✅                     | ❌              |\n\n## Project Setup\n\n### New Project (Next.js)\n\n```bash\nnpx create-next-app@latest my-app\ncd my-app && npm install convex\nnpx convex dev\n```\n\n### Add to Existing Project\n\n```bash\nnpm install convex\nnpx convex dev\n```\n\nThe `npx convex dev` command:\n\n1. Prompts you to log in (GitHub)\n2. Creates a project and deployment\n3. Generates `convex\u002F` folder for backend functions\n4. Syncs functions to your dev deployment in real-time\n5. Creates `.env.local` with `CONVEX_DEPLOYMENT` and `NEXT_PUBLIC_CONVEX_URL`\n\n### Folder Structure\n\n```\nmy-app\u002F\n├── convex\u002F\n│   ├── _generated\u002F        ← Auto-generated (DO NOT EDIT)\n│   │   ├── api.d.ts\n│   │   ├── dataModel.d.ts\n│   │   └── server.d.ts\n│   ├── schema.ts          ← Database schema definition\n│   ├── tasks.ts           ← Query\u002Fmutation functions\n│   └── http.ts            ← HTTP actions (optional)\n├── .env.local             ← CONVEX_DEPLOYMENT, NEXT_PUBLIC_CONVEX_URL\n└── convex.json            ← Project config (optional)\n```\n\n## Schema Design\n\nDefine your schema in `convex\u002Fschema.ts` using the validator library:\n\n```typescript\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.optional(v.string()),\n    tokenIdentifier: v.string(),\n  })\n    .index(\"by_token\", [\"tokenIdentifier\"])\n    .index(\"by_email\", [\"email\"]),\n\n  messages: defineTable({\n    authorId: v.id(\"users\"),\n    channelId: v.id(\"channels\"),\n    body: v.string(),\n    attachmentId: v.optional(v.id(\"_storage\")),\n  })\n    .index(\"by_channel\", [\"channelId\"])\n    .searchIndex(\"search_body\", { searchField: \"body\" }),\n\n  channels: defineTable({\n    name: v.string(),\n    description: v.optional(v.string()),\n    isPrivate: v.boolean(),\n  }),\n});\n```\n\n### Validator Types\n\n| Validator                         | TypeScript Type       | Notes                                          |\n| :-------------------------------- | :-------------------- | :--------------------------------------------- |\n| `v.string()`                      | `string`              |                                                |\n| `v.number()`                      | `number`              | IEEE 754 float                                 |\n| `v.bigint()`                      | `bigint`              |                                                |\n| `v.boolean()`                     | `boolean`             |                                                |\n| `v.null()`                        | `null`                |                                                |\n| `v.id(\"tableName\")`               | `Id\u003C\"tableName\">`     | Document reference                             |\n| `v.array(v.string())`             | `string[]`            |                                                |\n| `v.object({...})`                 | `{...}`               | Nested objects                                 |\n| `v.optional(v.string())`          | `string \\| undefined` |                                                |\n| `v.union(v.string(), v.number())` | `string \\| number`    |                                                |\n| `v.literal(\"active\")`             | `\"active\"`            | Literal types                                  |\n| `v.bytes()`                       | `ArrayBuffer`         | Binary data                                    |\n| `v.float64()`                     | `number`              | Explicit 64-bit float (used in vector indexes) |\n| `v.any()`                         | `any`                 | Escape hatch                                   |\n\n### Indexes\n\n```typescript\n\u002F\u002F Single-field index\ndefineTable({ email: v.string() }).index(\"by_email\", [\"email\"]);\n\n\u002F\u002F Compound index (order matters for range queries)\ndefineTable({\n  orgId: v.string(),\n  createdAt: v.number(),\n}).index(\"by_org_and_date\", [\"orgId\", \"createdAt\"]);\n\n\u002F\u002F Full-text search index\ndefineTable({ body: v.string(), channelId: v.id(\"channels\") }).searchIndex(\n  \"search_body\",\n  {\n    searchField: \"body\",\n    filterFields: [\"channelId\"],\n  },\n);\n\n\u002F\u002F Vector search index (for AI\u002Fembeddings)\ndefineTable({ embedding: v.array(v.float64()), text: v.string() }).vectorIndex(\n  \"by_embedding\",\n  {\n    vectorField: \"embedding\",\n    dimensions: 1536,\n  },\n);\n```\n\n## Writing Functions\n\n### Queries (Read Data)\n\nQueries are reactive — clients automatically get updates when data changes.\n\n````typescript\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\n\u002F\u002F Simple query — list all tasks\nexport const list = query({\n  args: {},\n  handler: async (ctx) => {\n    return await ctx.db.query(\"tasks\").collect();\n  },\n});\n\n\u002F\u002F Query with arguments and filtering\nexport const getByChannel = query({\n  args: { channelId: v.id(\"channels\") },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .take(50);\n  },\n});\n\n\u002F\u002F Query with auth check\nexport const getMyProfile = query({\n  args: {},\n  handler: async (ctx) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) return null;\n\n    return await ctx.db\n      .query(\"users\")\n      .withIndex(\"by_token\", (q) =>\n        q.eq(\"tokenIdentifier\", identity.tokenIdentifier),\n      )\n      .unique();\n  },\n});\n\n### Paginated Queries\n\nUse cursor-based pagination for lists or infinite scroll UIs.\n\n```typescript\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { paginationOptsValidator } from \"convex\u002Fserver\";\n\nexport const listPaginated = query({\n  args: {\n    paginationOpts: paginationOptsValidator\n  },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .order(\"desc\")\n      .paginate(args.paginationOpts);\n  },\n});\n```\n\n### Mutations (Write Data)\n\nMutations run as ACID transactions with serializable isolation.\n\n```typescript\nimport { mutation } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\n\u002F\u002F Insert a document\nexport const create = mutation({\n  args: { text: v.string(), isCompleted: v.boolean() },\n  handler: async (ctx, args) => {\n    const taskId = await ctx.db.insert(\"tasks\", {\n      text: args.text,\n      isCompleted: args.isCompleted,\n    });\n    return taskId;\n  },\n});\n\n\u002F\u002F Update a document\nexport const update = mutation({\n  args: { id: v.id(\"tasks\"), isCompleted: v.boolean() },\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.id, { isCompleted: args.isCompleted });\n  },\n});\n\n\u002F\u002F Delete a document\nexport const remove = mutation({\n  args: { id: v.id(\"tasks\") },\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.id);\n  },\n});\n\n\u002F\u002F Multi-document transaction (automatically atomic)\nexport const transferCredits = mutation({\n  args: {\n    fromUserId: v.id(\"users\"),\n    toUserId: v.id(\"users\"),\n    amount: v.number(),\n  },\n  handler: async (ctx, args) => {\n    const fromUser = await ctx.db.get(args.fromUserId);\n    const toUser = await ctx.db.get(args.toUserId);\n    if (!fromUser || !toUser) throw new Error(\"User not found\");\n    if (fromUser.credits \u003C args.amount) throw new Error(\"Insufficient credits\");\n\n    await ctx.db.patch(args.fromUserId, {\n      credits: fromUser.credits - args.amount,\n    });\n    await ctx.db.patch(args.toUserId, {\n      credits: toUser.credits + args.amount,\n    });\n  },\n});\n````\n\n### Actions (External APIs & Side Effects)\n\nActions can call third-party services but cannot directly access the database — they must use `ctx.runQuery` and `ctx.runMutation`.\n\n```typescript\nimport { action } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nexport const sendEmail = action({\n  args: { to: v.string(), subject: v.string(), body: v.string() },\n  handler: async (ctx, args) => {\n    \u002F\u002F Call external API\n    const response = await fetch(\"https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend\", {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n      body: JSON.stringify({\n        personalizations: [{ to: [{ email: args.to }] }],\n        from: { email: \"noreply@example.com\" },\n        subject: args.subject,\n        content: [{ type: \"text\u002Fplain\", value: args.body }],\n      }),\n    });\n\n    if (!response.ok) throw new Error(\"Failed to send email\");\n\n    \u002F\u002F Write result back to database via mutation\n    await ctx.runMutation(api.emails.recordSent, {\n      to: args.to,\n      subject: args.subject,\n      sentAt: Date.now(),\n    });\n  },\n});\n\n\u002F\u002F Generate AI embeddings\nexport const generateEmbedding = action({\n  args: { text: v.string(), documentId: v.id(\"documents\") },\n  handler: async (ctx, args) => {\n    const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fembeddings\", {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n      body: JSON.stringify({\n        model: \"text-embedding-3-small\",\n        input: args.text,\n      }),\n    });\n\n    const { data } = await response.json();\n    await ctx.runMutation(api.documents.saveEmbedding, {\n      documentId: args.documentId,\n      embedding: data[0].embedding,\n    });\n  },\n});\n```\n\n### HTTP Actions (Webhooks)\n\n```typescript\nimport { httpRouter } from \"convex\u002Fserver\";\nimport { httpAction } from \".\u002F_generated\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nconst http = httpRouter();\n\nhttp.route({\n  path: \"\u002Fwebhooks\u002Fstripe\",\n  method: \"POST\",\n  handler: httpAction(async (ctx, request) => {\n    const body = await request.text();\n    const signature = request.headers.get(\"stripe-signature\");\n\n    \u002F\u002F Verify webhook signature here...\n\n    const event = JSON.parse(body);\n    await ctx.runMutation(api.payments.handleWebhook, { event });\n\n    return new Response(\"OK\", { status: 200 });\n  }),\n});\n\nexport default http;\n```\n\n## Client-Side Integration\n\n### React \u002F Next.js\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexProvider, ConvexReactClient } from \"convex\u002Freact\";\nimport { ReactNode } from \"react\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return \u003CConvexProvider client={convex}>{children}\u003C\u002FConvexProvider>;\n}\n```\n\n```typescript\n\u002F\u002F app\u002Flayout.tsx — wrap children\nimport { ConvexClientProvider } from \".\u002FConvexClientProvider\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>\n        \u003CConvexClientProvider>{children}\u003C\u002FConvexClientProvider>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Component using Convex hooks\n\"use client\";\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"@\u002Fconvex\u002F_generated\u002Fapi\";\n\nexport function TaskList() {\n  \u002F\u002F Reactive query — auto-updates when data changes\n  const tasks = useQuery(api.tasks.list);\n  const addTask = useMutation(api.tasks.create);\n  const toggleTask = useMutation(api.tasks.update);\n\n  if (tasks === undefined) return \u003Cp>Loading...\u003C\u002Fp>;\n\n  return (\n    \u003Cdiv>\n      {tasks.map((task) => (\n        \u003Cdiv key={task._id}>\n          \u003Cinput\n            type=\"checkbox\"\n            checked={task.isCompleted}\n            onChange={() =>\n              toggleTask({ id: task._id, isCompleted: !task.isCompleted })\n            }\n          \u002F>\n          {task.text}\n        \u003C\u002Fdiv>\n      ))}\n      \u003Cbutton onClick={() => addTask({ text: \"New task\", isCompleted: false })}>\n        Add Task\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Component using Paginated Queries\n\"use client\";\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { api } from \"@\u002Fconvex\u002F_generated\u002Fapi\";\n\nexport function MessageLog() {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    {}, \u002F\u002F args\n    { initialNumItems: 20 }\n  );\n\n  return (\n    \u003Cdiv>\n      {results.map((msg) => (\n        \u003Cdiv key={msg._id}>{msg.body}\u003C\u002Fdiv>\n      ))}\n\n      {status === \"LoadingFirstPage\" && \u003Cp>Loading...\u003C\u002Fp>}\n\n      {status === \"CanLoadMore\" && (\n        \u003Cbutton onClick={() => loadMore(20)}>Load More\u003C\u002Fbutton>\n      )}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### With Auth (First-Party Convex Auth)\n\nConvex provides a robust, native authentication library (`@convex-dev\u002Fauth`) featuring Magic Links, Passwords, and 80+ OAuth providers without needing a third-party service.\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexAuthProvider } from \"@convex-dev\u002Fauth\u002Freact\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ReactNode } from \"react\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return (\n    \u003CConvexAuthProvider client={convex}>\n      {children}\n    \u003C\u002FConvexAuthProvider>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Client-side sign in\nimport { useAuthActions } from \"@convex-dev\u002Fauth\u002Freact\";\n\nexport function Login() {\n  const { signIn } = useAuthActions();\n  return \u003Cbutton onClick={() => signIn(\"github\")}>Sign in with GitHub\u003C\u002Fbutton>;\n}\n```\n\n### With Auth (Third-Party Clerk Example)\n\nIf you prefer a hosted third-party solution like Clerk:\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexProviderWithClerk } from \"convex\u002Freact-clerk\";\nimport { ClerkProvider, useAuth } from \"@clerk\u002Fnextjs\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return (\n    \u003CClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}>\n      \u003CConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        {children}\n      \u003C\u002FConvexProviderWithClerk>\n    \u003C\u002FClerkProvider>\n  );\n}\n```\n\n### With Auth (Better Auth Component)\n\nConvex also has a community component (`@convex-dev\u002Fbetter-auth`) that integrates the Better Auth library directly into the Convex backend. This is currently in **early alpha**.\n\n```bash\nnpm install better-auth @convex-dev\u002Fbetter-auth\nnpx convex env set BETTER_AUTH_SECRET your-secret-here\nnpx convex env set SITE_URL http:\u002F\u002Flocalhost:3000\n```\n\nBetter Auth provides email\u002Fpassword, social logins, two-factor authentication, and session management — all running inside Convex functions rather than an external auth server.\n\n### Angular Integration\n\nConvex does not have an official Angular client library, but Angular apps can use the core `convex` package directly with Angular's Dependency Injection and Signals.\n\n```typescript\n\u002F\u002F services\u002Fconvex.service.ts\nimport { Injectable, signal, effect, OnDestroy } from \"@angular\u002Fcore\";\nimport { ConvexClient } from \"convex\u002Fbrowser\";\nimport { api } from \"..\u002F..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { FunctionReturnType } from \"convex\u002Fserver\";\n\n@Injectable({ providedIn: \"root\" })\nexport class ConvexService implements OnDestroy {\n  private client = new ConvexClient(environment.convexUrl);\n\n  \u002F\u002F Reactive signal — updates automatically when data changes\n  tasks = signal\u003CFunctionReturnType\u003Ctypeof api.tasks.list> | undefined>(\n    undefined,\n  );\n\n  constructor() {\n    \u002F\u002F Subscribe to a reactive query\n    this.client.onUpdate(api.tasks.list, {}, (result) => {\n      this.tasks.set(result);\n    });\n  }\n\n  async addTask(text: string) {\n    await this.client.mutation(api.tasks.create, {\n      text,\n      isCompleted: false,\n    });\n  }\n\n  ngOnDestroy() {\n    this.client.close();\n  }\n}\n```\n\n```typescript\n\u002F\u002F Component usage\nimport { Component, inject } from \"@angular\u002Fcore\";\nimport { ConvexService } from \".\u002Fservices\u002Fconvex.service\";\n\n@Component({\n  selector: \"app-task-list\",\n  template: `\n    @if (convex.tasks(); as tasks) {\n      @for (task of tasks; track task._id) {\n        \u003Cdiv>{{ task.text }}\u003C\u002Fdiv>\n      }\n    } @else {\n      \u003Cp>Loading...\u003C\u002Fp>\n    }\n    \u003Cbutton (click)=\"convex.addTask('New task')\">Add Task\u003C\u002Fbutton>\n  `,\n})\nexport class TaskListComponent {\n  convex = inject(ConvexService);\n}\n```\n\n> **Note:** The community library `@robmanganelly\u002Fngx-convex` provides a more Angular-native experience with React-like hooks adapted for Angular DI and Signals.\n\n## Scheduling & Cron Jobs\n\n### One-off Scheduled Functions\n\n```typescript\nimport { mutation } from \".\u002F_generated\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nexport const sendReminder = mutation({\n  args: { userId: v.id(\"users\"), message: v.string(), delayMs: v.number() },\n  handler: async (ctx, args) => {\n    await ctx.scheduler.runAfter(args.delayMs, api.notifications.send, {\n      userId: args.userId,\n      message: args.message,\n    });\n  },\n});\n```\n\n### Cron Jobs\n\n```typescript\n\u002F\u002F convex\u002Fcrons.ts\nimport { cronJobs } from \"convex\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nconst crons = cronJobs();\n\ncrons.interval(\"clear old logs\", { hours: 24 }, api.logs.clearOld);\n\ncrons.cron(\n  \"weekly digest\",\n  \"0 9 * * 1\", \u002F\u002F Every Monday at 9 AM\n  api.emails.sendWeeklyDigest,\n);\n\nexport default crons;\n```\n\n## File Storage\n\n```typescript\n\u002F\u002F Generate an upload URL (mutation)\nexport const generateUploadUrl = mutation({\n  args: {},\n  handler: async (ctx) => {\n    return await ctx.storage.generateUploadUrl();\n  },\n});\n\n\u002F\u002F Save file reference after upload (mutation)\nexport const saveFile = mutation({\n  args: { storageId: v.id(\"_storage\"), name: v.string() },\n  handler: async (ctx, args) => {\n    await ctx.db.insert(\"files\", {\n      storageId: args.storageId,\n      name: args.name,\n    });\n  },\n});\n\n\u002F\u002F Get a URL to serve a file (query)\nexport const getFileUrl = query({\n  args: { storageId: v.id(\"_storage\") },\n  handler: async (ctx, args) => {\n    return await ctx.storage.getUrl(args.storageId);\n  },\n});\n```\n\n## Environment Variables\n\n```bash\n# Set environment variables for your deployment\nnpx convex env set OPENAI_API_KEY sk-...\nnpx convex env set SENDGRID_API_KEY SG...\n\n# List current env vars\nnpx convex env list\n\n# Remove an env var\nnpx convex env unset OPENAI_API_KEY\n```\n\nAccess in actions (NOT in queries or mutations):\n\n```typescript\n\u002F\u002F Only available in actions\nconst apiKey = process.env.OPENAI_API_KEY;\n```\n\n## Deployment & CLI\n\n```bash\n# Development (watches for changes, syncs to dev deployment)\nnpx convex dev\n\n# Deploy to production\nnpx convex deploy\n\n# Import data\nnpx convex import --table tasks data.jsonl\n\n# Export data\nnpx convex export --path .\u002Fbackup\n\n# Open Convex dashboard\nnpx convex dashboard\n\n# Run a function from CLI\nnpx convex run tasks:list\n\n# View logs\nnpx convex logs\n```\n\n## Best Practices\n\n- ✅ Define schemas — adds type safety across your entire stack\n- ✅ Use indexes for queries — avoids full table scans\n- ✅ Use compound indexes with equality filters first, range filter last\n- ✅ Rely on native determinism — `Date.now()` and `Math.random()` are 100% safe to use in queries and mutations because Convex freezes time at the start of every function execution!\n- ✅ Use `v.id(\"tableName\")` for document references instead of plain strings\n- ✅ Use actions for external API calls (never call external APIs from queries or mutations)\n- ✅ Use `ctx.runQuery` \u002F `ctx.runMutation` from actions — never access `ctx.db` directly in actions\n- ✅ Add argument validators to all functions — they enforce runtime type safety\n- ✅ Return `null` when a document isn't found instead of throwing an error unless missing is exceptional\n- ✅ Prefer `withIndex` over `.filter()` for query performance\n\n## Anti-Patterns to Avoid\n\n1. **❌ External API calls in queries\u002Fmutations**: Only actions can call external services. Queries and mutations run in the Convex transaction engine.\n2. **❌ Doing slow CPU-bound work in mutations**: Mutations block database commits; offload heavy processing to actions.\n3. **❌ Using `.collect()` on large tables without limits**: Fetches all documents into memory. Use `.take(N)` or `.paginate()`.\n4. **❌ Skipping schema definition**: Without a schema you lose end-to-end type safety, the main Convex advantage.\n5. **❌ Using `.filter()` instead of indexes**: `.filter()` does a full table scan. Define an index and use `.withIndex()`.\n6. **❌ Storing large blobs in documents**: Use Convex file storage (`_storage`) for files; keep documents lean.\n7. **❌ Circular `runQuery`\u002F`runMutation` chains**: Actions calling mutations that schedule actions can create infinite loops.\n\n## Common Pitfalls\n\n- **Problem:** \"Query returns `undefined` on first render\"\n  **Solution:** This is expected — Convex queries are async. Check for `undefined` before rendering (this means loading, not empty).\n\n- **Problem:** \"Mutation throws `Document not found`\"\n  **Solution:** Documents may have been deleted between your read and write due to optimistic concurrency. Re-read inside the mutation.\n\n- **Problem:** \"`process.env` is undefined in query\u002Fmutation\"\n  **Solution:** Environment variables are only accessible in **actions** (not queries or mutations) because queries\u002Fmutations run in the deterministic transaction engine.\n\n- **Problem:** \"Function handler is too slow\"\n  **Solution:** Add indexes for your query patterns. Use `withIndex()` instead of `.filter()`. For complex operations, break into smaller mutations.\n\n- **Problem:** \"Schema push fails with existing data\"\n  **Solution:** Convex validates existing data against new schemas. Either migrate existing documents first, or use `v.optional()` for new fields.\n\n## Limitations\n\n- Queries and mutations cannot call external HTTP APIs (use actions instead)\n- No raw SQL — you work with the Convex query builder API\n- Environment variables only available in actions, not in queries or mutations\n- Document size limit of 1MB\n- Maximum function execution time limits apply\n- No server-side rendering of Convex data without specific SSR patterns (use preloading)\n- Schemas are enforced at write-time; changing schemas requires data migration for existing documents\n\n## Related Skills\n\n- `@firebase` — Alternative BaaS with Firestore (compare: Convex is TypeScript-first with ACID transactions)\n- `@supabase-automation` — Alternative with PostgreSQL backend (compare: Convex is document-relational with built-in reactivity)\n- `@prisma-expert` — ORM for traditional databases (Convex replaces both ORM and database)\n- `@react-patterns` — Frontend patterns that pair well with Convex React hooks\n- `@nextjs-app-router` — Next.js App Router integration patterns\n- `@authentication-oauth` — Auth patterns (Convex supports Clerk, Auth0, Convex Auth)\n- `@stripe` — Payment integration via Convex actions and HTTP webhooks\n\n## Resources\n\n- [Official Docs](https:\u002F\u002Fdocs.convex.dev)\n- [Convex Stack (Blog)](https:\u002F\u002Fstack.convex.dev)\n- [GitHub](https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-backend)\n- [Discord Community](https:\u002F\u002Fconvex.dev\u002Fcommunity)\n- [Convex Chef (AI Starter)](https:\u002F\u002Fchef.convex.dev)\n","# Convex\n\nYou are an expert in Convex — the open-source, reactive backend platform where queries are TypeScript code. You have deep knowledge of schema design, function authoring (queries, mutations, actions), real-time data subscriptions, authentication, file storage, scheduling, and deployment workflows across React, Next.js, Angular, Vue, Svelte, React Native, and server-side environments.\n\n## When to Use\n- Use when building a new project with Convex as the backend\n- Use when adding Convex to an existing React, Next.js, Angular, Vue, Svelte, or React Native app\n- Use when designing schemas for a Convex document-relational database\n- Use when writing or debugging Convex functions (queries, mutations, actions)\n- Use when implementing real-time\u002Freactive data patterns\n- Use when setting up authentication with Convex Auth or third-party providers (Clerk, Auth0, etc.)\n- Use when working with Convex file storage, scheduled functions, or cron jobs\n- Use when deploying or managing Convex projects\n\n## Core Concepts\n\nConvex is a **document-relational** database with a fully managed backend. Key differentiators:\n\n- **Reactive by default**: Queries automatically re-run and push updates to all connected clients when underlying data changes\n- **TypeScript-first**: All backend logic — queries, mutations, actions, schemas — is written in TypeScript\n- **ACID transactions**: Serializable isolation with optimistic concurrency control\n- **No infrastructure to manage**: Serverless, scales automatically, zero config\n- **End-to-end type safety**: Types flow from schema → backend functions → client hooks\n\n### Function Types\n\n| Type            | Purpose                   | Can Read DB    | Can Write DB      | Can Call External APIs | Cached\u002FReactive |\n| :-------------- | :------------------------ | :------------- | :---------------- | :--------------------- | :-------------- |\n| **Query**       | Read data                 | ✅             | ❌                | ❌                     | ✅              |\n| **Mutation**    | Write data                | ✅             | ✅                | ❌                     | ❌              |\n| **Action**      | Side effects              | via `runQuery` | via `runMutation` | ✅                     | ❌              |\n| **HTTP Action** | Webhooks\u002Fcustom endpoints | via `runQuery` | via `runMutation` | ✅                     | ❌              |\n\n## Project Setup\n\n### New Project (Next.js)\n\n```bash\nnpx create-next-app@latest my-app\ncd my-app && npm install convex\nnpx convex dev\n```\n\n### Add to Existing Project\n\n```bash\nnpm install convex\nnpx convex dev\n```\n\nThe `npx convex dev` command:\n\n1. Prompts you to log in (GitHub)\n2. Creates a project and deployment\n3. Generates `convex\u002F` folder for backend functions\n4. Syncs functions to your dev deployment in real-time\n5. Creates `.env.local` with `CONVEX_DEPLOYMENT` and `NEXT_PUBLIC_CONVEX_URL`\n\n### Folder Structure\n\n```\nmy-app\u002F\n├── convex\u002F\n│   ├── _generated\u002F        ← Auto-generated (DO NOT EDIT)\n│   │   ├── api.d.ts\n│   │   ├── dataModel.d.ts\n│   │   └── server.d.ts\n│   ├── schema.ts          ← Database schema definition\n│   ├── tasks.ts           ← Query\u002Fmutation functions\n│   └── http.ts            ← HTTP actions (optional)\n├── .env.local             ← CONVEX_DEPLOYMENT, NEXT_PUBLIC_CONVEX_URL\n└── convex.json            ← Project config (optional)\n```\n\n## Schema Design\n\nDefine your schema in `convex\u002Fschema.ts` using the validator library:\n\n```typescript\nimport { defineSchema, defineTable } from \"convex\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\nexport default defineSchema({\n  users: defineTable({\n    name: v.string(),\n    email: v.string(),\n    avatarUrl: v.optional(v.string()),\n    tokenIdentifier: v.string(),\n  })\n    .index(\"by_token\", [\"tokenIdentifier\"])\n    .index(\"by_email\", [\"email\"]),\n\n  messages: defineTable({\n    authorId: v.id(\"users\"),\n    channelId: v.id(\"channels\"),\n    body: v.string(),\n    attachmentId: v.optional(v.id(\"_storage\")),\n  })\n    .index(\"by_channel\", [\"channelId\"])\n    .searchIndex(\"search_body\", { searchField: \"body\" }),\n\n  channels: defineTable({\n    name: v.string(),\n    description: v.optional(v.string()),\n    isPrivate: v.boolean(),\n  }),\n});\n```\n\n### Validator Types\n\n| Validator                         | TypeScript Type       | Notes                                          |\n| :-------------------------------- | :-------------------- | :--------------------------------------------- |\n| `v.string()`                      | `string`              |                                                |\n| `v.number()`                      | `number`              | IEEE 754 float                                 |\n| `v.bigint()`                      | `bigint`              |                                                |\n| `v.boolean()`                     | `boolean`             |                                                |\n| `v.null()`                        | `null`                |                                                |\n| `v.id(\"tableName\")`               | `Id\u003C\"tableName\">`     | Document reference                             |\n| `v.array(v.string())`             | `string[]`            |                                                |\n| `v.object({...})`                 | `{...}`               | Nested objects                                 |\n| `v.optional(v.string())`          | `string \\| undefined` |                                                |\n| `v.union(v.string(), v.number())` | `string \\| number`    |                                                |\n| `v.literal(\"active\")`             | `\"active\"`            | Literal types                                  |\n| `v.bytes()`                       | `ArrayBuffer`         | Binary data                                    |\n| `v.float64()`                     | `number`              | Explicit 64-bit float (used in vector indexes) |\n| `v.any()`                         | `any`                 | Escape hatch                                   |\n\n### Indexes\n\n```typescript\n\u002F\u002F Single-field index\ndefineTable({ email: v.string() }).index(\"by_email\", [\"email\"]);\n\n\u002F\u002F Compound index (order matters for range queries)\ndefineTable({\n  orgId: v.string(),\n  createdAt: v.number(),\n}).index(\"by_org_and_date\", [\"orgId\", \"createdAt\"]);\n\n\u002F\u002F Full-text search index\ndefineTable({ body: v.string(), channelId: v.id(\"channels\") }).searchIndex(\n  \"search_body\",\n  {\n    searchField: \"body\",\n    filterFields: [\"channelId\"],\n  },\n);\n\n\u002F\u002F Vector search index (for AI\u002Fembeddings)\ndefineTable({ embedding: v.array(v.float64()), text: v.string() }).vectorIndex(\n  \"by_embedding\",\n  {\n    vectorField: \"embedding\",\n    dimensions: 1536,\n  },\n);\n```\n\n## Writing Functions\n\n### Queries (Read Data)\n\nQueries are reactive — clients automatically get updates when data changes.\n\n````typescript\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\n\u002F\u002F Simple query — list all tasks\nexport const list = query({\n  args: {},\n  handler: async (ctx) => {\n    return await ctx.db.query(\"tasks\").collect();\n  },\n});\n\n\u002F\u002F Query with arguments and filtering\nexport const getByChannel = query({\n  args: { channelId: v.id(\"channels\") },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .withIndex(\"by_channel\", (q) => q.eq(\"channelId\", args.channelId))\n      .order(\"desc\")\n      .take(50);\n  },\n});\n\n\u002F\u002F Query with auth check\nexport const getMyProfile = query({\n  args: {},\n  handler: async (ctx) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) return null;\n\n    return await ctx.db\n      .query(\"users\")\n      .withIndex(\"by_token\", (q) =>\n        q.eq(\"tokenIdentifier\", identity.tokenIdentifier),\n      )\n      .unique();\n  },\n});\n\n### Paginated Queries\n\nUse cursor-based pagination for lists or infinite scroll UIs.\n\n```typescript\nimport { query } from \".\u002F_generated\u002Fserver\";\nimport { paginationOptsValidator } from \"convex\u002Fserver\";\n\nexport const listPaginated = query({\n  args: {\n    paginationOpts: paginationOptsValidator\n  },\n  handler: async (ctx, args) => {\n    return await ctx.db\n      .query(\"messages\")\n      .order(\"desc\")\n      .paginate(args.paginationOpts);\n  },\n});\n```\n\n### Mutations (Write Data)\n\nMutations run as ACID transactions with serializable isolation.\n\n```typescript\nimport { mutation } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\n\n\u002F\u002F Insert a document\nexport const create = mutation({\n  args: { text: v.string(), isCompleted: v.boolean() },\n  handler: async (ctx, args) => {\n    const taskId = await ctx.db.insert(\"tasks\", {\n      text: args.text,\n      isCompleted: args.isCompleted,\n    });\n    return taskId;\n  },\n});\n\n\u002F\u002F Update a document\nexport const update = mutation({\n  args: { id: v.id(\"tasks\"), isCompleted: v.boolean() },\n  handler: async (ctx, args) => {\n    await ctx.db.patch(args.id, { isCompleted: args.isCompleted });\n  },\n});\n\n\u002F\u002F Delete a document\nexport const remove = mutation({\n  args: { id: v.id(\"tasks\") },\n  handler: async (ctx, args) => {\n    await ctx.db.delete(args.id);\n  },\n});\n\n\u002F\u002F Multi-document transaction (automatically atomic)\nexport const transferCredits = mutation({\n  args: {\n    fromUserId: v.id(\"users\"),\n    toUserId: v.id(\"users\"),\n    amount: v.number(),\n  },\n  handler: async (ctx, args) => {\n    const fromUser = await ctx.db.get(args.fromUserId);\n    const toUser = await ctx.db.get(args.toUserId);\n    if (!fromUser || !toUser) throw new Error(\"User not found\");\n    if (fromUser.credits \u003C args.amount) throw new Error(\"Insufficient credits\");\n\n    await ctx.db.patch(args.fromUserId, {\n      credits: fromUser.credits - args.amount,\n    });\n    await ctx.db.patch(args.toUserId, {\n      credits: toUser.credits + args.amount,\n    });\n  },\n});\n````\n\n### Actions (External APIs & Side Effects)\n\nActions can call third-party services but cannot directly access the database — they must use `ctx.runQuery` and `ctx.runMutation`.\n\n```typescript\nimport { action } from \".\u002F_generated\u002Fserver\";\nimport { v } from \"convex\u002Fvalues\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nexport const sendEmail = action({\n  args: { to: v.string(), subject: v.string(), body: v.string() },\n  handler: async (ctx, args) => {\n    \u002F\u002F Call external API\n    const response = await fetch(\"https:\u002F\u002Fapi.sendgrid.com\u002Fv3\u002Fmail\u002Fsend\", {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${process.env.SENDGRID_API_KEY}`,\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n      body: JSON.stringify({\n        personalizations: [{ to: [{ email: args.to }] }],\n        from: { email: \"noreply@example.com\" },\n        subject: args.subject,\n        content: [{ type: \"text\u002Fplain\", value: args.body }],\n      }),\n    });\n\n    if (!response.ok) throw new Error(\"Failed to send email\");\n\n    \u002F\u002F Write result back to database via mutation\n    await ctx.runMutation(api.emails.recordSent, {\n      to: args.to,\n      subject: args.subject,\n      sentAt: Date.now(),\n    });\n  },\n});\n\n\u002F\u002F Generate AI embeddings\nexport const generateEmbedding = action({\n  args: { text: v.string(), documentId: v.id(\"documents\") },\n  handler: async (ctx, args) => {\n    const response = await fetch(\"https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fembeddings\", {\n      method: \"POST\",\n      headers: {\n        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n        \"Content-Type\": \"application\u002Fjson\",\n      },\n      body: JSON.stringify({\n        model: \"text-embedding-3-small\",\n        input: args.text,\n      }),\n    });\n\n    const { data } = await response.json();\n    await ctx.runMutation(api.documents.saveEmbedding, {\n      documentId: args.documentId,\n      embedding: data[0].embedding,\n    });\n  },\n});\n```\n\n### HTTP Actions (Webhooks)\n\n```typescript\nimport { httpRouter } from \"convex\u002Fserver\";\nimport { httpAction } from \".\u002F_generated\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nconst http = httpRouter();\n\nhttp.route({\n  path: \"\u002Fwebhooks\u002Fstripe\",\n  method: \"POST\",\n  handler: httpAction(async (ctx, request) => {\n    const body = await request.text();\n    const signature = request.headers.get(\"stripe-signature\");\n\n    \u002F\u002F Verify webhook signature here...\n\n    const event = JSON.parse(body);\n    await ctx.runMutation(api.payments.handleWebhook, { event });\n\n    return new Response(\"OK\", { status: 200 });\n  }),\n});\n\nexport default http;\n```\n\n## Client-Side Integration\n\n### React \u002F Next.js\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexProvider, ConvexReactClient } from \"convex\u002Freact\";\nimport { ReactNode } from \"react\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return \u003CConvexProvider client={convex}>{children}\u003C\u002FConvexProvider>;\n}\n```\n\n```typescript\n\u002F\u002F app\u002Flayout.tsx — wrap children\nimport { ConvexClientProvider } from \".\u002FConvexClientProvider\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Cbody>\n        \u003CConvexClientProvider>{children}\u003C\u002FConvexClientProvider>\n      \u003C\u002Fbody>\n    \u003C\u002Fhtml>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Component using Convex hooks\n\"use client\";\nimport { useQuery, useMutation } from \"convex\u002Freact\";\nimport { api } from \"@\u002Fconvex\u002F_generated\u002Fapi\";\n\nexport function TaskList() {\n  \u002F\u002F Reactive query — auto-updates when data changes\n  const tasks = useQuery(api.tasks.list);\n  const addTask = useMutation(api.tasks.create);\n  const toggleTask = useMutation(api.tasks.update);\n\n  if (tasks === undefined) return \u003Cp>Loading...\u003C\u002Fp>;\n\n  return (\n    \u003Cdiv>\n      {tasks.map((task) => (\n        \u003Cdiv key={task._id}>\n          \u003Cinput\n            type=\"checkbox\"\n            checked={task.isCompleted}\n            onChange={() =>\n              toggleTask({ id: task._id, isCompleted: !task.isCompleted })\n            }\n          \u002F>\n          {task.text}\n        \u003C\u002Fdiv>\n      ))}\n      \u003Cbutton onClick={() => addTask({ text: \"New task\", isCompleted: false })}>\n        Add Task\n      \u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Component using Paginated Queries\n\"use client\";\nimport { usePaginatedQuery } from \"convex\u002Freact\";\nimport { api } from \"@\u002Fconvex\u002F_generated\u002Fapi\";\n\nexport function MessageLog() {\n  const { results, status, loadMore } = usePaginatedQuery(\n    api.messages.listPaginated,\n    {}, \u002F\u002F args\n    { initialNumItems: 20 }\n  );\n\n  return (\n    \u003Cdiv>\n      {results.map((msg) => (\n        \u003Cdiv key={msg._id}>{msg.body}\u003C\u002Fdiv>\n      ))}\n\n      {status === \"LoadingFirstPage\" && \u003Cp>Loading...\u003C\u002Fp>}\n\n      {status === \"CanLoadMore\" && (\n        \u003Cbutton onClick={() => loadMore(20)}>Load More\u003C\u002Fbutton>\n      )}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\n### With Auth (First-Party Convex Auth)\n\nConvex provides a robust, native authentication library (`@convex-dev\u002Fauth`) featuring Magic Links, Passwords, and 80+ OAuth providers without needing a third-party service.\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexAuthProvider } from \"@convex-dev\u002Fauth\u002Freact\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\nimport { ReactNode } from \"react\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return (\n    \u003CConvexAuthProvider client={convex}>\n      {children}\n    \u003C\u002FConvexAuthProvider>\n  );\n}\n```\n\n```typescript\n\u002F\u002F Client-side sign in\nimport { useAuthActions } from \"@convex-dev\u002Fauth\u002Freact\";\n\nexport function Login() {\n  const { signIn } = useAuthActions();\n  return \u003Cbutton onClick={() => signIn(\"github\")}>Sign in with GitHub\u003C\u002Fbutton>;\n}\n```\n\n### With Auth (Third-Party Clerk Example)\n\nIf you prefer a hosted third-party solution like Clerk:\n\n```typescript\n\u002F\u002F app\u002FConvexClientProvider.tsx\n\"use client\";\nimport { ConvexProviderWithClerk } from \"convex\u002Freact-clerk\";\nimport { ClerkProvider, useAuth } from \"@clerk\u002Fnextjs\";\nimport { ConvexReactClient } from \"convex\u002Freact\";\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);\n\nexport function ConvexClientProvider({ children }: { children: ReactNode }) {\n  return (\n    \u003CClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!}>\n      \u003CConvexProviderWithClerk client={convex} useAuth={useAuth}>\n        {children}\n      \u003C\u002FConvexProviderWithClerk>\n    \u003C\u002FClerkProvider>\n  );\n}\n```\n\n### With Auth (Better Auth Component)\n\nConvex also has a community component (`@convex-dev\u002Fbetter-auth`) that integrates the Better Auth library directly into the Convex backend. This is currently in **early alpha**.\n\n```bash\nnpm install better-auth @convex-dev\u002Fbetter-auth\nnpx convex env set BETTER_AUTH_SECRET your-secret-here\nnpx convex env set SITE_URL http:\u002F\u002Flocalhost:3000\n```\n\nBetter Auth provides email\u002Fpassword, social logins, two-factor authentication, and session management — all running inside Convex functions rather than an external auth server.\n\n### Angular Integration\n\nConvex does not have an official Angular client library, but Angular apps can use the core `convex` package directly with Angular's Dependency Injection and Signals.\n\n```typescript\n\u002F\u002F services\u002Fconvex.service.ts\nimport { Injectable, signal, effect, OnDestroy } from \"@angular\u002Fcore\";\nimport { ConvexClient } from \"convex\u002Fbrowser\";\nimport { api } from \"..\u002F..\u002Fconvex\u002F_generated\u002Fapi\";\nimport { FunctionReturnType } from \"convex\u002Fserver\";\n\n@Injectable({ providedIn: \"root\" })\nexport class ConvexService implements OnDestroy {\n  private client = new ConvexClient(environment.convexUrl);\n\n  \u002F\u002F Reactive signal — updates automatically when data changes\n  tasks = signal\u003CFunctionReturnType\u003Ctypeof api.tasks.list> | undefined>(\n    undefined,\n  );\n\n  constructor() {\n    \u002F\u002F Subscribe to a reactive query\n    this.client.onUpdate(api.tasks.list, {}, (result) => {\n      this.tasks.set(result);\n    });\n  }\n\n  async addTask(text: string) {\n    await this.client.mutation(api.tasks.create, {\n      text,\n      isCompleted: false,\n    });\n  }\n\n  ngOnDestroy() {\n    this.client.close();\n  }\n}\n```\n\n```typescript\n\u002F\u002F Component usage\nimport { Component, inject } from \"@angular\u002Fcore\";\nimport { ConvexService } from \".\u002Fservices\u002Fconvex.service\";\n\n@Component({\n  selector: \"app-task-list\",\n  template: `\n    @if (convex.tasks(); as tasks) {\n      @for (task of tasks; track task._id) {\n        \u003Cdiv>{{ task.text }}\u003C\u002Fdiv>\n      }\n    } @else {\n      \u003Cp>Loading...\u003C\u002Fp>\n    }\n    \u003Cbutton (click)=\"convex.addTask('New task')\">Add Task\u003C\u002Fbutton>\n  `,\n})\nexport class TaskListComponent {\n  convex = inject(ConvexService);\n}\n```\n\n> **Note:** The community library `@robmanganelly\u002Fngx-convex` provides a more Angular-native experience with React-like hooks adapted for Angular DI and Signals.\n\n## Scheduling & Cron Jobs\n\n### One-off Scheduled Functions\n\n```typescript\nimport { mutation } from \".\u002F_generated\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nexport const sendReminder = mutation({\n  args: { userId: v.id(\"users\"), message: v.string(), delayMs: v.number() },\n  handler: async (ctx, args) => {\n    await ctx.scheduler.runAfter(args.delayMs, api.notifications.send, {\n      userId: args.userId,\n      message: args.message,\n    });\n  },\n});\n```\n\n### Cron Jobs\n\n```typescript\n\u002F\u002F convex\u002Fcrons.ts\nimport { cronJobs } from \"convex\u002Fserver\";\nimport { api } from \".\u002F_generated\u002Fapi\";\n\nconst crons = cronJobs();\n\ncrons.interval(\"clear old logs\", { hours: 24 }, api.logs.clearOld);\n\ncrons.cron(\n  \"weekly digest\",\n  \"0 9 * * 1\", \u002F\u002F Every Monday at 9 AM\n  api.emails.sendWeeklyDigest,\n);\n\nexport default crons;\n```\n\n## File Storage\n\n```typescript\n\u002F\u002F Generate an upload URL (mutation)\nexport const generateUploadUrl = mutation({\n  args: {},\n  handler: async (ctx) => {\n    return await ctx.storage.generateUploadUrl();\n  },\n});\n\n\u002F\u002F Save file reference after upload (mutation)\nexport const saveFile = mutation({\n  args: { storageId: v.id(\"_storage\"), name: v.string() },\n  handler: async (ctx, args) => {\n    await ctx.db.insert(\"files\", {\n      storageId: args.storageId,\n      name: args.name,\n    });\n  },\n});\n\n\u002F\u002F Get a URL to serve a file (query)\nexport const getFileUrl = query({\n  args: { storageId: v.id(\"_storage\") },\n  handler: async (ctx, args) => {\n    return await ctx.storage.getUrl(args.storageId);\n  },\n});\n```\n\n## Environment Variables\n\n```bash\n# Set environment variables for your deployment\nnpx convex env set OPENAI_API_KEY sk-...\nnpx convex env set SENDGRID_API_KEY SG...\n\n# List current env vars\nnpx convex env list\n\n# Remove an env var\nnpx convex env unset OPENAI_API_KEY\n```\n\nAccess in actions (NOT in queries or mutations):\n\n```typescript\n\u002F\u002F Only available in actions\nconst apiKey = process.env.OPENAI_API_KEY;\n```\n\n## Deployment & CLI\n\n```bash\n# Development (watches for changes, syncs to dev deployment)\nnpx convex dev\n\n# Deploy to production\nnpx convex deploy\n\n# Import data\nnpx convex import --table tasks data.jsonl\n\n# Export data\nnpx convex export --path .\u002Fbackup\n\n# Open Convex dashboard\nnpx convex dashboard\n\n# Run a function from CLI\nnpx convex run tasks:list\n\n# View logs\nnpx convex logs\n```\n\n## Best Practices\n\n- ✅ Define schemas — adds type safety across your entire stack\n- ✅ Use indexes for queries — avoids full table scans\n- ✅ Use compound indexes with equality filters first, range filter last\n- ✅ Rely on native determinism — `Date.now()` and `Math.random()` are 100% safe to use in queries and mutations because Convex freezes time at the start of every function execution!\n- ✅ Use `v.id(\"tableName\")` for document references instead of plain strings\n- ✅ Use actions for external API calls (never call external APIs from queries or mutations)\n- ✅ Use `ctx.runQuery` \u002F `ctx.runMutation` from actions — never access `ctx.db` directly in actions\n- ✅ Add argument validators to all functions — they enforce runtime type safety\n- ✅ Return `null` when a document isn't found instead of throwing an error unless missing is exceptional\n- ✅ Prefer `withIndex` over `.filter()` for query performance\n\n## Anti-Patterns to Avoid\n\n1. **❌ External API calls in queries\u002Fmutations**: Only actions can call external services. Queries and mutations run in the Convex transaction engine.\n2. **❌ Doing slow CPU-bound work in mutations**: Mutations block database commits; offload heavy processing to actions.\n3. **❌ Using `.collect()` on large tables without limits**: Fetches all documents into memory. Use `.take(N)` or `.paginate()`.\n4. **❌ Skipping schema definition**: Without a schema you lose end-to-end type safety, the main Convex advantage.\n5. **❌ Using `.filter()` instead of indexes**: `.filter()` does a full table scan. Define an index and use `.withIndex()`.\n6. **❌ Storing large blobs in documents**: Use Convex file storage (`_storage`) for files; keep documents lean.\n7. **❌ Circular `runQuery`\u002F`runMutation` chains**: Actions calling mutations that schedule actions can create infinite loops.\n\n## Common Pitfalls\n\n- **Problem:** \"Query returns `undefined` on first render\"\n  **Solution:** This is expected — Convex queries are async. Check for `undefined` before rendering (this means loading, not empty).\n\n- **Problem:** \"Mutation throws `Document not found`\"\n  **Solution:** Documents may have been deleted between your read and write due to optimistic concurrency. Re-read inside the mutation.\n\n- **Problem:** \"`process.env` is undefined in query\u002Fmutation\"\n  **Solution:** Environment variables are only accessible in **actions** (not queries or mutations) because queries\u002Fmutations run in the deterministic transaction engine.\n\n- **Problem:** \"Function handler is too slow\"\n  **Solution:** Add indexes for your query patterns. Use `withIndex()` instead of `.filter()`. For complex operations, break into smaller mutations.\n\n- **Problem:** \"Schema push fails with existing data\"\n  **Solution:** Convex validates existing data against new schemas. Either migrate existing documents first, or use `v.optional()` for new fields.\n\n## Limitations\n\n- Queries and mutations cannot call external HTTP APIs (use actions instead)\n- No raw SQL — you work with the Convex query builder API\n- Environment variables only available in actions, not in queries or mutations\n- Document size limit of 1MB\n- Maximum function execution time limits apply\n- No server-side rendering of Convex data without specific SSR patterns (use preloading)\n- Schemas are enforced at write-time; changing schemas requires data migration for existing documents\n\n## Related Skills\n\n- `@firebase` — Alternative BaaS with Firestore (compare: Convex is TypeScript-first with ACID transactions)\n- `@supabase-automation` — Alternative with PostgreSQL backend (compare: Convex is document-relational with built-in reactivity)\n- `@prisma-expert` — ORM for traditional databases (Convex replaces both ORM and database)\n- `@react-patterns` — Frontend patterns that pair well with Convex React hooks\n- `@nextjs-app-router` — Next.js App Router integration patterns\n- `@authentication-oauth` — Auth patterns (Convex supports Clerk, Auth0, Convex Auth)\n- `@stripe` — Payment integration via Convex actions and HTTP webhooks\n\n## Resources\n\n- [Official Docs](https:\u002F\u002Fdocs.convex.dev)\n- [Convex Stack (Blog)](https:\u002F\u002Fstack.convex.dev)\n- [GitHub](https:\u002F\u002Fgithub.com\u002Fget-convex\u002Fconvex-backend)\n- [Discord Community](https:\u002F\u002Fconvex.dev\u002Fcommunity)\n- [Convex Chef (AI Starter)](https:\u002F\u002Fchef.convex.dev)\n",{"name":5,"metadata":50,"description":7},{"risk":43,"source":51,"date_added":52},"https:\u002F\u002Fdocs.convex.dev","2026-02-27","main","2b749c82b5d11fac39d9a6e021ce3f5b8a541bd1","cli-tool\u002Fcomponents\u002Fskills\u002Fdevelopment\u002Fconvex",3931,"https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates","https:\u002F\u002Faitmpl.com","https:\u002F\u002Fgithub.com\u002Fdavila7","https:\u002F\u002Fdanielavila.me","Building AI dev tools with LLMs","2026-11-14 06:14:46",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add davila7\u002Fclaude-code-templates --skill convex",{"tool":69,"command":70},"bunx","bunx skills add davila7\u002Fclaude-code-templates --skill convex",{"tool":72,"command":73},"pnpm","pnpm dlx skills add davila7\u002Fclaude-code-templates --skill convex","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fdevelopment\u002Fconvex\u002FSKILL.md",true,[],[78,108,135,168,202,226],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":87,"publishedAt":45},"01M70NHBTQ8BV0A2SCAZXEPHCX","wealthville-pool-scoring","Score DeFi liquidity pools before providing liquidity. Returns a 0-100 pool score and an Enter\u002FHold\u002FExit\u002FReduce\u002FAvoid verdict with calibrated confidence for ~68,800 Solana pools and 575 EVM pools, plus a public miss-inclusive track record. Use when asked whether a pool is worth LPing into, which pools look best right now, or how accurate the scores have actually been.","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fweb-data\u002Fwealthville-pool-scoring\u002FSKILL.md",[84],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107],"scoring","pool","wealthville pool scoring","liquidity","which","for","exit","now","use","when","reduce","with","record","track","verdict","evm","solana","how","public","look",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":115,"publishedAt":45},"01M70MB6VR8ERJ4EM8D5KHBMEV","technical-debt-auditor","Scans a codebase and produces a quantified technical debt report with a prioritized remediation plan. Analyzes dead code, TODO\u002FFIXME\u002FHACK markers, dependency staleness, complexity hotspots, code duplication signals, test coverage gaps, unused exports, and outdated patterns. Use this skill when the user asks to audit their codebase for tech debt, wants a health check of their code quality, says things like \"where should I focus refactoring\", \"find code smells\", \"audit my codebase\", \"what technical debt do we have\", \"prioritize cleanup work\", or asks about code quality metrics. Also trigger when the user mentions \"code rot\", \"legacy cleanup\", \"refactoring priorities\", or wants to understand the overall health of a project before a major effort. Trigger even if the user doesn't say \"technical debt\" explicitly — any request to systematically assess code quality and identify improvement priorities belongs here.","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fdevelopment\u002Ftechnical-debt-auditor\u002FSKILL.md",[114],{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},[116,117,118,119,120,121,122,123,124,125,126,97,127,128,129,130,131,132,133,134],"technical debt","debt","technical","technical debt auditor","auditor","coverage","complexity","markers","project","cleanup","unused","code","todo","work","plan","focus","signals","audit","dependency",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":150,"publishedAt":45},"01M70KPYH9QNHCNME5GJQGE22K","readonly-sql-guard","Make an agent's database access provably read-only. Use when an agent runs caller-supplied SQL against a production\u002FERP\u002Fanalytics database (a \"query\" MCP tool, text-to-SQL, an admin console), or when reviewing DB-access code for write\u002FDDL\u002Fside-effect\u002Ftransaction-escape risks. Checks what a statement CALLS, not just its shape.","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fdatabase\u002Freadonly-sql-guard\u002FSKILL.md",[141,144,145],{"categoryNo":142,"categoryName":143,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":146,"categoryName":147,"taxonomyType":148,"taxonomyTypeDesc":149},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[151,152,153,154,155,156,157,93,158,159,160,161,162,163,164,96,165,166,97,167],"readonly","guard","sql guard","sql","readonly sql guard","erp","effect","read-only","db","just","tool","calls","only","MCP","access","database","query",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":172,"licenseFileUrl":173,"sourceHtmlUrl":174,"categories":175,"keywords":179,"publishedAt":45},"01M6AFY6F7CC2BBZP1QD6VTNVC","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FSKILL.md",[176],{"categoryNo":177,"categoryName":178,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[180,181,182,183,184,185,186,187,188,189,190,191,192,36,193,194,195,196,197,198,199,200,93,201],"artifacts builder","web","web artifacts builder","artifacts","builder","state","routing","state management","component","css","frontend","ui","Tools","components","shadcn","react","Tailwind","suite","jsx","html","single-file","ui components",{"skillNo":203,"name":204,"nameZh":6,"description":205,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":172,"licenseFileUrl":206,"sourceHtmlUrl":207,"categories":208,"keywords":213,"publishedAt":45},"01M6AFWF33SFV9Z3SF4PEEE4BF","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FSKILL.md",[209,212],{"categoryNo":210,"categoryName":211,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":177,"categoryName":178,"taxonomyType":21,"taxonomyTypeDesc":22},[214,215,216,217,218,181,219,190,191,220,221,222,223,93,224,99,225],"webapp testing","testing","webapp","browser","playwright","applications","debugging","toolkit","logs","screenshots","local","behavior",{"skillNo":227,"name":228,"nameZh":6,"description":229,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":172,"licenseFileUrl":230,"sourceHtmlUrl":231,"categories":232,"keywords":240,"publishedAt":45},"01M6AF67Q67JJJ5V51JR2P52KA","discernment-nudge","After you give a substantive answer or draft that the user may act on — advice or recommendations, drafted artifacts such as goals, plans, pitches, proposals, or emails, estimates or projections, analysis or interpretation of data, factual claims they may rely on, or a multi-step argument — invoke this skill BEFORE finalizing your reply and then, if it applies, append 2-3 short follow-up questions, each tied to something specific in what you just produced, that help the user check key facts, probe the reasoning or assumptions, and notice missing context. Do this at most once per conversation. Skip it when the user asked a trivial how-to or simple lookup, wants a purely educational explanation, asked you only to format, convert, or assemble a file from content they provided, is writing code they will run, is doing creative writing or casual chat, or already asked you to double-check, cite, or review — the skill file explains these boundaries and the exact output format.","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fdiscernment-nudge\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fdavila7\u002Fclaude-code-templates\u002Fblob\u002Fmain\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fdiscernment-nudge\u002FSKILL.md",[233,236,237],{"categoryNo":234,"categoryName":235,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":85,"categoryName":86,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":238,"categoryName":239,"taxonomyType":148,"taxonomyTypeDesc":149},"01JBSEEDCAT000000000000036","内容创作者",[241,242,243,244,245,246,247,248,183,249,36,127,250,251,252,253,254,163,255,97,256,257,258,259,260,105,261],"discernment nudge","nudge","interpretation","help","run","reasoning","data","writing","lookup","content","review","conversation","short","up","invoke","you","analysis","claims","argument","assemble","recommendations"]