chore: update Convex AI files
Some checks failed
Security Gate: Secret Scanning / Scan for Verified Secrets (push) Has been cancelled

This commit is contained in:
github-actions[bot] 2026-05-04 08:53:04 +00:00
parent 395862fadf
commit 5f99d9a53a
28 changed files with 918 additions and 411 deletions

View File

@ -1,17 +1,22 @@
---
name: convex-create-component
description: Builds reusable Convex components with isolated tables and app-facing APIs. Use for new components, reusable backend modules, integrations, or component boundary work.
description:
Builds reusable Convex components with isolated tables and app-facing APIs.
Use for new components, reusable backend modules, integrations, or component
boundary work.
---
# Convex Create Component
Create reusable Convex components with clear boundaries and a small app-facing API.
Create reusable Convex components with clear boundaries and a small app-facing
API.
## When to Use
- Creating a new Convex component in an existing app
- Extracting reusable backend logic into a component
- Building a third-party integration that should own its own tables and workflows
- Building a third-party integration that should own its own tables and
workflows
- Packaging Convex functionality for reuse across multiple apps
## When Not to Use
@ -23,20 +28,30 @@ Create reusable Convex components with clear boundaries and a small app-facing A
## Workflow
1. Ask the user what they are building and what the end goal is. If the repo already makes the answer obvious, say so and confirm before proceeding.
2. Choose the shape using the decision tree below and read the matching reference file.
3. Decide whether a component is justified. Prefer normal app code or a regular library if the feature does not need isolated tables, backend functions, or reusable persistent state.
1. Ask the user what they are building and what the end goal is. If the repo
already makes the answer obvious, say so and confirm before proceeding.
2. Choose the shape using the decision tree below and read the matching
reference file.
3. Decide whether a component is justified. Prefer normal app code or a regular
library if the feature does not need isolated tables, backend functions, or
reusable persistent state.
4. Make a short plan for:
- what tables the component owns
- what public functions it exposes
- what data must be passed in from the app (auth, env vars, parent IDs)
- what stays in the app as wrappers or HTTP mounts
5. Create the component structure with `convex.config.ts`, `schema.ts`, and function files.
6. Implement functions using the component's own `./_generated/server` imports, not the app's generated files.
7. Wire the component into the app with `app.use(...)`. If the app does not already have `convex/convex.config.ts`, create it.
8. Call the component from the app through `components.<name>` using `ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction`.
9. If React clients, HTTP callers, or public APIs need access, create wrapper functions in the app instead of exposing component functions directly.
10. Run `npx convex dev` and fix codegen, type, or boundary issues before finishing.
5. Create the component structure with `convex.config.ts`, `schema.ts`, and
function files.
6. Implement functions using the component's own `./_generated/server` imports,
not the app's generated files.
7. Wire the component into the app with `app.use(...)`. If the app does not
already have `convex/convex.config.ts`, create it.
8. Call the component from the app through `components.<name>` using
`ctx.runQuery`, `ctx.runMutation`, or `ctx.runAction`.
9. If React clients, HTTP callers, or public APIs need access, create wrapper
functions in the app instead of exposing component functions directly.
10. Run `npx convex dev` and fix codegen, type, or boundary issues before
finishing.
## Choose the Shape
@ -169,19 +184,32 @@ export const myUnread = query({
});
```
Note the reference path shape: a function in `convex/components/notifications/lib.ts` is called as `components.notifications.lib.send` from the app.
Note the reference path shape: a function in
`convex/components/notifications/lib.ts` is called as
`components.notifications.lib.send` from the app.
## Critical Rules
- Keep authentication in the app, because `ctx.auth` is not available inside components.
- Keep environment access in the app, because component functions cannot read `process.env`.
- Pass parent app IDs across the boundary as strings, because `Id` types become plain strings in the app-facing `ComponentApi`.
- Do not use `v.id("parentTable")` for app-owned tables inside component args or schema, because the component has no access to the app's table namespace.
- Import `query`, `mutation`, and `action` from the component's own `./_generated/server`, not the app's generated files.
- Do not expose component functions directly to clients. Create app wrappers when client access is needed, because components are internal and need auth/env wiring the app provides.
- If the component defines HTTP handlers, mount the routes in the app's `convex/http.ts`, because components cannot register their own HTTP routes.
- If the component needs pagination, use `paginator` from `convex-helpers` instead of built-in `.paginate()`, because `.paginate()` does not work across the component boundary.
- Add `args` and `returns` validators to all public component functions, because the component boundary requires explicit type contracts.
- Keep authentication in the app, because `ctx.auth` is not available inside
components.
- Keep environment access in the app, because component functions cannot read
`process.env`.
- Pass parent app IDs across the boundary as strings, because `Id` types become
plain strings in the app-facing `ComponentApi`.
- Do not use `v.id("parentTable")` for app-owned tables inside component args or
schema, because the component has no access to the app's table namespace.
- Import `query`, `mutation`, and `action` from the component's own
`./_generated/server`, not the app's generated files.
- Do not expose component functions directly to clients. Create app wrappers
when client access is needed, because components are internal and need
auth/env wiring the app provides.
- If the component defines HTTP handlers, mount the routes in the app's
`convex/http.ts`, because components cannot register their own HTTP routes.
- If the component needs pagination, use `paginator` from `convex-helpers`
instead of built-in `.paginate()`, because `.paginate()` does not work across
the component boundary.
- Add `args` and `returns` validators to all public component functions, because
the component boundary requires explicit type contracts.
## Patterns
@ -248,7 +276,9 @@ args: {
### Advanced Patterns
For additional patterns including function handles for callbacks, deriving validators from schema, static configuration with a globals table, and class-based client wrappers, see `references/advanced-patterns.md`.
For additional patterns including function handles for callbacks, deriving
validators from schema, static configuration with a globals table, and
class-based client wrappers, see `references/advanced-patterns.md`.
## Validation
@ -261,8 +291,10 @@ Try validation in this order:
Important:
- Fresh repos may fail these commands until `CONVEX_DEPLOYMENT` is configured.
- Until codegen runs, component-local `./_generated/*` imports and app-side `components.<name>...` references will not typecheck.
- If validation blocks on Convex login or deployment setup, stop and ask the user for that exact step instead of guessing.
- Until codegen runs, component-local `./_generated/*` imports and app-side
`components.<name>...` references will not typecheck.
- If validation blocks on Convex login or deployment setup, stop and ask the
user for that exact step instead of guessing.
## Reference Files
@ -272,7 +304,8 @@ Read exactly one of these after the user confirms the goal:
- `references/packaged-components.md`
- `references/hybrid-components.md`
Official docs: [Authoring Components](https://docs.convex.dev/components/authoring)
Official docs:
[Authoring Components](https://docs.convex.dev/components/authoring)
## Checklist
@ -280,7 +313,8 @@ Official docs: [Authoring Components](https://docs.convex.dev/components/authori
- [ ] Read the matching reference file
- [ ] Confirmed a component is the right abstraction
- [ ] Planned tables, public API, boundaries, and app wrappers
- [ ] Component lives under `convex/components/<name>/` (or package layout if publishing)
- [ ] Component lives under `convex/components/<name>/` (or package layout if
publishing)
- [ ] Component imports from its own `./_generated/server`
- [ ] Auth, env access, and HTTP routes stay in the app
- [ ] Parent app IDs cross the boundary as `v.string()`

View File

@ -1,10 +1,14 @@
interface:
display_name: "Convex Create Component"
short_description: "Design and build reusable Convex components with clear boundaries."
short_description:
"Design and build reusable Convex components with clear boundaries."
icon_small: "./assets/icon.svg"
icon_large: "./assets/icon.svg"
brand_color: "#14B8A6"
default_prompt: "Help me create a Convex component for this feature. First check that a component is actually justified, then design the tables, API surface, and app-facing wrappers before implementing it."
default_prompt:
"Help me create a Convex component for this feature. First check that a
component is actually justified, then design the tables, API surface, and
app-facing wrappers before implementing it."
policy:
allow_implicit_invocation: true

View File

@ -1,10 +1,13 @@
# Advanced Component Patterns
Additional patterns for Convex components that go beyond the basics covered in the main skill file.
Additional patterns for Convex components that go beyond the basics covered in
the main skill file.
## Function Handles for callbacks
When the app needs to pass a callback function to the component, use function handles. This is common for components that run app-defined logic on a schedule or in a workflow.
When the app needs to pass a callback function to the component, use function
handles. This is common for components that run app-defined logic on a schedule
or in a workflow.
```ts
// App side: create a handle and pass it to the component
@ -37,7 +40,8 @@ export const enqueue = mutation({
## Deriving validators from schema
Instead of manually repeating field types in return validators, extend the schema validator:
Instead of manually repeating field types in return validators, extend the
schema validator:
```ts
import { v } from "convex/values";
@ -59,7 +63,8 @@ export const getLatest = query({
## Static configuration with a globals table
A common pattern for component configuration is a single-document "globals" table:
A common pattern for component configuration is a single-document "globals"
table:
```ts
// schema.ts
@ -91,7 +96,8 @@ export const configure = mutation({
## Class-based client wrappers
For components with many functions or configuration options, a class-based client provides a cleaner API. This pattern is common in published components.
For components with many functions or configuration options, a class-based
client provides a cleaner API. This pattern is common in published components.
```ts
// src/client/index.ts

View File

@ -10,7 +10,8 @@ This can help when:
- the user wants a local install but also shared package logic
- the component needs extension points or override hooks
- some logic should live in normal TypeScript code outside the component boundary
- some logic should live in normal TypeScript code outside the component
boundary
## Default Advice

View File

@ -1,6 +1,7 @@
# Local Convex Components
Read this file when the component should live inside the current app and does not need to be published as an npm package.
Read this file when the component should live inside the current app and does
not need to be published as an npm package.
## When to Choose This

View File

@ -1,6 +1,7 @@
# Packaged Convex Components
Read this file when the user wants a reusable npm package or a component shared across multiple apps.
Read this file when the user wants a reusable npm package or a component shared
across multiple apps.
## When to Choose This
@ -11,12 +12,14 @@ Read this file when the user wants a reusable npm package or a component shared
## Default Approach
- Prefer starting from `npx create-convex@latest --component` when possible
- Keep the official authoring docs as the source of truth for package layout and exports
- Keep the official authoring docs as the source of truth for package layout and
exports
- Validate the bundled package through an example app, not just the source files
## Build Flow
When building a packaged component, make sure the bundled output exists before the example app tries to consume it.
When building a packaged component, make sure the bundled output exists before
the example app tries to consume it.
Recommended order:

View File

@ -1,6 +1,9 @@
---
name: convex-migration-helper
description: Plans Convex schema and data migrations with widen-migrate-narrow and @convex-dev/migrations. Use for breaking schema changes, backfills, table reshaping, or zero-downtime rollouts.
description:
Plans Convex schema and data migrations with widen-migrate-narrow and
@convex-dev/migrations. Use for breaking schema changes, backfills, table
reshaping, or zero-downtime rollouts.
---
# Convex Migration Helper
@ -27,25 +30,32 @@ Safely migrate Convex schemas and data when making breaking changes.
### Schema Validation Drives the Workflow
Convex will not let you deploy a schema that does not match the data at rest. This is the fundamental constraint that shapes every migration:
Convex will not let you deploy a schema that does not match the data at rest.
This is the fundamental constraint that shapes every migration:
- You cannot add a required field if existing documents don't have it
- You cannot change a field's type if existing documents have the old type
- You cannot remove a field from the schema if existing documents still have it
This means migrations follow a predictable pattern: **widen the schema, migrate the data, narrow the schema**.
This means migrations follow a predictable pattern: **widen the schema, migrate
the data, narrow the schema**.
### Online Migrations
Convex migrations run online, meaning the app continues serving requests while data is updated asynchronously in batches. During the migration window, your code must handle both old and new data formats.
Convex migrations run online, meaning the app continues serving requests while
data is updated asynchronously in batches. During the migration window, your
code must handle both old and new data formats.
### Prefer New Fields Over Changing Types
When changing the shape of data, create a new field rather than modifying an existing one. This makes the transition safer and easier to roll back.
When changing the shape of data, create a new field rather than modifying an
existing one. This makes the transition safer and easier to roll back.
### Don't Delete Data
Unless you are certain, prefer deprecating fields over deleting them. Mark the field as `v.optional` and add a code comment explaining it is deprecated and why it existed.
Unless you are certain, prefer deprecating fields over deleting them. Mark the
field as `v.optional` and add a code comment explaining it is deprecated and why
it existed.
## Safe Changes (No Migration Needed)
@ -88,7 +98,8 @@ Every breaking migration follows the same multi-deploy pattern:
**Deploy 1 - Widen the schema:**
1. Update schema to allow both old and new formats (e.g., add optional new field)
1. Update schema to allow both old and new formats (e.g., add optional new
field)
2. Update code to handle both formats when reading
3. Update code to write the new format for new documents
4. Deploy
@ -106,13 +117,18 @@ Every breaking migration follows the same multi-deploy pattern:
## Using the Migrations Component
For any non-trivial migration, use the [`@convex-dev/migrations`](https://www.convex.dev/components/migrations) component. It handles batching, cursor-based pagination, state tracking, resume from failure, dry runs, and progress monitoring.
For any non-trivial migration, use the
[`@convex-dev/migrations`](https://www.convex.dev/components/migrations)
component. It handles batching, cursor-based pagination, state tracking, resume
from failure, dry runs, and progress monitoring.
See `references/migrations-component.md` for installation, setup, defining and running migrations, dry runs, status monitoring, and configuration options.
See `references/migrations-component.md` for installation, setup, defining and
running migrations, dry runs, status monitoring, and configuration options.
## Common Migration Patterns
See `references/migration-patterns.md` for complete patterns with code examples covering:
See `references/migration-patterns.md` for complete patterns with code examples
covering:
- Adding a required field
- Deleting a field
@ -125,12 +141,23 @@ See `references/migration-patterns.md` for complete patterns with code examples
## Common Pitfalls
1. **Making a field required before migrating data**: Convex rejects the deploy because existing documents lack the field. Always widen the schema first.
2. **Using `.collect()` on large tables**: Hits transaction limits or causes timeouts. Use the migrations component for proper batched pagination. `.collect()` is only safe for tables you know are small.
3. **Not writing the new format before migrating**: Documents created during the migration window will be missed, leaving unmigrated data after the migration "completes."
4. **Skipping the dry run**: Use `dryRun: true` to validate migration logic before committing changes to production data. Catches bugs before they touch real documents.
5. **Deleting fields prematurely**: Prefer deprecating with `v.optional` and a comment. Only delete after you are confident the data is no longer needed and no code references it.
6. **Using crons for migration batches**: The migrations component handles batching via recursive scheduling internally. Crons require manual cleanup and an extra deploy to remove.
1. **Making a field required before migrating data**: Convex rejects the deploy
because existing documents lack the field. Always widen the schema first.
2. **Using `.collect()` on large tables**: Hits transaction limits or causes
timeouts. Use the migrations component for proper batched pagination.
`.collect()` is only safe for tables you know are small.
3. **Not writing the new format before migrating**: Documents created during the
migration window will be missed, leaving unmigrated data after the migration
"completes."
4. **Skipping the dry run**: Use `dryRun: true` to validate migration logic
before committing changes to production data. Catches bugs before they touch
real documents.
5. **Deleting fields prematurely**: Prefer deprecating with `v.optional` and a
comment. Only delete after you are confident the data is no longer needed and
no code references it.
6. **Using crons for migration batches**: The migrations component handles
batching via recursive scheduling internally. Crons require manual cleanup
and an extra deploy to remove.
## Migration Checklist

View File

@ -4,7 +4,10 @@ interface:
icon_small: "./assets/icon.svg"
icon_large: "./assets/icon.svg"
brand_color: "#8B5CF6"
default_prompt: "Help me plan and execute this Convex migration safely. Start by identifying the schema change, the existing data shape, and the widen-migrate-narrow path before making edits."
default_prompt:
"Help me plan and execute this Convex migration safely. Start by identifying
the schema change, the existing data shape, and the widen-migrate-narrow
path before making edits."
policy:
allow_implicit_invocation: true

View File

@ -1,6 +1,7 @@
# Migration Patterns Reference
Common migration patterns, zero-downtime strategies, and verification techniques for Convex schema and data migrations.
Common migration patterns, zero-downtime strategies, and verification techniques
for Convex schema and data migrations.
## Adding a Required Field
@ -30,7 +31,8 @@ users: defineTable({
## Deleting a Field
Mark the field optional first, migrate data to remove it, then remove from schema:
Mark the field optional first, migrate data to remove it, then remove from
schema:
```typescript
// Deploy 1: Make optional
@ -51,7 +53,8 @@ export const removeIsPro = migrations.define({
## Changing a Field Type
Prefer creating a new field. You can combine adding and deleting in one migration:
Prefer creating a new field. You can combine adding and deleting in one
migration:
```typescript
// Deploy 1: Add new field, keep old field optional
@ -98,7 +101,9 @@ export const extractPreferences = migrations.define({
});
```
Make sure your code is already writing to the new `userPreferences` table for new users before running this migration, so you don't miss documents created during the migration window.
Make sure your code is already writing to the new `userPreferences` table for
new users before running this migration, so you don't miss documents created
during the migration window.
## Cleaning Up Orphaned Documents
@ -120,18 +125,21 @@ export const deleteOrphanedEmbeddings = migrations.define({
## Zero-Downtime Strategies
During the migration window, your app must handle both old and new data formats. There are two main strategies.
During the migration window, your app must handle both old and new data formats.
There are two main strategies.
### Dual Write (Preferred)
Write to both old and new structures. Read from the old structure until migration is complete.
Write to both old and new structures. Read from the old structure until
migration is complete.
1. Deploy code that writes both formats, reads old format
2. Run migration on existing data
3. Deploy code that reads new format, still writes both
4. Deploy code that only reads and writes new format
This is preferred because you can safely roll back at any point, the old format is always up to date.
This is preferred because you can safely roll back at any point, the old format
is always up to date.
```typescript
// Bad: only writing to new structure before migration is done
@ -167,7 +175,9 @@ Read both formats. Write only the new format.
2. Run migration on existing data
3. Deploy code that reads and writes only new format
This avoids duplicating writes, which is useful when having two copies of data could cause inconsistencies. The downside is that rolling back to before step 1 is harder, since new documents only have the new format.
This avoids duplicating writes, which is useful when having two copies of data
could cause inconsistencies. The downside is that rolling back to before step 1
is harder, since new documents only have the new format.
```typescript
// Good: reading both formats, preferring new
@ -179,7 +189,8 @@ function getTeamPlan(team: Doc<"teams">): "basic" | "pro" {
## Small Table Shortcut
For small tables (a few thousand documents at most), you can migrate in a single `internalMutation` without the component:
For small tables (a few thousand documents at most), you can migrate in a single
`internalMutation` without the component:
```typescript
import { internalMutation } from "./_generated/server";
@ -200,7 +211,8 @@ export const backfillSmallTable = internalMutation({
npx convex run migrations:backfillSmallTable
```
Only use `.collect()` when you are certain the table is small. For anything larger, use the migrations component.
Only use `.collect()` when you are certain the table is small. For anything
larger, use the migrations component.
## Verifying a Migration

View File

@ -1,6 +1,8 @@
# Migrations Component Reference
Complete guide to the [`@convex-dev/migrations`](https://www.convex.dev/components/migrations) component for batched, resumable Convex data migrations.
Complete guide to the
[`@convex-dev/migrations`](https://www.convex.dev/components/migrations)
component for batched, resumable Convex data migrations.
## Installation
@ -30,11 +32,13 @@ export const migrations = new Migrations<DataModel>(components.migrations);
export const run = migrations.runner();
```
The `DataModel` type parameter is optional but provides type safety for migration definitions.
The `DataModel` type parameter is optional but provides type safety for
migration definitions.
## Define a Migration
The `migrateOne` function processes a single document. The component handles batching and pagination automatically.
The `migrateOne` function processes a single document. The component handles
batching and pagination automatically.
```typescript
// convex/migrations.ts
@ -90,7 +94,8 @@ export const runAll = migrations.runner([
npx convex run migrations:runAll
```
If one fails, it stops and will not continue to the next. Call it again to retry from where it left off. Completed migrations are skipped automatically.
If one fails, it stops and will not continue to the next. Call it again to retry
from where it left off. Completed migrations are skipped automatically.
## Dry Run
@ -100,7 +105,8 @@ Test a migration before committing changes:
npx convex run migrations:runIt '{"dryRun": true}'
```
This runs one batch and then rolls back, so you can see what it would do without changing any data.
This runs one batch and then rolls back, so you can see what it would do without
changing any data.
## Check Migration Status
@ -132,7 +138,8 @@ npx convex deploy --cmd 'npm run build' && npx convex run migrations:runAll --pr
### Custom Batch Size
If documents are large or the table has heavy write traffic, reduce the batch size to avoid transaction limits or OCC conflicts:
If documents are large or the table has heavy write traffic, reduce the batch
size to avoid transaction limits or OCC conflicts:
```typescript
export const migrateHeavyTable = migrations.define({
@ -158,7 +165,8 @@ export const fixEmptyNames = migrations.define({
### Parallelize Within a Batch
By default each document in a batch is processed serially. Enable parallel processing if your migration logic does not depend on ordering:
By default each document in a batch is processed serially. Enable parallel
processing if your migration logic does not depend on ordering:
```typescript
export const clearField = migrations.define({

View File

@ -1,17 +1,23 @@
---
name: convex-performance-audit
description: Audits Convex performance for reads, subscriptions, write contention, and function limits. Use for slow features, insights findings, OCC conflicts, or read amplification.
description:
Audits Convex performance for reads, subscriptions, write contention, and
function limits. Use for slow features, insights findings, OCC conflicts, or
read amplification.
---
# Convex Performance Audit
Diagnose and fix performance problems in Convex applications, one problem class at a time.
Diagnose and fix performance problems in Convex applications, one problem class
at a time.
## When to Use
- A Convex page or feature feels slow or expensive
- `npx convex insights --details` reports high bytes read, documents read, or OCC conflicts
- Low-freshness read paths are using reactivity where point-in-time reads would do
- `npx convex insights --details` reports high bytes read, documents read, or
OCC conflicts
- Low-freshness read paths are using reactivity where point-in-time reads would
do
- OCC conflict errors or excessive mutation retries
- High subscription count or slow UI updates
- Functions approaching execution or transaction limits
@ -21,27 +27,39 @@ Diagnose and fix performance problems in Convex applications, one problem class
- Initial Convex setup, auth setup, or component extraction
- Pure schema migrations with no performance goal
- One-off micro-optimizations without a user-visible or deployment-visible problem
- One-off micro-optimizations without a user-visible or deployment-visible
problem
## Guardrails
- Prefer simpler code when scale is small, traffic is modest, or the available signals are weak
- Do not recommend digest tables, document splitting, fetch-strategy changes, or migration-heavy rollouts unless there is a measured signal, a clearly unbounded path, or a known hot read/write path
- In Convex, a simple scan on a small table is often acceptable. Do not invent structural work just because a pattern is not ideal at large scale
- Prefer simpler code when scale is small, traffic is modest, or the available
signals are weak
- Do not recommend digest tables, document splitting, fetch-strategy changes, or
migration-heavy rollouts unless there is a measured signal, a clearly
unbounded path, or a known hot read/write path
- In Convex, a simple scan on a small table is often acceptable. Do not invent
structural work just because a pattern is not ideal at large scale
## First Step: Gather Signals
Start with the strongest signal available:
1. If deployment Health insights are already available from the user or the current context, treat them as a first-class source of performance signals.
2. If CLI insights are available, run `npx convex insights --details`. Use `--prod`, `--preview-name`, or `--deployment-name` when needed.
- If the local repo's Convex CLI is too old to support `insights`, try `npx -y convex@latest insights --details` before giving up.
3. If the repo already uses `convex-doctor`, you may treat its findings as hints. Do not require it, and do not treat it as the source of truth.
4. If runtime signals are unavailable, audit from code anyway, but keep the guardrails above in mind. Lack of insights is not proof of health, but it is also not proof that a large refactor is warranted.
1. If deployment Health insights are already available from the user or the
current context, treat them as a first-class source of performance signals.
2. If CLI insights are available, run `npx convex insights --details`. Use
`--prod`, `--preview-name`, or `--deployment-name` when needed.
- If the local repo's Convex CLI is too old to support `insights`, try
`npx -y convex@latest insights --details` before giving up.
3. If the repo already uses `convex-doctor`, you may treat its findings as
hints. Do not require it, and do not treat it as the source of truth.
4. If runtime signals are unavailable, audit from code anyway, but keep the
guardrails above in mind. Lack of insights is not proof of health, but it is
also not proof that a large refactor is warranted.
## Signal Routing
After gathering signals, identify the problem class and read the matching reference file.
After gathering signals, identify the problem class and read the matching
reference file.
| Signal | Reference |
| -------------------------------------------------------------- | ----------------------------------------- |
@ -51,26 +69,31 @@ After gathering signals, identify the problem class and read the matching refere
| Function timeouts, transaction size errors, large payloads | `references/function-budget.md` |
| General "it's slow" with no specific signal | Start with `references/hot-path-rules.md` |
Multiple problem classes can overlap. Read the most relevant reference first, then check the others if symptoms remain.
Multiple problem classes can overlap. Read the most relevant reference first,
then check the others if symptoms remain.
## Escalate Larger Fixes
If the likely fix is invasive, cross-cutting, or migration-heavy, stop and present options before editing.
If the likely fix is invasive, cross-cutting, or migration-heavy, stop and
present options before editing.
Examples:
- introducing digest or summary tables across multiple flows
- splitting documents to isolate frequently-updated fields
- reworking pagination or fetch strategy across several screens
- switching to a new index or denormalized field that needs migration-safe rollout
- switching to a new index or denormalized field that needs migration-safe
rollout
When correctness depends on handling old and new states during a rollout, consult `skills/convex-migration-helper/SKILL.md` for the migration workflow.
When correctness depends on handling old and new states during a rollout,
consult `skills/convex-migration-helper/SKILL.md` for the migration workflow.
## Workflow
### 1. Scope the problem
Pick one concrete user flow from the actual project. Look at the codebase, client pages, and API surface to find the flow that matches the symptom.
Pick one concrete user flow from the actual project. Look at the codebase,
client pages, and API surface to find the flow that matches the symptom.
Write down:
@ -90,27 +113,37 @@ For each function in the path:
4. Identify all sibling functions touching the same tables
5. Identify reactive stats, aggregates, or widgets rendered on the same page
In Convex, every extra read increases transaction work, and every write can invalidate reactive subscribers. Treat read amplification and invalidation amplification as first-class problems.
In Convex, every extra read increases transaction work, and every write can
invalidate reactive subscribers. Treat read amplification and invalidation
amplification as first-class problems.
### 3. Apply fixes from the relevant reference
Read the reference file matching your problem class. Each reference includes specific patterns, code examples, and a recommended fix order.
Read the reference file matching your problem class. Each reference includes
specific patterns, code examples, and a recommended fix order.
Do not stop at the single function named by an insight. Trace sibling readers and writers touching the same tables.
Do not stop at the single function named by an insight. Trace sibling readers
and writers touching the same tables.
### 4. Fix sibling functions together
When one function touching a table has a performance bug, audit sibling functions for the same pattern.
When one function touching a table has a performance bug, audit sibling
functions for the same pattern.
After finding one problem, inspect both sibling readers and sibling writers for the same table family, including companion digest or summary tables.
After finding one problem, inspect both sibling readers and sibling writers for
the same table family, including companion digest or summary tables.
Examples:
- If one list query switches from full docs to a digest table, inspect the other list queries for that table
- If one mutation isolates a frequently-updated field or splits a hot document, inspect the other writers to the same table
- If one read path needs a migration-safe rollout for an unbackfilled field, inspect sibling reads for the same rollout risk
- If one list query switches from full docs to a digest table, inspect the other
list queries for that table
- If one mutation isolates a frequently-updated field or splits a hot document,
inspect the other writers to the same table
- If one read path needs a migration-safe rollout for an unbackfilled field,
inspect sibling reads for the same rollout risk
Do not leave one path fixed and another path on the old pattern unless there is a clear product reason.
Do not leave one path fixed and another path on the old pattern unless there is
a clear product reason.
### 5. Verify before finishing
@ -119,17 +152,26 @@ Confirm all of these:
1. Results are the same as before, no dropped records
2. Eliminated reads or writes are no longer in the path where expected
3. Fallback behavior works when denormalized or indexed fields are missing
4. Frequently-updated fields are isolated from widely-read documents where needed
5. Every relevant sibling reader and writer was inspected, not just the original function
4. Frequently-updated fields are isolated from widely-read documents where
needed
5. Every relevant sibling reader and writer was inspected, not just the original
function
## Reference Files
- `references/hot-path-rules.md` - Read amplification, invalidation, denormalization, indexes, digest tables
- `references/occ-conflicts.md` - Write contention, OCC resolution, hot document splitting
- `references/subscription-cost.md` - Reactive query cost, subscription granularity, point-in-time reads
- `references/function-budget.md` - Execution limits, transaction size, large documents, payload size
- `references/hot-path-rules.md` - Read amplification, invalidation,
denormalization, indexes, digest tables
- `references/occ-conflicts.md` - Write contention, OCC resolution, hot document
splitting
- `references/subscription-cost.md` - Reactive query cost, subscription
granularity, point-in-time reads
- `references/function-budget.md` - Execution limits, transaction size, large
documents, payload size
Also check the official [Convex Best Practices](https://docs.convex.dev/understanding/best-practices/) page for additional patterns covering argument validation, access control, and code organization that may surface during the audit.
Also check the official
[Convex Best Practices](https://docs.convex.dev/understanding/best-practices/)
page for additional patterns covering argument validation, access control, and
code organization that may surface during the audit.
## Checklist

View File

@ -1,10 +1,14 @@
interface:
display_name: "Convex Performance Audit"
short_description: "Audit slow Convex reads, subscriptions, OCC conflicts, and limits."
short_description:
"Audit slow Convex reads, subscriptions, OCC conflicts, and limits."
icon_small: "./assets/icon.svg"
icon_large: "./assets/icon.svg"
brand_color: "#EF4444"
default_prompt: "Audit this Convex app for performance issues. Start with the strongest signal available, identify the problem class, and suggest the smallest high-impact fix before proposing bigger structural changes."
default_prompt:
"Audit this Convex app for performance issues. Start with the strongest
signal available, identify the problem class, and suggest the smallest
high-impact fix before proposing bigger structural changes."
policy:
allow_implicit_invocation: true

View File

@ -1,14 +1,19 @@
# Function Budget
Use these rules when functions are hitting execution limits, transaction size errors, or returning excessively large payloads to the client.
Use these rules when functions are hitting execution limits, transaction size
errors, or returning excessively large payloads to the client.
## Core Principle
Convex functions run inside transactions with budgets for time, reads, and writes. Staying well within these limits is not just about avoiding errors, it reduces latency and contention.
Convex functions run inside transactions with budgets for time, reads, and
writes. Staying well within these limits is not just about avoiding errors, it
reduces latency and contention.
## Limits to Know
These are the current values from the [Convex limits docs](https://docs.convex.dev/production/state/limits). Check that page for the latest numbers.
These are the current values from the
[Convex limits docs](https://docs.convex.dev/production/state/limits). Check
that page for the latest numbers.
| Resource | Limit |
| --------------------------------- | ----------------------------------------------------- |
@ -34,15 +39,18 @@ These are the current values from the [Convex limits docs](https://docs.convex.d
### Unbounded collection
A query that calls `.collect()` on a table without a reasonable limit. As the table grows, the query reads more and more documents.
A query that calls `.collect()` on a table without a reasonable limit. As the
table grows, the query reads more and more documents.
### Large document reads on hot paths
Reading documents with large fields (rich text, embedded media references, long arrays) when only a small subset of the data is needed for the current view.
Reading documents with large fields (rich text, embedded media references, long
arrays) when only a small subset of the data is needed for the current view.
### Mutation doing too much work
A single mutation that updates hundreds of documents, backfills data, or rebuilds derived state in one transaction.
A single mutation that updates hundreds of documents, backfills data, or
rebuilds derived state in one transaction.
### Returning too much data to the client
@ -70,13 +78,16 @@ const messages = await ctx.db
### 2. Read smaller shapes
If the list page only needs title, author, and date, do not read full documents with rich content fields.
If the list page only needs title, author, and date, do not read full documents
with rich content fields.
Use digest or summary tables for hot list pages. See `hot-path-rules.md` for the digest table pattern.
Use digest or summary tables for hot list pages. See `hot-path-rules.md` for the
digest table pattern.
### 3. Break large mutations into batches
If a mutation needs to update hundreds of documents, split it into a self-scheduling chain.
If a mutation needs to update hundreds of documents, split it into a
self-scheduling chain.
```ts
// Bad: one mutation updating every row
@ -118,9 +129,12 @@ export const backfillBatch = internalMutation({
### 4. Move heavy work to actions
Queries and mutations run inside Convex's transactional runtime with strict budgets. If you need to do CPU-intensive computation, call external APIs, or process large files, use an action instead.
Queries and mutations run inside Convex's transactional runtime with strict
budgets. If you need to do CPU-intensive computation, call external APIs, or
process large files, use an action instead.
Actions run outside the transaction and can call mutations to write results back.
Actions run outside the transaction and can call mutations to write results
back.
```ts
// Bad: heavy computation inside a mutation
@ -144,7 +158,8 @@ export const processUpload = action({
### 5. Trim return values
Only return what the client needs. If a query fetches full documents but the component only renders a few fields, map the results before returning.
Only return what the client needs. If a query fetches full documents but the
component only renders a few fields, map the results before returning.
```ts
// Bad: returns full documents including large content fields
@ -172,7 +187,9 @@ export const list = query({
### 6. Replace `ctx.runQuery` and `ctx.runMutation` with helper functions
Inside queries and mutations, `ctx.runQuery` and `ctx.runMutation` have overhead compared to calling a plain TypeScript helper function. They run in the same transaction but pay extra per-call cost.
Inside queries and mutations, `ctx.runQuery` and `ctx.runMutation` have overhead
compared to calling a plain TypeScript helper function. They run in the same
transaction but pay extra per-call cost.
```ts
// Bad: unnecessary overhead from ctx.runQuery inside a mutation
@ -194,11 +211,15 @@ export const createProject = mutation({
});
```
Exception: components require `ctx.runQuery`/`ctx.runMutation`. Use them there, but prefer helpers everywhere else.
Exception: components require `ctx.runQuery`/`ctx.runMutation`. Use them there,
but prefer helpers everywhere else.
### 7. Avoid unnecessary `runAction` calls
`runAction` from within an action creates a separate function invocation with its own memory and CPU budget. The parent action just sits idle waiting. Replace with a plain TypeScript function call unless you need a different runtime (e.g. calling Node.js code from the Convex runtime).
`runAction` from within an action creates a separate function invocation with
its own memory and CPU budget. The parent action just sits idle waiting. Replace
with a plain TypeScript function call unless you need a different runtime (e.g.
calling Node.js code from the Convex runtime).
```ts
// Bad: runAction overhead for no reason
@ -228,5 +249,6 @@ export const processItems = action({
2. `npx convex insights --details` shows reduced bytes read
3. Large mutations are batched and self-scheduling
4. Client payloads are reasonably sized for the UI they serve
5. `ctx.runQuery`/`ctx.runMutation` in queries and mutations replaced with helpers where possible
5. `ctx.runQuery`/`ctx.runMutation` in queries and mutations replaced with
helpers where possible
6. Sibling functions with similar patterns were checked

View File

@ -1,6 +1,8 @@
# Hot Path Rules
Use these rules when the top-level workflow points to read amplification, denormalization, index rollout, reactive query cost, or invalidation-heavy writes.
Use these rules when the top-level workflow points to read amplification,
denormalization, index rollout, reactive query cost, or invalidation-heavy
writes.
## Contents
@ -10,8 +12,10 @@ Use these rules when the top-level workflow points to read amplification, denorm
- 2. Minimize Data Sources (denormalization, fallback rule)
- 3. Minimize Row Size (digest tables)
- 4. Skip No-Op Writes
- 5. Match Consistency To Read Patterns (high-read/low-write, high-read/high-write)
- Convex-Specific Notes (reactive queries, point-in-time reads, triggers, aggregates, backfills)
- 5. Match Consistency To Read Patterns (high-read/low-write,
high-read/high-write)
- Convex-Specific Notes (reactive queries, point-in-time reads, triggers,
aggregates, backfills)
- Verification
## Core Principle
@ -22,11 +26,13 @@ Think:
`cost x calls_per_second x 86400`
In Convex, every write can also fan out into reactive invalidation, replication work, and downstream sync.
In Convex, every write can also fan out into reactive invalidation, replication
work, and downstream sync.
## Consistency Rule
If you fix a hot-path pattern for one function, audit sibling functions touching the same tables for the same pattern.
If you fix a hot-path pattern for one function, audit sibling functions touching
the same tables for the same pattern.
Do this especially for:
@ -37,7 +43,11 @@ Do this especially for:
## 1. Push Filters To Storage
Both JavaScript `.filter()` and the Convex query `.filter()` method after a DB scan mean you already paid for the read. The Convex `.filter()` method has the same performance as filtering in JS, it does not push the predicate to the storage layer. Only `.withIndex()` and `.withSearchIndex()` actually reduce the documents scanned.
Both JavaScript `.filter()` and the Convex query `.filter()` method after a DB
scan mean you already paid for the read. The Convex `.filter()` method has the
same performance as filtering in JS, it does not push the predicate to the
storage layer. Only `.withIndex()` and `.withSearchIndex()` actually reduce the
documents scanned.
Prefer:
@ -87,17 +97,22 @@ export const listOpen = query({
### Migration rule for indexes
New indexes on partially backfilled fields can create correctness bugs during rollout.
New indexes on partially backfilled fields can create correctness bugs during
rollout.
Important Convex detail:
`undefined !== false`
If an older document is missing a field entirely, it will not match a compound index entry that expects `false`.
If an older document is missing a field entirely, it will not match a compound
index entry that expects `false`.
Do not trust old comments saying a field is "not backfilled" or "already backfilled". Verify.
Do not trust old comments saying a field is "not backfilled" or "already
backfilled". Verify.
If correctness depends on handling old and new states during rollout, do not improvise a partial-backfill workaround in the hot path. Use a migration-safe rollout and consult `skills/convex-migration-helper/SKILL.md`.
If correctness depends on handling old and new states during rollout, do not
improvise a partial-backfill workaround in the hot path. Use a migration-safe
rollout and consult `skills/convex-migration-helper/SKILL.md`.
```ts
// Bad: optional booleans can miss older rows where the field is undefined
@ -115,7 +130,10 @@ const projects = await ctx.db
### Check for redundant indexes
Indexes like `by_foo` and `by_foo_and_bar` are usually redundant. You only need `by_foo_and_bar`, since you can query it with just the `foo` condition and omit `bar`. Extra indexes add storage cost and write overhead on every insert, patch, and delete.
Indexes like `by_foo` and `by_foo_and_bar` are usually redundant. You only need
`by_foo_and_bar`, since you can query it with just the `foo` condition and omit
`bar`. Extra indexes add storage cost and write overhead on every insert, patch,
and delete.
```ts
// Bad: two indexes where one would do
@ -126,19 +144,24 @@ defineTable({ team: v.id("teams"), user: v.id("users") })
```ts
// Good: single compound index serves both query patterns
defineTable({ team: v.id("teams"), user: v.id("users") }).index("by_team_and_user", [
"team",
"user",
]);
defineTable({ team: v.id("teams"), user: v.id("users") }).index(
"by_team_and_user",
["team", "user"],
);
```
Exception: `.index("by_foo", ["foo"])` is really an index on `foo` + `_creationTime`, while `.index("by_foo_and_bar", ["foo", "bar"])` is on `foo` + `bar` + `_creationTime`. If you need results sorted by `foo` then `_creationTime`, you need the single-field index because the compound one would sort by `bar` first.
Exception: `.index("by_foo", ["foo"])` is really an index on `foo` +
`_creationTime`, while `.index("by_foo_and_bar", ["foo", "bar"])` is on `foo` +
`bar` + `_creationTime`. If you need results sorted by `foo` then
`_creationTime`, you need the single-field index because the compound one would
sort by `bar` first.
## 2. Minimize Data Sources
Trace every read.
If a function resolves a foreign key for a tiny display field and a denormalized copy already exists, prefer the denormalized field on the hot path.
If a function resolves a foreign key for a tiny display field and a denormalized
copy already exists, prefer the denormalized field on the hot path.
### When to denormalize
@ -152,7 +175,8 @@ Useful mental model:
`join_cost = rows_per_page x foreign_doc_size x pages_per_second`
Small-table joins are often fine. Large-document joins for tiny fields on hot list pages are usually not.
Small-table joins are often fine. Large-document joins for tiny fields on hot
list pages are usually not.
### Fallback rule
@ -171,7 +195,8 @@ const ownerName = project.ownerName ?? "Unknown owner";
```ts
// Good: denormalized data is an optimization, not the only source of truth
const ownerName = project.ownerName ?? (await ctx.db.get(project.ownerId))?.name ?? null;
const ownerName =
project.ownerName ?? (await ctx.db.get(project.ownerId))?.name ?? null;
```
Bad lookup map pattern:
@ -195,9 +220,11 @@ const ownersById =
### No denormalized copy yet
Prefer adding fields to an existing summary, companion, or digest table instead of bloating the primary hot-path table.
Prefer adding fields to an existing summary, companion, or digest table instead
of bloating the primary hot-path table.
If introducing the new field or table requires a staged rollout, backfill, or old/new-shape handling, use the migration helper skill for the rollout plan.
If introducing the new field or table requires a staged rollout, backfill, or
old/new-shape handling, use the migration helper skill for the rollout plan.
Rollout order:
@ -208,7 +235,8 @@ Rollout order:
## 3. Minimize Row Size
Hot list pages should read the smallest document shape that still answers the UI.
Hot list pages should read the smallest document shape that still answers the
UI.
Prefer summary or digest tables over full source tables when:
@ -216,12 +244,17 @@ Prefer summary or digest tables over full source tables when:
- source documents are large
- the query is high volume
An 800 byte summary row is materially cheaper than a 3 KB full document on a hot page.
An 800 byte summary row is materially cheaper than a 3 KB full document on a hot
page.
Digest tables are a tradeoff, not a default:
- Worth it when the path is clearly hot, the source rows are much larger than the UI needs, or many readers are repeatedly paying the same join and payload cost
- Probably not worth it when an indexed read on the source table is already cheap enough, the table is still small, or the extra write and migration complexity would dominate the benefit
- Worth it when the path is clearly hot, the source rows are much larger than
the UI needs, or many readers are repeatedly paying the same join and payload
cost
- Probably not worth it when an indexed read on the source table is already
cheap enough, the table is still small, or the extra write and migration
complexity would dominate the benefit
```ts
// Bad: list page reads source docs, then joins owner data per row
@ -242,11 +275,14 @@ const projects = await ctx.db
## 4. Isolate Frequently-Updated Fields
Convex already no-ops unchanged writes. The invalidation problem here is real writes hitting documents that many queries subscribe to.
Convex already no-ops unchanged writes. The invalidation problem here is real
writes hitting documents that many queries subscribe to.
Move high-churn fields like `lastSeen`, counters, presence, or ephemeral status off widely-read documents when most readers do not need them.
Move high-churn fields like `lastSeen`, counters, presence, or ephemeral status
off widely-read documents when most readers do not need them.
Apply this across sibling writers too. Splitting one write path does not help much if three other mutations still update the same widely-read document.
Apply this across sibling writers too. Splitting one write path does not help
much if three other mutations still update the same widely-read document.
```ts
// Bad: every presence heartbeat invalidates subscribers to the whole profile
@ -289,7 +325,9 @@ Prefer:
- local state for pagination
- caching where appropriate
Do not treat subscriptions as automatically wrong here. Prefer point-in-time reads only when the product does not need live freshness and the reactive cost is material. See `subscription-cost.md` for detailed patterns.
Do not treat subscriptions as automatically wrong here. Prefer point-in-time
reads only when the product does not need live freshness and the reactive cost
is material. See `subscription-cost.md` for detailed patterns.
### High-read, high-write
@ -305,18 +343,21 @@ Reactive queries may be worth the ongoing cost.
### Reactive queries
Every `ctx.db.get()` and `ctx.db.query()` contributes to the invalidation set for the query.
Every `ctx.db.get()` and `ctx.db.query()` contributes to the invalidation set
for the query.
On the client:
- `useQuery` creates a live subscription
- `usePaginatedQuery` creates a live subscription per page
For low-freshness flows, consider a point-in-time read instead of a live subscription only when the product does not need updates pushed automatically.
For low-freshness flows, consider a point-in-time read instead of a live
subscription only when the product does not need updates pushed automatically.
### Point-in-time reads
Framework helpers, server-rendered fetches, or one-shot client reads can avoid ongoing subscription cost when live updates are not useful.
Framework helpers, server-rendered fetches, or one-shot client reads can avoid
ongoing subscription cost when live updates are not useful.
Use them for:
@ -327,7 +368,8 @@ Use them for:
### Triggers and fan-out
Triggers fire on every write, including writes that did not materially change the document.
Triggers fire on every write, including writes that did not materially change
the document.
When a write exists only to keep derived state in sync:
@ -348,7 +390,8 @@ for global stats that do not need live updates every second.
### Backfills
For larger backfills, use cursor-based, self-scheduling `internalMutation` jobs or the migrations component.
For larger backfills, use cursor-based, self-scheduling `internalMutation` jobs
or the migrations component.
Deploy code that can handle both states before running the backfill.

View File

@ -1,10 +1,14 @@
# OCC Conflict Resolution
Use these rules when insights, logs, or dashboard health show OCC (Optimistic Concurrency Control) conflicts, mutation retries, or write contention on hot tables.
Use these rules when insights, logs, or dashboard health show OCC (Optimistic
Concurrency Control) conflicts, mutation retries, or write contention on hot
tables.
## Core Principle
Convex uses optimistic concurrency control. When two transactions read or write overlapping data, one succeeds and the other retries automatically. High contention means wasted work and increased latency.
Convex uses optimistic concurrency control. When two transactions read or write
overlapping data, one succeeds and the other retries automatically. High
contention means wasted work and increased latency.
## Symptoms
@ -17,21 +21,31 @@ Convex uses optimistic concurrency control. When two transactions read or write
### Hot documents
Multiple mutations writing to the same document concurrently. Classic examples: a global counter, a shared settings row, or a "last updated" timestamp on a parent record.
Multiple mutations writing to the same document concurrently. Classic examples:
a global counter, a shared settings row, or a "last updated" timestamp on a
parent record.
### Broad read sets causing false conflicts
A query that scans a large table range creates a broad read set. If any write touches that range, the query's transaction conflicts even if the specific document the query cared about was not modified.
A query that scans a large table range creates a broad read set. If any write
touches that range, the query's transaction conflicts even if the specific
document the query cared about was not modified.
### Fan-out from triggers or cascading writes
A single user action triggers multiple mutations that all touch related documents. Each mutation competes with the others.
A single user action triggers multiple mutations that all touch related
documents. Each mutation competes with the others.
Database triggers (e.g. from `convex-helpers`) run inside the same transaction as the mutation that caused them. If a trigger does heavy work, reads extra tables, or writes to many documents, it extends the transaction's read/write set and increases the window for conflicts. Keep trigger logic minimal, or move expensive derived work to a scheduled function.
Database triggers (e.g. from `convex-helpers`) run inside the same transaction
as the mutation that caused them. If a trigger does heavy work, reads extra
tables, or writes to many documents, it extends the transaction's read/write set
and increases the window for conflicts. Keep trigger logic minimal, or move
expensive derived work to a scheduled function.
### Write-then-read chains
A mutation writes a document, then a reactive query re-reads it, then another mutation writes it again. Under load, these chains stack up.
A mutation writes a document, then a reactive query re-reads it, then another
mutation writes it again. Under load, these chains stack up.
## Fix Order
@ -75,7 +89,9 @@ Aggregate the shards in a query or scheduled job when you need the total.
### 3. Move non-critical work to scheduled functions
If a mutation does primary work plus secondary bookkeeping (analytics, non-critical notifications, cache warming), the bookkeeping extends the transaction's lifetime and read/write set.
If a mutation does primary work plus secondary bookkeeping (analytics,
non-critical notifications, cache warming), the bookkeeping extends the
transaction's lifetime and read/write set.
```ts
// Bad: canonical write and derived work happen in the same transaction
@ -98,13 +114,20 @@ await ctx.scheduler.runAfter(0, internal.users.recordNameChangeAnalytics, {
### 4. Combine competing writes
If two mutations must update the same document atomically, consider whether they can be combined into a single mutation call from the client, reducing round trips and conflict windows.
If two mutations must update the same document atomically, consider whether they
can be combined into a single mutation call from the client, reducing round
trips and conflict windows.
Do not introduce artificial locks or queues unless the above steps have been tried first.
Do not introduce artificial locks or queues unless the above steps have been
tried first.
## Related: Invalidation Scope
Splitting hot documents also reduces subscription invalidation, not just OCC contention. If a document is written frequently and read by many queries, those queries re-run on every write even when the fields they care about have not changed. See `subscription-cost.md` section 4 ("Isolate frequently-updated fields") for that pattern.
Splitting hot documents also reduces subscription invalidation, not just OCC
contention. If a document is written frequently and read by many queries, those
queries re-run on every write even when the fields they care about have not
changed. See `subscription-cost.md` section 4 ("Isolate frequently-updated
fields") for that pattern.
## Verification

View File

@ -1,14 +1,20 @@
# Subscription Cost
Use these rules when the problem is too many reactive subscriptions, queries invalidating too frequently, or React components re-rendering excessively due to Convex state changes.
Use these rules when the problem is too many reactive subscriptions, queries
invalidating too frequently, or React components re-rendering excessively due to
Convex state changes.
## Core Principle
Every `useQuery` and `usePaginatedQuery` call creates a live subscription. The server tracks the query's read set and re-executes the query whenever any document in that read set changes. Subscription cost scales with:
Every `useQuery` and `usePaginatedQuery` call creates a live subscription. The
server tracks the query's read set and re-executes the query whenever any
document in that read set changes. Subscription cost scales with:
`subscriptions x invalidation_frequency x query_cost`
Subscriptions are not inherently bad. Convex reactivity is often the right default. The goal is to reduce unnecessary invalidation work, not to eliminate subscriptions on principle.
Subscriptions are not inherently bad. Convex reactivity is often the right
default. The goal is to reduce unnecessary invalidation work, not to eliminate
subscriptions on principle.
## Symptoms
@ -22,35 +28,47 @@ Subscriptions are not inherently bad. Convex reactivity is often the right defau
### Reactive queries on low-freshness flows
Some user flows are read-heavy and do not need live updates every time the underlying data changes. In those cases, ongoing subscriptions may cost more than they are worth.
Some user flows are read-heavy and do not need live updates every time the
underlying data changes. In those cases, ongoing subscriptions may cost more
than they are worth.
### Overly broad queries
A query that returns a large result set invalidates whenever any document in that set changes. The broader the query, the more frequent the invalidation.
A query that returns a large result set invalidates whenever any document in
that set changes. The broader the query, the more frequent the invalidation.
### Too many subscriptions per page
A page with 20 list items, each running its own `useQuery` to fetch related data, creates 20+ subscriptions per visitor.
A page with 20 list items, each running its own `useQuery` to fetch related
data, creates 20+ subscriptions per visitor.
### Paginated queries keeping all pages live
`usePaginatedQuery` with `loadMore` keeps every loaded page subscribed. On a page where a user has scrolled through 10 pages, all 10 stay reactive.
`usePaginatedQuery` with `loadMore` keeps every loaded page subscribed. On a
page where a user has scrolled through 10 pages, all 10 stay reactive.
### Frequently-updated fields on widely-read documents
A document that many queries touch gets a frequently-updated field (like `lastSeen`, `lastActiveAt`, or a counter). Every write to that field invalidates every subscription that reads the document, even if those subscriptions never use the field. This is different from OCC conflicts (see `occ-conflicts.md`), which are write-vs-write contention. This is write-vs-subscription: the write succeeds fine, but it forces hundreds of queries to re-run for no reason.
A document that many queries touch gets a frequently-updated field (like
`lastSeen`, `lastActiveAt`, or a counter). Every write to that field invalidates
every subscription that reads the document, even if those subscriptions never
use the field. This is different from OCC conflicts (see `occ-conflicts.md`),
which are write-vs-write contention. This is write-vs-subscription: the write
succeeds fine, but it forces hundreds of queries to re-run for no reason.
## Fix Order
### 1. Use point-in-time reads when live updates are not valuable
Keep `useQuery` and `usePaginatedQuery` by default when the product benefits from fresh live data.
Keep `useQuery` and `usePaginatedQuery` by default when the product benefits
from fresh live data.
Consider a point-in-time read instead when all of these are true:
- the flow is high-read
- the underlying data changes less often than users need to see
- explicit refresh, periodic refresh, or a fresh read on navigation is acceptable
- explicit refresh, periodic refresh, or a fresh read on navigation is
acceptable
Possible implementations depend on environment:
@ -99,7 +117,8 @@ Keep reactive for:
### 2. Batch related data into fewer queries
Instead of N components each fetching their own related data, fetch it in a single query.
Instead of N components each fetching their own related data, fetch it in a
single query.
```ts
// Bad: each card fetches its own author
@ -119,13 +138,17 @@ function ProjectList() {
}
```
This can use denormalized fields or server-side joins in the query handler. Either way, it is one subscription instead of N.
This can use denormalized fields or server-side joins in the query handler.
Either way, it is one subscription instead of N.
This is not automatically better. If the combined query becomes much broader and invalidates much more often, several narrower subscriptions may be the better tradeoff. Optimize for total invalidation cost, not raw subscription count.
This is not automatically better. If the combined query becomes much broader and
invalidates much more often, several narrower subscriptions may be the better
tradeoff. Optimize for total invalidation cost, not raw subscription count.
### 3. Use skip to avoid unnecessary subscriptions
The `"skip"` value prevents a subscription from being created when the arguments are not ready.
The `"skip"` value prevents a subscription from being created when the arguments
are not ready.
```ts
// Bad: subscribes with undefined args, wastes a subscription slot
@ -134,12 +157,17 @@ const profile = useQuery(api.users.getProfile, { userId: selectedId! });
```ts
// Good: skip when there is nothing to fetch
const profile = useQuery(api.users.getProfile, selectedId ? { userId: selectedId } : "skip");
const profile = useQuery(
api.users.getProfile,
selectedId ? { userId: selectedId } : "skip",
);
```
### 4. Isolate frequently-updated fields into separate documents
If a document is widely read but has a field that changes often, move that field to a separate document. Queries that do not need the field will no longer be invalidated by its writes.
If a document is widely read but has a field that changes often, move that field
to a separate document. Queries that do not need the field will no longer be
invalidated by its writes.
```ts
// Bad: lastSeen lives on the user doc, every heartbeat invalidates
@ -164,17 +192,31 @@ const heartbeats = defineTable({
});
```
Queries that only need `name` and `email` no longer re-run on every heartbeat. Queries that actually need online status fetch the heartbeat document explicitly.
Queries that only need `name` and `email` no longer re-run on every heartbeat.
Queries that actually need online status fetch the heartbeat document
explicitly.
For an even further optimization, if you only need a coarse online/offline boolean rather than the exact `lastSeen` timestamp, add a separate presence document with an `isOnline` flag. Update it immediately when a user comes online, and use a cron to batch-mark users offline when their heartbeat goes stale. This way the presence query only invalidates when online status actually changes, not on every heartbeat.
For an even further optimization, if you only need a coarse online/offline
boolean rather than the exact `lastSeen` timestamp, add a separate presence
document with an `isOnline` flag. Update it immediately when a user comes
online, and use a cron to batch-mark users offline when their heartbeat goes
stale. This way the presence query only invalidates when online status actually
changes, not on every heartbeat.
### 5. Use the aggregate component for counts and sums
Reactive global counts (`SELECT COUNT(*)` equivalent) invalidate on every insert or delete to the table. The [`@convex-dev/aggregate`](https://www.npmjs.com/package/@convex-dev/aggregate) component maintains denormalized COUNT, SUM, and MAX values efficiently so you do not need a reactive query scanning the full table.
Reactive global counts (`SELECT COUNT(*)` equivalent) invalidate on every insert
or delete to the table. The
[`@convex-dev/aggregate`](https://www.npmjs.com/package/@convex-dev/aggregate)
component maintains denormalized COUNT, SUM, and MAX values efficiently so you
do not need a reactive query scanning the full table.
Use it for leaderboards, totals, "X items" badges, or any stat that would otherwise require scanning many rows reactively.
Use it for leaderboards, totals, "X items" badges, or any stat that would
otherwise require scanning many rows reactively.
If the aggregate component is not appropriate, prefer point-in-time reads for global stats, or precomputed summary rows updated by a cron or trigger, over reactive queries that scan large tables.
If the aggregate component is not appropriate, prefer point-in-time reads for
global stats, or precomputed summary rows updated by a cron or trigger, over
reactive queries that scan large tables.
### 6. Narrow query read sets
@ -202,7 +244,9 @@ Writes to fields not in the digest table do not invalidate the digest query.
### 7. Remove `Date.now()` from queries
Using `Date.now()` inside a query defeats Convex's query cache. The cache is invalidated frequently to avoid showing stale time-dependent results, which increases database work even when the underlying data has not changed.
Using `Date.now()` inside a query defeats Convex's query cache. The cache is
invalidated frequently to avoid showing stale time-dependent results, which
increases database work even when the underlying data has not changed.
```ts
// Bad: Date.now() defeats query caching and causes frequent re-evaluation
@ -220,19 +264,25 @@ const releasedPosts = await ctx.db
.take(100);
```
If the query must compare against a time value, pass it as an explicit argument from the client and round it to a coarse interval (e.g. the most recent minute) so requests within that window share the same cache entry.
If the query must compare against a time value, pass it as an explicit argument
from the client and round it to a coarse interval (e.g. the most recent minute)
so requests within that window share the same cache entry.
### 8. Consider pagination strategy
For long lists where users scroll through many pages:
- If the data does not need live updates, use point-in-time fetching with manual "load more"
- If it does need live updates, accept the subscription cost but limit the number of loaded pages
- If the data does not need live updates, use point-in-time fetching with manual
"load more"
- If it does need live updates, accept the subscription cost but limit the
number of loaded pages
- Consider whether older pages can be unloaded as the user scrolls forward
### 9. Separate backend cost from UI churn
If the main problem is loading flash or UI churn when query arguments change, stabilizing the reactive UI behavior may be better than replacing reactivity altogether.
If the main problem is loading flash or UI churn when query arguments change,
stabilizing the reactive UI behavior may be better than replacing reactivity
altogether.
Treat this as a UX problem first when:
@ -245,5 +295,6 @@ Treat this as a UX problem first when:
1. Subscription count in dashboard is lower for the affected pages
2. UI responsiveness has improved
3. React profiling shows fewer unnecessary re-renders
4. Surfaces that do not need live updates are not paying for persistent subscriptions unnecessarily
4. Surfaces that do not need live updates are not paying for persistent
subscriptions unnecessarily
5. Sibling pages with similar patterns were updated consistently

View File

@ -1,6 +1,8 @@
---
name: convex-quickstart
description: Creates or adds Convex to an app. Use for new Convex projects, npm create convex@latest, frontend setup, env vars, or the first npx convex dev run.
description:
Creates or adds Convex to an app. Use for new Convex projects, npm create
convex@latest, frontend setup, env vars, or the first npx convex dev run.
---
# Convex Quickstart
@ -15,8 +17,10 @@ Set up a working Convex project as fast as possible.
## When Not to Use
- The project already has Convex installed and `convex/` exists - just start building
- You only need to add auth to an existing Convex app - use the `convex-setup-auth` skill
- The project already has Convex installed and `convex/` exists - just start
building
- You only need to add auth to an existing Convex app - use the
`convex-setup-auth` skill
## Workflow
@ -28,7 +32,8 @@ Set up a working Convex project as fast as possible.
## Path 1: New Project (Recommended)
Use the official scaffolding tool. It creates a complete project with the frontend framework, Convex backend, and all config wired together.
Use the official scaffolding tool. It creates a complete project with the
frontend framework, Convex backend, and all config wired together.
### Pick a template
@ -42,7 +47,8 @@ Use the official scaffolding tool. It creates a complete project with the fronte
| `nextjs-lucia-shadcn` | Next.js + Lucia auth + shadcn/ui |
| `bare` | Convex backend only, no frontend |
If the user has not specified a preference, default to `react-vite-shadcn` for simple apps or `nextjs-shadcn` for apps that need SSR or API routes.
If the user has not specified a preference, default to `react-vite-shadcn` for
simple apps or `nextjs-shadcn` for apps that need SSR or API routes.
You can also use any GitHub repo as a template:
@ -61,7 +67,8 @@ cd my-app
npm install
```
The scaffolding tool creates files but does not run `npm install`, so you must run it yourself.
The scaffolding tool creates files but does not run `npm install`, so you must
run it yourself.
To scaffold in the current directory (if it is empty):
@ -72,20 +79,28 @@ npm install
### Start the dev loop
`npx convex dev` is a long-running watcher process that syncs backend code to a Convex deployment on every save. It also requires authentication on first run (browser-based OAuth). Both of these make it unsuitable for an agent to run directly.
`npx convex dev` is a long-running watcher process that syncs backend code to a
Convex deployment on every save. It also requires authentication on first run
(browser-based OAuth). Both of these make it unsuitable for an agent to run
directly.
**Ask the user to run this themselves:**
Tell the user to run `npx convex dev` in their terminal. On first run it will prompt them to log in or develop anonymously. Once running, it will:
Tell the user to run `npx convex dev` in their terminal. On first run it will
prompt them to log in or develop anonymously. Once running, it will:
- Create a Convex project and dev deployment
- Write the deployment URL to `.env.local`
- Create the `convex/` directory with generated types
- Watch for changes and sync continuously
The user should keep `npx convex dev` running in the background while you work on code. The watcher will automatically pick up any files you create or edit in `convex/`.
The user should keep `npx convex dev` running in the background while you work
on code. The watcher will automatically pick up any files you create or edit in
`convex/`.
**Exception - cloud or headless agents:** Environments that cannot open a browser for interactive login should use Agent Mode (see below) to run anonymously without user interaction.
**Exception - cloud or headless agents:** Environments that cannot open a
browser for interactive login should use Agent Mode (see below) to run
anonymously without user interaction.
### Start the frontend
@ -122,7 +137,8 @@ Proceed to adding schema, functions, and UI.
## Path 2: Add Convex to an Existing App
Use this when the user already has a frontend project and wants to add Convex as the backend.
Use this when the user already has a frontend project and wants to add Convex as
the backend.
### Install
@ -132,7 +148,10 @@ npm install convex
### Initialize and start dev loop
Ask the user to run `npx convex dev` in their terminal. This handles login, creates the `convex/` directory, writes the deployment URL to `.env.local`, and starts the file watcher. See the notes in Path 1 about why the agent should not run this directly.
Ask the user to run `npx convex dev` in their terminal. This handles login,
creates the `convex/` directory, writes the deployment URL to `.env.local`, and
starts the file watcher. See the notes in Path 1 about why the agent should not
run this directly.
### Wire up the provider
@ -143,7 +162,9 @@ Create the `ConvexReactClient` at module scope, not inside a component:
```tsx
// Bad: re-creates the client on every render
function App() {
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
const convex = new ConvexReactClient(
import.meta.env.VITE_CONVEX_URL as string,
);
return <ConvexProvider client={convex}>...</ConvexProvider>;
}
@ -194,7 +215,11 @@ export function ConvexClientProvider({ children }: { children: ReactNode }) {
// app/layout.tsx
import { ConvexClientProvider } from "./ConvexClientProvider";
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
@ -207,7 +232,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
#### Other frameworks
For Vue, Svelte, React Native, TanStack Start, Remix, and others, follow the matching quickstart guide:
For Vue, Svelte, React Native, TanStack Start, Remix, and others, follow the
matching quickstart guide:
- [Vue](https://docs.convex.dev/quickstart/vue)
- [Svelte](https://docs.convex.dev/quickstart/svelte)
@ -231,7 +257,9 @@ The env var name depends on the framework:
## Agent Mode (Cloud and Headless Agents)
When running in a cloud or headless agent environment where interactive browser login is not possible, set `CONVEX_AGENT_MODE=anonymous` to use a local anonymous deployment.
When running in a cloud or headless agent environment where interactive browser
login is not possible, set `CONVEX_AGENT_MODE=anonymous` to use a local
anonymous deployment.
Add `CONVEX_AGENT_MODE=anonymous` to `.env.local`, or set it inline:
@ -239,7 +267,8 @@ Add `CONVEX_AGENT_MODE=anonymous` to `.env.local`, or set it inline:
CONVEX_AGENT_MODE=anonymous npx convex dev
```
This runs a local Convex backend on the VM without requiring authentication, and avoids conflicting with the user's personal dev deployment.
This runs a local Convex backend on the VM without requiring authentication, and
avoids conflicting with the user's personal dev deployment.
## Verify the Setup
@ -251,7 +280,8 @@ After setup, confirm everything is working:
## Writing Your First Function
Once the project is set up, create a schema and a query to verify the full loop works.
Once the project is set up, create a schema and a query to verify the full loop
works.
`convex/schema.ts`:
@ -288,7 +318,8 @@ export const create = mutation({
});
```
Use in a React component (adjust the import path based on your file location relative to `convex/`):
Use in a React component (adjust the import path based on your file location
relative to `convex/`):
```tsx
import { useQuery, useMutation } from "convex/react";
@ -311,7 +342,8 @@ function Tasks() {
## Development vs Production
Always use `npx convex dev` during development. It runs against your personal dev deployment and syncs code on save.
Always use `npx convex dev` during development. It runs against your personal
dev deployment and syncs code on save.
When ready to ship, deploy to production:
@ -319,21 +351,25 @@ When ready to ship, deploy to production:
npx convex deploy
```
This pushes to the production deployment, which is separate from dev. Do not use `deploy` during development.
This pushes to the production deployment, which is separate from dev. Do not use
`deploy` during development.
## Next Steps
- Add authentication: use the `convex-setup-auth` skill
- Design your schema: see [Schema docs](https://docs.convex.dev/database/schemas)
- Design your schema: see
[Schema docs](https://docs.convex.dev/database/schemas)
- Build components: use the `convex-create-component` skill
- Plan a migration: use the `convex-migration-helper` skill
- Add file storage: see [File Storage docs](https://docs.convex.dev/file-storage)
- Add file storage: see
[File Storage docs](https://docs.convex.dev/file-storage)
- Set up cron jobs: see [Scheduling docs](https://docs.convex.dev/scheduling)
## Checklist
- [ ] Determined starting point: new project or existing app
- [ ] If new project: scaffolded with `npm create convex@latest` using appropriate template
- [ ] If new project: scaffolded with `npm create convex@latest` using
appropriate template
- [ ] If existing app: installed `convex` and wired up the provider
- [ ] User has `npx convex dev` running and connected to a deployment
- [ ] `convex/_generated/` directory exists with types

View File

@ -1,10 +1,14 @@
interface:
display_name: "Convex Quickstart"
short_description: "Start a new Convex app or add Convex to an existing frontend."
short_description:
"Start a new Convex app or add Convex to an existing frontend."
icon_small: "./assets/icon.svg"
icon_large: "./assets/icon.svg"
brand_color: "#F97316"
default_prompt: "Set up Convex for this project as fast as possible. First decide whether this is a new app or an existing app, then scaffold or integrate Convex and verify the setup works."
default_prompt:
"Set up Convex for this project as fast as possible. First decide whether
this is a new app or an existing app, then scaffold or integrate Convex and
verify the setup works."
policy:
allow_implicit_invocation: true

View File

@ -1,25 +1,30 @@
---
name: convex-setup-auth
description: Sets up Convex auth, identity mapping, and access control. Use for login, auth providers, users tables, protected functions, or roles in a Convex app.
description:
Sets up Convex auth, identity mapping, and access control. Use for login, auth
providers, users tables, protected functions, or roles in a Convex app.
---
# Convex Authentication Setup
Implement secure authentication in Convex with user management and access control.
Implement secure authentication in Convex with user management and access
control.
## When to Use
- Setting up authentication for the first time
- Implementing user management (users table, identity mapping)
- Creating authentication helper functions
- Setting up auth providers (Convex Auth, Clerk, WorkOS AuthKit, Auth0, custom JWT)
- Setting up auth providers (Convex Auth, Clerk, WorkOS AuthKit, Auth0, custom
JWT)
## When Not to Use
- Auth for a non-Convex backend
- Pure OAuth/OIDC documentation without a Convex implementation
- Debugging unrelated bugs that happen to surface near auth code
- The auth provider is already fully configured and the user only needs a one-line fix
- The auth provider is already fully configured and the user only needs a
one-line fix
## First Step: Choose the Auth Provider
@ -27,34 +32,49 @@ Convex supports multiple authentication approaches. Do not assume a provider.
Before writing setup code:
1. Ask the user which auth solution they want, unless the repository already makes it obvious
2. If the repo already uses a provider, continue with that provider unless the user wants to switch
3. If the user has not chosen a provider and the repo does not make it obvious, ask before proceeding
1. Ask the user which auth solution they want, unless the repository already
makes it obvious
2. If the repo already uses a provider, continue with that provider unless the
user wants to switch
3. If the user has not chosen a provider and the repo does not make it obvious,
ask before proceeding
Common options:
- [Convex Auth](https://docs.convex.dev/auth/convex-auth) - good default when the user wants auth handled directly in Convex
- [Clerk](https://docs.convex.dev/auth/clerk) - use when the app already uses Clerk or the user wants Clerk's hosted auth features
- [WorkOS AuthKit](https://docs.convex.dev/auth/authkit/) - use when the app already uses WorkOS or the user wants AuthKit specifically
- [Auth0](https://docs.convex.dev/auth/auth0) - use when the app already uses Auth0
- Custom JWT provider - use when integrating an existing auth system not covered above
- [Convex Auth](https://docs.convex.dev/auth/convex-auth) - good default when
the user wants auth handled directly in Convex
- [Clerk](https://docs.convex.dev/auth/clerk) - use when the app already uses
Clerk or the user wants Clerk's hosted auth features
- [WorkOS AuthKit](https://docs.convex.dev/auth/authkit/) - use when the app
already uses WorkOS or the user wants AuthKit specifically
- [Auth0](https://docs.convex.dev/auth/auth0) - use when the app already uses
Auth0
- Custom JWT provider - use when integrating an existing auth system not covered
above
Look for signals in the repo before asking:
- Dependencies such as `@clerk/*`, `@workos-inc/*`, `@auth0/*`, or Convex Auth packages
- Existing files such as `convex/auth.config.ts`, auth middleware, provider wrappers, or login components
- Dependencies such as `@clerk/*`, `@workos-inc/*`, `@auth0/*`, or Convex Auth
packages
- Existing files such as `convex/auth.config.ts`, auth middleware, provider
wrappers, or login components
- Environment variables that clearly point at a provider
## After Choosing a Provider
Read the provider's official guide and the matching local reference file:
- Convex Auth: [official docs](https://docs.convex.dev/auth/convex-auth), then `references/convex-auth.md`
- Clerk: [official docs](https://docs.convex.dev/auth/clerk), then `references/clerk.md`
- WorkOS AuthKit: [official docs](https://docs.convex.dev/auth/authkit/), then `references/workos-authkit.md`
- Auth0: [official docs](https://docs.convex.dev/auth/auth0), then `references/auth0.md`
- Convex Auth: [official docs](https://docs.convex.dev/auth/convex-auth), then
`references/convex-auth.md`
- Clerk: [official docs](https://docs.convex.dev/auth/clerk), then
`references/clerk.md`
- WorkOS AuthKit: [official docs](https://docs.convex.dev/auth/authkit/), then
`references/workos-authkit.md`
- Auth0: [official docs](https://docs.convex.dev/auth/auth0), then
`references/auth0.md`
The local reference files contain the concrete workflow, expected files and env vars, gotchas, and validation checks.
The local reference files contain the concrete workflow, expected files and env
vars, gotchas, and validation checks.
Use those sources for:
@ -67,15 +87,25 @@ Use those sources for:
For shared auth behavior, use the official Convex docs as the source of truth:
- [Auth in Functions](https://docs.convex.dev/auth/functions-auth) for `ctx.auth.getUserIdentity()`
- [Storing Users in the Convex Database](https://docs.convex.dev/auth/database-auth) for optional app-level user storage
- [Authentication](https://docs.convex.dev/auth) for general auth and authorization guidance
- [Convex Auth Authorization](https://labs.convex.dev/auth/authz) when the provider is Convex Auth
- [Auth in Functions](https://docs.convex.dev/auth/functions-auth) for
`ctx.auth.getUserIdentity()`
- [Storing Users in the Convex Database](https://docs.convex.dev/auth/database-auth)
for optional app-level user storage
- [Authentication](https://docs.convex.dev/auth) for general auth and
authorization guidance
- [Convex Auth Authorization](https://labs.convex.dev/auth/authz) when the
provider is Convex Auth
Prefer official docs over recalled steps, because provider CLIs and Convex Auth internals change between versions. Inventing setup from memory risks outdated patterns.
For third-party providers, only add app-level user storage if the app actually needs user documents in Convex. Not every app needs a `users` table.
For Convex Auth, follow the Convex Auth docs and built-in auth tables rather than adding a parallel `users` table plus `storeUser` flow, because Convex Auth already manages user records internally.
After running provider initialization commands, verify generated files and complete the post-init wiring steps the provider reference calls out. Initialization commands rarely finish the entire integration.
Prefer official docs over recalled steps, because provider CLIs and Convex Auth
internals change between versions. Inventing setup from memory risks outdated
patterns. For third-party providers, only add app-level user storage if the app
actually needs user documents in Convex. Not every app needs a `users` table.
For Convex Auth, follow the Convex Auth docs and built-in auth tables rather
than adding a parallel `users` table plus `storeUser` flow, because Convex Auth
already manages user records internally. After running provider initialization
commands, verify generated files and complete the post-init wiring steps the
provider reference calls out. Initialization commands rarely finish the entire
integration.
## Core Pattern: Protecting Backend Functions
@ -101,7 +131,9 @@ export const getMyProfile = query({
return await ctx.db
.query("users")
.withIndex("by_tokenIdentifier", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier))
.withIndex("by_tokenIdentifier", (q) =>
q.eq("tokenIdentifier", identity.tokenIdentifier),
)
.unique();
},
});
@ -113,15 +145,20 @@ export const getMyProfile = query({
2. Ask whether the user wants local-only setup or production-ready setup now
3. Read the matching provider reference file
4. Follow the official provider docs for current setup details
5. Follow the official Convex docs for shared backend auth behavior, user storage, and authorization patterns
5. Follow the official Convex docs for shared backend auth behavior, user
storage, and authorization patterns
6. Only add app-level user storage if the docs and app requirements call for it
7. Add authorization checks for ownership, roles, or team access only where the app needs them
8. Verify login state, protected queries, environment variables, and production configuration if requested
7. Add authorization checks for ownership, roles, or team access only where the
app needs them
8. Verify login state, protected queries, environment variables, and production
configuration if requested
If the flow blocks on interactive provider or deployment setup, ask the user explicitly for the exact human step needed, then continue after they complete it.
For UI-facing auth flows, offer to validate the real sign-up or sign-in flow after setup is done.
If the environment has browser automation tools, you can use them.
If it does not, give the user a short manual validation checklist instead.
If the flow blocks on interactive provider or deployment setup, ask the user
explicitly for the exact human step needed, then continue after they complete
it. For UI-facing auth flows, offer to validate the real sign-up or sign-in flow
after setup is done. If the environment has browser automation tools, you can
use them. If it does not, give the user a short manual validation checklist
instead.
## Reference Files
@ -138,9 +175,11 @@ If it does not, give the user a short manual validation checklist instead.
- [ ] Read the relevant provider reference file
- [ ] Asked whether the user wants local-only setup or production-ready setup
- [ ] Used the official provider docs for provider-specific wiring
- [ ] Used the official Convex docs for shared auth behavior and authorization patterns
- [ ] Used the official Convex docs for shared auth behavior and authorization
patterns
- [ ] Only added app-level user storage if the app actually needs it
- [ ] Did not invent a cross-provider `users` table or `storeUser` flow for Convex Auth
- [ ] Did not invent a cross-provider `users` table or `storeUser` flow for
Convex Auth
- [ ] Added authentication checks in protected backend functions
- [ ] Added authorization checks where the app actually needs them
- [ ] Clear error messages ("Not authenticated", "Unauthorized")

View File

@ -1,10 +1,14 @@
interface:
display_name: "Convex Setup Auth"
short_description: "Set up Convex auth, user identity mapping, and access control."
short_description:
"Set up Convex auth, user identity mapping, and access control."
icon_small: "./assets/icon.svg"
icon_large: "./assets/icon.svg"
brand_color: "#2563EB"
default_prompt: "Set up authentication for this Convex app. Figure out the provider first, then wire up the user model, identity mapping, and access control with the smallest solid implementation."
default_prompt:
"Set up authentication for this Convex app. Figure out the provider first,
then wire up the user model, identity mapping, and access control with the
smallest solid implementation."
policy:
allow_implicit_invocation: true

View File

@ -15,25 +15,35 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
3. Ask whether the user wants local-only setup or production-ready setup now
4. Read the official Convex and Auth0 guides before making changes
5. Ask whether they want the fastest setup path by installing the Auth0 CLI
6. If they agree, install the Auth0 CLI and do as much of the Auth0 app setup as possible through the CLI
6. If they agree, install the Auth0 CLI and do as much of the Auth0 app setup as
possible through the CLI
7. If they do not want the CLI path, use the Auth0 dashboard path instead
8. Complete the relevant Auth0 frontend quickstart if the app does not already have Auth0 wired up
8. Complete the relevant Auth0 frontend quickstart if the app does not already
have Auth0 wired up
9. Configure `convex/auth.config.ts` with the Auth0 domain and client ID
10. Set environment variables for local and production environments
11. Wrap the app with `Auth0Provider` and `ConvexProviderWithAuth0`
12. Gate Convex-backed UI with Convex auth state
13. Try to verify Convex reports the user as authenticated after Auth0 login
14. If the refresh-token path fails, stop improvising and send the user back to the official docs
15. If the user wants production-ready setup, make sure the production Auth0 tenant and env vars are also covered
14. If the refresh-token path fails, stop improvising and send the user back to
the official docs
15. If the user wants production-ready setup, make sure the production Auth0
tenant and env vars are also covered
## What To Do
- Read the official Convex and Auth0 guide before writing setup code
- Prefer the Auth0 CLI path for mechanical setup if the user is willing to install it, but do not present it as a fully validated end-to-end path yet
- Ask the user directly: "The fastest path is to install the Auth0 CLI so I can do more of this for you. If you want, I can install it and then only ask you to log in when needed. Would you like me to do that?"
- Make sure the app has already completed the relevant Auth0 quickstart for its frontend
- Prefer the Auth0 CLI path for mechanical setup if the user is willing to
install it, but do not present it as a fully validated end-to-end path yet
- Ask the user directly: "The fastest path is to install the Auth0 CLI so I can
do more of this for you. If you want, I can install it and then only ask you
to log in when needed. Would you like me to do that?"
- Make sure the app has already completed the relevant Auth0 quickstart for its
frontend
- Use the official examples for `Auth0Provider` and `ConvexProviderWithAuth0`
- If the Auth0 login or refresh flow starts failing in a way that is not clearly explained by the docs, say that plainly and fall back to the official docs instead of pretending the flow is validated
- If the Auth0 login or refresh flow starts failing in a way that is not clearly
explained by the docs, say that plainly and fall back to the official docs
instead of pretending the flow is validated
## Key Setup Areas
@ -56,11 +66,15 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
## Concrete Steps
1. Start by reading `https://docs.convex.dev/auth/auth0` and the relevant Auth0 quickstart for the app's framework
1. Start by reading `https://docs.convex.dev/auth/auth0` and the relevant Auth0
quickstart for the app's framework
2. Ask whether the user wants the Auth0 CLI path
3. If yes, install Auth0 CLI and have the user authenticate it with `auth0 login`
4. Use `auth0 apps create` with SPA settings, callback URL, logout URL, and web origins if creating a new app
5. If not using the CLI path, complete the relevant Auth0 frontend quickstart and create the Auth0 app in the dashboard
3. If yes, install Auth0 CLI and have the user authenticate it with
`auth0 login`
4. Use `auth0 apps create` with SPA settings, callback URL, logout URL, and web
origins if creating a new app
5. If not using the CLI path, complete the relevant Auth0 frontend quickstart
and create the Auth0 app in the dashboard
6. Get the Auth0 domain and client ID from the CLI output or the Auth0 dashboard
7. Install the Auth0 SDK for the app's framework
8. Create or update `convex/auth.config.ts` with the Auth0 domain and client ID
@ -69,31 +83,52 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
11. Replace plain `ConvexProvider` wiring with `ConvexProviderWithAuth0`
12. Run the normal Convex dev or deploy flow after backend config changes
13. Try the official provider config shown in the Convex docs
14. If login works but Convex auth or token refresh fails in a way you cannot clearly resolve, stop and tell the user to follow the official docs manually for now
15. Only claim success if the user can sign in and Convex recognizes the authenticated session
16. If the user wants production-ready setup, configure the production Auth0 tenant values and production environment variables too
14. If login works but Convex auth or token refresh fails in a way you cannot
clearly resolve, stop and tell the user to follow the official docs manually
for now
15. Only claim success if the user can sign in and Convex recognizes the
authenticated session
16. If the user wants production-ready setup, configure the production Auth0
tenant values and production environment variables too
## Gotchas
- The Convex docs assume the Auth0 side is already set up, so do not skip the Auth0 quickstart if the app is starting from scratch
- The Auth0 CLI is often the fastest path for a fresh setup, but it still requires the user to authenticate the CLI to their Auth0 tenant
- If the user agrees to install the Auth0 CLI, do the mechanical setup yourself instead of bouncing them through the dashboard
- If login succeeds but Convex still reports unauthenticated, double-check `convex/auth.config.ts` and whether the backend config was synced
- We were able to automate Auth0 app creation and Convex config wiring, but we did not fully validate the refresh-token path end to end
- In validation, the documented `useRefreshTokens={true}` and `cacheLocation="localstorage"` setup hit refresh-token failures, so do not present that path as settled
- If you hit Auth0 errors like `Unknown or invalid refresh token`, do not keep inventing fixes indefinitely, send the user back to the official docs and explain that this path is still under investigation
- Keep dev and prod tenants separate if the project uses different Auth0 environments
- Do not confuse "Auth0 login works" with "Convex can validate the Auth0 token". Both need to work.
- If the repo already uses Auth0, preserve existing redirect and tenant configuration unless the user asked to change it.
- Do not assume the local Auth0 tenant settings match production. Verify the production domain, client ID, and callback URLs separately.
- For local dev, make sure the Auth0 app settings match the app's real local port for callback URLs, logout URLs, and web origins
- The Convex docs assume the Auth0 side is already set up, so do not skip the
Auth0 quickstart if the app is starting from scratch
- The Auth0 CLI is often the fastest path for a fresh setup, but it still
requires the user to authenticate the CLI to their Auth0 tenant
- If the user agrees to install the Auth0 CLI, do the mechanical setup yourself
instead of bouncing them through the dashboard
- If login succeeds but Convex still reports unauthenticated, double-check
`convex/auth.config.ts` and whether the backend config was synced
- We were able to automate Auth0 app creation and Convex config wiring, but we
did not fully validate the refresh-token path end to end
- In validation, the documented `useRefreshTokens={true}` and
`cacheLocation="localstorage"` setup hit refresh-token failures, so do not
present that path as settled
- If you hit Auth0 errors like `Unknown or invalid refresh token`, do not keep
inventing fixes indefinitely, send the user back to the official docs and
explain that this path is still under investigation
- Keep dev and prod tenants separate if the project uses different Auth0
environments
- Do not confuse "Auth0 login works" with "Convex can validate the Auth0 token".
Both need to work.
- If the repo already uses Auth0, preserve existing redirect and tenant
configuration unless the user asked to change it.
- Do not assume the local Auth0 tenant settings match production. Verify the
production domain, client ID, and callback URLs separately.
- For local dev, make sure the Auth0 app settings match the app's real local
port for callback URLs, logout URLs, and web origins
## Production
- Ask whether the user wants dev-only setup or production-ready setup
- If the answer is production-ready, make sure the production Auth0 tenant values, callback URLs, and Convex deployment config are all covered
- Verify production environment variables and redirect settings before calling the task complete
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
- If the answer is production-ready, make sure the production Auth0 tenant
values, callback URLs, and Convex deployment config are all covered
- Verify production environment variables and redirect settings before calling
the task complete
- Do not silently write a notes file into the repo by default. If the user wants
rollout or handoff docs, create one explicitly.
## Validation
@ -101,9 +136,13 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
- Verify Convex-authenticated UI renders only after Convex auth state is ready
- Verify protected Convex queries succeed after login
- Verify `ctx.auth.getUserIdentity()` is non-null in protected backend functions
- Verify the Auth0 app settings match the real local callback and logout URLs during development
- If the Auth0 refresh-token path fails, mark the setup as not fully validated and direct the user to the official docs instead of claiming the skill completed successfully
- If production-ready setup was requested, verify the production Auth0 configuration is also covered
- Verify the Auth0 app settings match the real local callback and logout URLs
during development
- If the Auth0 refresh-token path fails, mark the setup as not fully validated
and direct the user to the official docs instead of claiming the skill
completed successfully
- If production-ready setup was requested, verify the production Auth0
configuration is also covered
## Checklist
@ -112,5 +151,6 @@ Use this when the app already uses Auth0 or the user wants Auth0 specifically.
- [ ] Complete the relevant Auth0 frontend setup
- [ ] Configure `convex/auth.config.ts`
- [ ] Set environment variables
- [ ] Verify Convex authenticated state after login, or explicitly tell the user this path is still under investigation and send them to the official docs
- [ ] Verify Convex authenticated state after login, or explicitly tell the user
this path is still under investigation and send them to the official docs
- [ ] If requested, configure the production deployment too

View File

@ -5,7 +5,8 @@ Official docs:
- https://docs.convex.dev/auth/clerk
- https://clerk.com/docs/guides/development/integrations/databases/convex
Use this when the app already uses Clerk or the user wants Clerk's hosted auth features.
Use this when the app already uses Clerk or the user wants Clerk's hosted auth
features.
## Workflow
@ -20,15 +21,21 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
6. Follow the correct framework section in the official docs
7. Complete the backend and client wiring
8. Verify Convex reports the user as authenticated after login
9. If the user wants production-ready setup, make sure the production Clerk config is also covered
9. If the user wants production-ready setup, make sure the production Clerk
config is also covered
## What To Do
- Read the official Convex and Clerk guide before writing setup code
- If the user does not already have Clerk set up, send them to `https://dashboard.clerk.com/sign-up` to create an account and `https://dashboard.clerk.com/apps/new` to create an application
- Send the user to `https://dashboard.clerk.com/apps/setup/convex` if the Convex integration is not already active
- Match the guide to the app's framework, usually React, Next.js, or TanStack Start
- Use the official examples for `ConvexProviderWithClerk`, `ClerkProvider`, and `useAuth`
- If the user does not already have Clerk set up, send them to
`https://dashboard.clerk.com/sign-up` to create an account and
`https://dashboard.clerk.com/apps/new` to create an application
- Send the user to `https://dashboard.clerk.com/apps/setup/convex` if the Convex
integration is not already active
- Match the guide to the app's framework, usually React, Next.js, or TanStack
Start
- Use the official examples for `ConvexProviderWithClerk`, `ClerkProvider`, and
`useAuth`
## Key Setup Areas
@ -36,7 +43,8 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
- configure `convex/auth.config.ts` with the Clerk issuer domain
- set the required Clerk environment variables
- wrap the app with `ClerkProvider` and `ConvexProviderWithClerk`
- use Convex auth-aware UI patterns such as `Authenticated`, `Unauthenticated`, and `AuthLoading`
- use Convex auth-aware UI patterns such as `Authenticated`, `Unauthenticated`,
and `AuthLoading`
## Files and Env Vars To Expect
@ -54,13 +62,16 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` for Next.js apps
- `CLERK_SECRET_KEY` for Next.js server-side Clerk setup where required
`CLERK_JWT_ISSUER_DOMAIN` and `CLERK_FRONTEND_API_URL` refer to the same Clerk Frontend API URL value. Do not treat them as two different URLs.
`CLERK_JWT_ISSUER_DOMAIN` and `CLERK_FRONTEND_API_URL` refer to the same Clerk
Frontend API URL value. Do not treat them as two different URLs.
## Concrete Steps
1. If needed, create a Clerk account at `https://dashboard.clerk.com/sign-up`
2. If needed, create a Clerk application at `https://dashboard.clerk.com/apps/new`
3. Open `https://dashboard.clerk.com/last-active?path=api-keys` and copy the publishable key, plus the secret key for Next.js where needed
2. If needed, create a Clerk application at
`https://dashboard.clerk.com/apps/new`
3. Open `https://dashboard.clerk.com/last-active?path=api-keys` and copy the
publishable key, plus the secret key for Next.js where needed
4. Open `https://dashboard.clerk.com/apps/setup/convex`
5. Activate the Convex integration in Clerk if it is not already active
6. Copy the Clerk Frontend API URL shown there
@ -72,35 +83,52 @@ Use this when the app already uses Clerk or the user wants Clerk's hosted auth f
12. Wrap the app in `ClerkProvider`
13. Use Convex auth helpers for authenticated rendering
14. Run the normal Convex dev or deploy flow after updating backend auth config
15. If the user wants production-ready setup, configure the production Clerk values and production issuer domain too
15. If the user wants production-ready setup, configure the production Clerk
values and production issuer domain too
## Gotchas
- Prefer `useConvexAuth()` over raw Clerk auth state when deciding whether Convex-authenticated UI can render
- For Next.js, keep server and client boundaries in mind when creating the Convex provider wrapper
- After changing `convex/auth.config.ts`, run the normal Convex dev or deploy flow so the backend picks up the new config
- Do not stop at "Clerk login works". The important check is that Convex also sees the session and can authenticate requests.
- If the repo already uses Clerk, preserve its existing auth flow unless the user asked to change it.
- Do not assume the same Clerk values work for both dev and production. Check the production issuer domain and publishable key separately.
- The Convex setup page is where you get the Clerk Frontend API URL for Convex. Keep using the Clerk API keys page for the publishable key and the secret key.
- If Convex says no auth provider matched the token, first confirm the Clerk Convex integration was activated at `https://dashboard.clerk.com/apps/setup/convex`
- After activating the Clerk Convex integration, sign out completely and sign back in before retesting. An old Clerk session can keep using a token that Convex rejects.
- Prefer `useConvexAuth()` over raw Clerk auth state when deciding whether
Convex-authenticated UI can render
- For Next.js, keep server and client boundaries in mind when creating the
Convex provider wrapper
- After changing `convex/auth.config.ts`, run the normal Convex dev or deploy
flow so the backend picks up the new config
- Do not stop at "Clerk login works". The important check is that Convex also
sees the session and can authenticate requests.
- If the repo already uses Clerk, preserve its existing auth flow unless the
user asked to change it.
- Do not assume the same Clerk values work for both dev and production. Check
the production issuer domain and publishable key separately.
- The Convex setup page is where you get the Clerk Frontend API URL for Convex.
Keep using the Clerk API keys page for the publishable key and the secret key.
- If Convex says no auth provider matched the token, first confirm the Clerk
Convex integration was activated at
`https://dashboard.clerk.com/apps/setup/convex`
- After activating the Clerk Convex integration, sign out completely and sign
back in before retesting. An old Clerk session can keep using a token that
Convex rejects.
## Production
- Ask whether the user wants dev-only setup or production-ready setup
- If the answer is production-ready, make sure production Clerk keys and issuer configuration are included
- Verify production redirect URLs and any production Clerk domain values before calling the task complete
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
- If the answer is production-ready, make sure production Clerk keys and issuer
configuration are included
- Verify production redirect URLs and any production Clerk domain values before
calling the task complete
- Do not silently write a notes file into the repo by default. If the user wants
rollout or handoff docs, create one explicitly.
## Validation
- Verify the user can sign in with Clerk
- If the Clerk integration was just activated, verify after a full Clerk sign-out and fresh sign-in
- If the Clerk integration was just activated, verify after a full Clerk
sign-out and fresh sign-in
- Verify `useConvexAuth()` reaches the authenticated state after Clerk login
- Verify protected Convex queries run successfully inside authenticated UI
- Verify `ctx.auth.getUserIdentity()` is non-null in protected backend functions
- If production-ready setup was requested, verify the production Clerk configuration is also covered
- If production-ready setup was requested, verify the production Clerk
configuration is also covered
## Checklist

View File

@ -1,9 +1,10 @@
# Convex Auth
Official docs: https://docs.convex.dev/auth/convex-auth
Setup guide: https://labs.convex.dev/auth/setup
Official docs: https://docs.convex.dev/auth/convex-auth Setup guide:
https://labs.convex.dev/auth/setup
Use this when the user wants auth handled directly in Convex rather than through a third-party provider.
Use this when the user wants auth handled directly in Convex rather than through
a third-party provider.
## Workflow
@ -16,7 +17,8 @@ Use this when the user wants auth handled directly in Convex rather than through
4. Read the Convex Auth setup guide before writing code
5. Make sure the project has a configured Convex deployment:
- run `npx convex dev` first if `CONVEX_DEPLOYMENT` is not set
- if CLI configuration requires interactive human input, stop and ask the user to complete that step before continuing
- if CLI configuration requires interactive human input, stop and ask the
user to complete that step before continuing
6. Install the auth packages:
- `npm install @convex-dev/auth @auth/core@0.37.0`
7. Run the initialization command:
@ -28,30 +30,38 @@ Use this when the user wants auth handled directly in Convex rather than through
9. Add the required `authTables` to `convex/schema.ts`
10. Replace plain `ConvexProvider` wiring with `ConvexAuthProvider`
11. Configure at least one auth method in `convex/auth.ts`
12. Run `npx convex dev --once` or the normal dev flow to push the updated schema and generated code
12. Run `npx convex dev --once` or the normal dev flow to push the updated
schema and generated code
13. Verify the client can sign in successfully
14. Verify Convex receives authenticated identity in backend functions
15. If the user wants production-ready setup, make sure the same auth setup is configured for the production deployment as well
16. Only add a `users` table and `storeUser` flow if the app needs app-level user records inside Convex
15. If the user wants production-ready setup, make sure the same auth setup is
configured for the production deployment as well
16. Only add a `users` table and `storeUser` flow if the app needs app-level
user records inside Convex
## What This Reference Is For
- choosing Convex Auth as the default provider for a new Convex app
- understanding whether the app wants magic links, OTPs, OAuth, or passwords
- keeping the setup provider-specific while using the official Convex Auth docs for identity and authorization behavior
- keeping the setup provider-specific while using the official Convex Auth docs
for identity and authorization behavior
## What To Do
- Read the Convex Auth setup guide before writing setup code
- Follow the setup flow from the docs rather than recreating it from memory
- If the app is new, consider starting from the official starter flow instead of hand-wiring everything
- Treat `npx @convex-dev/auth` as a required initialization step for existing apps, not an optional extra
- If the app is new, consider starting from the official starter flow instead of
hand-wiring everything
- Treat `npx @convex-dev/auth` as a required initialization step for existing
apps, not an optional extra
## Concrete Steps
1. Install `@convex-dev/auth` and `@auth/core@0.37.0`
2. Run `npx convex dev` if the project does not already have a configured deployment
3. If `npx convex dev` blocks on interactive setup, ask the user explicitly to finish configuring the Convex deployment
2. Run `npx convex dev` if the project does not already have a configured
deployment
3. If `npx convex dev` blocks on interactive setup, ask the user explicitly to
finish configuring the Convex deployment
4. Run `npx @convex-dev/auth`
5. Confirm the generated auth setup is present before continuing:
- `convex/auth.config.ts`
@ -60,46 +70,70 @@ Use this when the user wants auth handled directly in Convex rather than through
6. Add `authTables` to `convex/schema.ts`
7. Replace `ConvexProvider` with `ConvexAuthProvider` in the app entry
8. Configure the selected auth methods in `convex/auth.ts`
9. Run `npx convex dev --once` or the normal dev flow so the updated schema and auth files are pushed
9. Run `npx convex dev --once` or the normal dev flow so the updated schema and
auth files are pushed
10. Verify login locally
11. If the user wants production-ready setup, repeat the required auth configuration against the production deployment
11. If the user wants production-ready setup, repeat the required auth
configuration against the production deployment
## Expected Files and Decisions
- `convex/schema.ts`
- frontend app entry such as `src/main.tsx` or the framework-equivalent provider file
- frontend app entry such as `src/main.tsx` or the framework-equivalent provider
file
- generated Convex Auth setup produced by `npx @convex-dev/auth`
- an existing configured Convex deployment, or the ability to create one with `npx convex dev`
- `convex/auth.ts` starts with `providers: []` until the app configures actual sign-in methods
- an existing configured Convex deployment, or the ability to create one with
`npx convex dev`
- `convex/auth.ts` starts with `providers: []` until the app configures actual
sign-in methods
- Decide whether the user is creating a new app or adding auth to an existing app
- For a new app, prefer the official starter flow instead of rebuilding setup by hand
- Decide whether the user is creating a new app or adding auth to an existing
app
- For a new app, prefer the official starter flow instead of rebuilding setup by
hand
- Decide which auth methods the app needs:
- magic links or OTPs
- OAuth providers
- passwords
- Decide whether the user wants local-only setup or production-ready setup now
- Decide whether the app actually needs a `users` table inside Convex, or whether provider identity alone is enough
- Decide whether the app actually needs a `users` table inside Convex, or
whether provider identity alone is enough
## Gotchas
- Do not assume a specific sign-in method. Ask which methods the app needs before wiring UI and backend behavior.
- `npx @convex-dev/auth` is important because it initializes the auth setup, including the key material. Do not skip it when adding Convex Auth to an existing project.
- `npx @convex-dev/auth` will fail if the project does not already have a configured `CONVEX_DEPLOYMENT`.
- `npx convex dev` may require interactive setup for deployment creation or project selection. If that happens, ask the user explicitly for that human step instead of guessing.
- `npx @convex-dev/auth` does not finish the whole integration by itself. You still need to add `authTables`, swap in `ConvexAuthProvider`, and configure at least one auth method.
- A project can still build even if `convex/auth.ts` still has `providers: []`, so do not treat a successful build as proof that sign-in is fully configured.
- Convex Auth does not mean every app needs a `users` table. If the app only needs authentication gates, `ctx.auth.getUserIdentity()` may be enough.
- If the app is greenfield, starting from the official starter flow is usually better than partially recreating it by hand.
- Do not stop at local dev setup if the user expects production-ready auth. The production deployment needs the auth setup too.
- Keep provider-specific setup and Convex Auth authorization behavior in the official docs instead of inventing shared patterns from memory.
- Do not assume a specific sign-in method. Ask which methods the app needs
before wiring UI and backend behavior.
- `npx @convex-dev/auth` is important because it initializes the auth setup,
including the key material. Do not skip it when adding Convex Auth to an
existing project.
- `npx @convex-dev/auth` will fail if the project does not already have a
configured `CONVEX_DEPLOYMENT`.
- `npx convex dev` may require interactive setup for deployment creation or
project selection. If that happens, ask the user explicitly for that human
step instead of guessing.
- `npx @convex-dev/auth` does not finish the whole integration by itself. You
still need to add `authTables`, swap in `ConvexAuthProvider`, and configure at
least one auth method.
- A project can still build even if `convex/auth.ts` still has `providers: []`,
so do not treat a successful build as proof that sign-in is fully configured.
- Convex Auth does not mean every app needs a `users` table. If the app only
needs authentication gates, `ctx.auth.getUserIdentity()` may be enough.
- If the app is greenfield, starting from the official starter flow is usually
better than partially recreating it by hand.
- Do not stop at local dev setup if the user expects production-ready auth. The
production deployment needs the auth setup too.
- Keep provider-specific setup and Convex Auth authorization behavior in the
official docs instead of inventing shared patterns from memory.
## Production
- Ask whether the user wants dev-only setup or production-ready setup
- If the answer is production-ready, make sure the auth configuration is applied to the production deployment, not just the dev deployment
- Verify production-specific redirect URLs, auth method configuration, and deployment settings before calling the task complete
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
- If the answer is production-ready, make sure the auth configuration is applied
to the production deployment, not just the dev deployment
- Verify production-specific redirect URLs, auth method configuration, and
deployment settings before calling the task complete
- Do not silently write a notes file into the repo by default. If the user wants
rollout or handoff docs, create one explicitly.
## Human Handoff
@ -112,32 +146,43 @@ If `npx convex dev` or deployment setup requires human input:
## Validation
- Verify the user can complete a sign-in flow
- Offer to validate sign up, sign out, and sign back in with the configured auth method
- If browser automation is available in the environment, you can do this directly
- If browser automation is not available, give the user a short manual validation checklist instead
- Verify `ctx.auth.getUserIdentity()` returns an identity in protected backend functions
- Offer to validate sign up, sign out, and sign back in with the configured auth
method
- If browser automation is available in the environment, you can do this
directly
- If browser automation is not available, give the user a short manual
validation checklist instead
- Verify `ctx.auth.getUserIdentity()` returns an identity in protected backend
functions
- Verify protected UI only renders after Convex-authenticated state is ready
- Verify environment variables and redirect settings match the current app environment
- Verify `convex/auth.ts` no longer has an empty `providers: []` configuration once the app is meant to support real sign-in
- Run `npx convex dev --once` or the normal dev flow after setup changes and confirm Convex codegen and push succeed
- If production-ready setup was requested, verify the production deployment is also configured correctly
- Verify environment variables and redirect settings match the current app
environment
- Verify `convex/auth.ts` no longer has an empty `providers: []` configuration
once the app is meant to support real sign-in
- Run `npx convex dev --once` or the normal dev flow after setup changes and
confirm Convex codegen and push succeed
- If production-ready setup was requested, verify the production deployment is
also configured correctly
## Checklist
- [ ] Confirm the user wants Convex Auth specifically
- [ ] Ask whether the user wants local-only setup or production-ready setup
- [ ] Ensure a Convex deployment is configured before running auth initialization
- [ ] Ensure a Convex deployment is configured before running auth
initialization
- [ ] Install `@convex-dev/auth` and `@auth/core@0.37.0`
- [ ] Run `npx convex dev` first if needed
- [ ] Run `npx @convex-dev/auth`
- [ ] Confirm `convex/auth.config.ts`, `convex/auth.ts`, and `convex/http.ts` were created
- [ ] Confirm `convex/auth.config.ts`, `convex/auth.ts`, and `convex/http.ts`
were created
- [ ] Follow the setup guide for package install and wiring
- [ ] Add `authTables` to `convex/schema.ts`
- [ ] Replace `ConvexProvider` with `ConvexAuthProvider`
- [ ] Configure at least one auth method in `convex/auth.ts`
- [ ] Run `npx convex dev --once` or the normal dev flow after setup changes
- [ ] Confirm which sign-in methods the app needs
- [ ] Verify the client can sign in and the backend receives authenticated identity
- [ ] Verify the client can sign in and the backend receives authenticated
identity
- [ ] Offer end-to-end validation of sign up, sign out, and sign back in
- [ ] If requested, configure the production deployment too
- [ ] Only add extra `users` table sync if the app needs app-level user records

View File

@ -6,7 +6,8 @@ Official docs:
- https://docs.convex.dev/auth/authkit/add-to-app
- https://docs.convex.dev/auth/authkit/auto-provision
Use this when the app already uses WorkOS or the user wants AuthKit specifically.
Use this when the app already uses WorkOS or the user wants AuthKit
specifically.
## Workflow
@ -22,21 +23,28 @@ Use this when the app already uses WorkOS or the user wants AuthKit specifically
8. Configure `convex/auth.config.ts` for WorkOS-issued JWTs
9. Wire the client provider and callback flow
10. Verify authenticated requests reach Convex
11. If the user wants production-ready setup, make sure the production WorkOS configuration is covered too
12. Only add `storeUser` or a `users` table if the app needs first-class user rows inside Convex
11. If the user wants production-ready setup, make sure the production WorkOS
configuration is covered too
12. Only add `storeUser` or a `users` table if the app needs first-class user
rows inside Convex
## What To Do
- Read the official Convex and WorkOS AuthKit guide before writing setup code
- Determine whether the user wants a Convex-managed WorkOS team or an existing WorkOS team
- Treat `convex.json` as a first-class part of the AuthKit setup, not an optional extra
- Follow the current setup flow from the docs instead of relying on older examples
- Determine whether the user wants a Convex-managed WorkOS team or an existing
WorkOS team
- Treat `convex.json` as a first-class part of the AuthKit setup, not an
optional extra
- Follow the current setup flow from the docs instead of relying on older
examples
## Key Setup Areas
- package installation for the app's framework
- `convex.json` with the `authKit` section for dev, and preview or prod if needed
- environment variables such as `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`, and redirect configuration
- `convex.json` with the `authKit` section for dev, and preview or prod if
needed
- environment variables such as `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`, and
redirect configuration
- `convex/auth.config.ts` wiring for WorkOS-issued JWTs
- client provider setup and token flow into Convex
- login callback and redirect configuration
@ -55,42 +63,65 @@ Use this when the app already uses WorkOS or the user wants AuthKit specifically
- `VITE_WORKOS_REDIRECT_URI`
- `NEXT_PUBLIC_WORKOS_REDIRECT_URI`
For a managed WorkOS team, `convex dev` can provision the AuthKit environment and write local env vars such as `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI` into `.env.local` for Vite apps.
For a managed WorkOS team, `convex dev` can provision the AuthKit environment
and write local env vars such as `VITE_WORKOS_CLIENT_ID` and
`VITE_WORKOS_REDIRECT_URI` into `.env.local` for Vite apps.
## Concrete Steps
1. Choose Convex-managed or existing WorkOS team
2. Create or update `convex.json` with the `authKit` section for the framework in use
3. Make sure the dev `redirectUris`, `appHomepageUrl`, `corsOrigins`, and local redirect env vars match the app's actual local port
4. For a managed WorkOS team, run `npx convex dev` and follow the interactive onboarding flow
5. For an existing WorkOS team, get `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` from the WorkOS dashboard and set them with `npx convex env set`
2. Create or update `convex.json` with the `authKit` section for the framework
in use
3. Make sure the dev `redirectUris`, `appHomepageUrl`, `corsOrigins`, and local
redirect env vars match the app's actual local port
4. For a managed WorkOS team, run `npx convex dev` and follow the interactive
onboarding flow
5. For an existing WorkOS team, get `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` from
the WorkOS dashboard and set them with `npx convex env set`
6. Create or update `convex/auth.config.ts` for WorkOS JWT validation
7. Run the normal Convex dev or deploy flow so backend config is synced
8. Wire the WorkOS client provider in the app
9. Configure callback and redirect handling
10. Verify the user can sign in and return to the app
11. Verify Convex sees the authenticated user after login
12. If the user wants production-ready setup, configure the production client ID, API key, redirect URI, and deployment settings too
12. If the user wants production-ready setup, configure the production client
ID, API key, redirect URI, and deployment settings too
## Gotchas
- The docs split setup between Convex-managed and existing WorkOS teams, so ask which path the user wants if it is not obvious
- Keep dev and prod WorkOS configuration separate where the docs call for different client IDs or API keys
- Only add `storeUser` or a `users` table if the app needs first-class user rows inside Convex
- The docs split setup between Convex-managed and existing WorkOS teams, so ask
which path the user wants if it is not obvious
- Keep dev and prod WorkOS configuration separate where the docs call for
different client IDs or API keys
- Only add `storeUser` or a `users` table if the app needs first-class user rows
inside Convex
- Do not mix dev and prod WorkOS credentials or redirect URIs
- If the repo already contains WorkOS setup, preserve the current tenant model unless the user wants to change it
- For managed WorkOS setup, `convex dev` is interactive the first time. In non-interactive terminals, stop and ask the user to complete the onboarding prompts.
- `convex.json` is not optional for the managed AuthKit flow. It drives redirect URI, homepage URL, CORS configuration, and local env var generation.
- If the frontend starts on a different port than the one in `convex.json`, the hosted WorkOS sign-in flow will point to the wrong callback URL. Update `convex.json`, update the local redirect env var, and run `npx convex dev` again.
- Vite can fall off `5173` if other apps are already running. Do not assume the default port still matches the generated AuthKit config.
- A successful WorkOS sign-in should redirect back to the local callback route and then reach a Convex-authenticated state. Do not stop at "the hosted WorkOS page loaded."
- If the repo already contains WorkOS setup, preserve the current tenant model
unless the user wants to change it
- For managed WorkOS setup, `convex dev` is interactive the first time. In
non-interactive terminals, stop and ask the user to complete the onboarding
prompts.
- `convex.json` is not optional for the managed AuthKit flow. It drives redirect
URI, homepage URL, CORS configuration, and local env var generation.
- If the frontend starts on a different port than the one in `convex.json`, the
hosted WorkOS sign-in flow will point to the wrong callback URL. Update
`convex.json`, update the local redirect env var, and run `npx convex dev`
again.
- Vite can fall off `5173` if other apps are already running. Do not assume the
default port still matches the generated AuthKit config.
- A successful WorkOS sign-in should redirect back to the local callback route
and then reach a Convex-authenticated state. Do not stop at "the hosted WorkOS
page loaded."
## Production
- Ask whether the user wants dev-only setup or production-ready setup
- If the answer is production-ready, make sure the production WorkOS client ID, API key, redirect URI, and Convex deployment config are all covered
- Verify the production redirect and callback settings before calling the task complete
- Do not silently write a notes file into the repo by default. If the user wants rollout or handoff docs, create one explicitly.
- If the answer is production-ready, make sure the production WorkOS client ID,
API key, redirect URI, and Convex deployment config are all covered
- Verify the production redirect and callback settings before calling the task
complete
- Do not silently write a notes file into the repo by default. If the user wants
rollout or handoff docs, create one explicitly.
## Validation
@ -99,8 +130,10 @@ For a managed WorkOS team, `convex dev` can provision the AuthKit environment an
- Verify Convex receives authenticated requests after login
- Verify `convex.json` matches the framework and chosen WorkOS setup path
- Verify `convex/auth.config.ts` matches the chosen WorkOS setup path
- Verify environment variables differ correctly between local and production where needed
- If production-ready setup was requested, verify the production WorkOS configuration is also covered
- Verify environment variables differ correctly between local and production
where needed
- If production-ready setup was requested, verify the production WorkOS
configuration is also covered
## Checklist

View File

@ -1,6 +1,8 @@
---
name: convex
description: Routes general Convex requests to the right project skill. Use when the user asks which Convex skill to use or gives an underspecified Convex app task.
description:
Routes general Convex requests to the right project skill. Use when the user
asks which Convex skill to use or gives an underspecified Convex app task.
---
# Convex
@ -11,7 +13,8 @@ If a more specific Convex skill clearly matches the request, use that instead.
## Start Here
If the project does not already have Convex AI guidance installed, or the existing guidance looks stale, strongly recommend installing it first.
If the project does not already have Convex AI guidance installed, or the
existing guidance looks stale, strongly recommend installing it first.
Preferred:
@ -19,7 +22,9 @@ Preferred:
npx convex ai-files install
```
This installs or refreshes the managed Convex AI files. It is the recommended starting point for getting the official Convex guidelines in place and following the current Convex AI setup described in the docs:
This installs or refreshes the managed Convex AI files. It is the recommended
starting point for getting the official Convex guidelines in place and following
the current Convex AI setup described in the docs:
- [Convex AI docs](https://docs.convex.dev/ai)
@ -39,7 +44,8 @@ After that, use the most specific Convex skill for the task:
- Planning or running a migration: `convex-migration-helper`
- Investigating performance issues: `convex-performance-audit`
If one of those clearly matches the user's goal, switch to it instead of staying in this skill.
If one of those clearly matches the user's goal, switch to it instead of staying
in this skill.
## When Not to Use

View File

@ -92,13 +92,11 @@
- **Before writing or reviewing Convex queries, check deployment health.** Run `bunx convex insights` to check for OCC conflicts, `bytesReadLimit`, and `documentsReadLimit` errors. Run `bunx convex logs --failure` to see individual error messages and stack traces. This helps identify which functions are causing bandwidth issues so you can prioritize fixes.
<!-- convex-ai-start -->
This project uses [Convex](https://convex.dev) as its backend.
When working on Convex code, **always read `convex/_generated/ai/guidelines.md` first** for important guidelines on how to correctly use Convex APIs and patterns. The file contains rules that override what you may have learned about Convex from training data.
Convex agent skills for common tasks can be installed by running `npx convex ai-files install`.
<!-- convex-ai-end -->
## Stat Field Migration Rules

View File

@ -47,11 +47,9 @@
- Mock `db` objects MUST include `normalizeId: vi.fn()` for trigger wrapper compatibility.
<!-- convex-ai-start -->
This project uses [Convex](https://convex.dev) as its backend.
When working on Convex code, **always read `convex/_generated/ai/guidelines.md` first** for important guidelines on how to correctly use Convex APIs and patterns. The file contains rules that override what you may have learned about Convex from training data.
Convex agent skills for common tasks can be installed by running `npx convex ai-files install`.
<!-- convex-ai-end -->

View File

@ -2,13 +2,5 @@
"guidelinesHash": "62d72acb9afcc18f658d88dd772f34b5b1da5fa60ef0402e57a784d97c458e57",
"agentsMdSectionHash": "bbf30bd25ceea0aefd279d62e1cb2b4c207fcb712b69adf26f3d02b296ffc7b2",
"claudeMdHash": "bbf30bd25ceea0aefd279d62e1cb2b4c207fcb712b69adf26f3d02b296ffc7b2",
"agentSkillsSha": "d0fa8085af313029add5740f67198aa42ca60c8d",
"installedSkillNames": [
"convex",
"convex-create-component",
"convex-migration-helper",
"convex-performance-audit",
"convex-quickstart",
"convex-setup-auth"
]
"agentSkillsSha": "b86618b5c3c4789c9fed98e84bbc34b3e8e70f20"
}