# Convex > Actions can call third party services to do things such as processing a payment with [Stripe](https://stripe.com). They can be run in Convex's JavaScript environment or in Node.js. They can interact w --- # Source: https://docs.convex.dev/tutorial/actions.md # Source: https://docs.convex.dev/functions/actions.md # Actions Actions can call third party services to do things such as processing a payment with [Stripe](https://stripe.com). They can be run in Convex's JavaScript environment or in Node.js. They can interact with the database indirectly by calling [queries](/functions/query-functions.md) and [mutations](/functions/mutation-functions.md). **Example:** [GIPHY Action](https://github.com/get-convex/convex-demos/tree/main/giphy-action) ## Action names[​](#action-names "Direct link to Action names") Actions follow the same naming rules as queries, see [Query names](/functions/query-functions.md#query-names). ## The `action` constructor[​](#the-action-constructor "Direct link to the-action-constructor") To declare an action in Convex you use the action constructor function. Pass it an object with a `handler` function, which performs the action: convex/myFunctions.ts TS ``` import { action } from "./_generated/server"; export const doSomething = action({ args: {}, handler: () => { // implementation goes here // optionally return a value return "success"; }, }); ``` Unlike a query, an action can but does not have to return a value. ### Action arguments and responses[​](#action-arguments-and-responses "Direct link to Action arguments and responses") Action arguments and responses follow the same rules as [mutations](/functions/mutation-functions.md#mutation-arguments): convex/myFunctions.ts TS ``` import { action } from "./_generated/server"; import { v } from "convex/values"; export const doSomething = action({ args: { a: v.number(), b: v.number() }, handler: (_, args) => { // do something with `args.a` and `args.b` // optionally return a value return "success"; }, }); ``` The first argument to the handler function is reserved for the action context. ### Action context[​](#action-context "Direct link to Action context") The `action` constructor enables interacting with the database, and other Convex features by passing an [ActionCtx](/api/interfaces/server.GenericActionCtx.md) object to the handler function as the first argument: convex/myFunctions.ts TS ``` import { action } from "./_generated/server"; import { v } from "convex/values"; export const doSomething = action({ args: { a: v.number(), b: v.number() }, handler: (ctx, args) => { // do something with `ctx` }, }); ``` Which part of that action context is used depends on what your action needs to do: * To read data from the database use the `runQuery` field, and call a query that performs the read: convex/myFunctions.ts TS ``` import { action, internalQuery } from "./_generated/server"; import { internal } from "./_generated/api"; import { v } from "convex/values"; export const doSomething = action({ args: { a: v.number() }, handler: async (ctx, args) => { const data = await ctx.runQuery(internal.myFunctions.readData, { a: args.a, }); // do something with `data` }, }); export const readData = internalQuery({ args: { a: v.number() }, handler: async (ctx, args) => { // read from `ctx.db` here }, }); ``` Here `readData` is an [internal query](/functions/internal-functions.md) because we don't want to expose it to the client directly. Actions, mutations and queries can be defined in the same file. * To write data to the database use the `runMutation` field, and call a mutation that performs the write: convex/myFunctions.ts TS ``` import { v } from "convex/values"; import { action } from "./_generated/server"; import { internal } from "./_generated/api"; export const doSomething = action({ args: { a: v.number() }, handler: async (ctx, args) => { const data = await ctx.runMutation(internal.myMutations.writeData, { a: args.a, }); // do something else, optionally use `data` }, }); ``` Use an [internal mutation](/functions/internal-functions.md) when you want to prevent users from calling the mutation directly. As with queries, it's often convenient to define actions and mutations in the same file. * To generate upload URLs for storing files use the `storage` field. Read on about [File Storage](/file-storage.md). * To check user authentication use the `auth` field. Auth is propagated automatically when calling queries and mutations from the action. Read on about [Authentication](/auth.md). * To schedule functions to run in the future, use the `scheduler` field. Read on about [Scheduled Functions](/scheduling/scheduled-functions.md). * To search a vector index, use the `vectorSearch` field. Read on about [Vector Search](/search/vector-search.md). ### Dealing with circular type inference[​](#dealing-with-circular-type-inference "Direct link to Dealing with circular type inference") Working around the TypeScript error: some action `implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.` When the return value of an action depends on the result of calling `ctx.runQuery` or `ctx.runMutation`, TypeScript will complain that it cannot infer the return type of the action. This is a minimal example of the issue: convex/myFunctions.ts ``` // TypeScript reports an error on `myAction` export const myAction = action({ args: {}, handler: async (ctx) => { return await ctx.runQuery(api.myFunctions.getSomething); }, }); export const getSomething = query({ args: {}, handler: () => { return null; }, }); ``` To work around this, there are two options: 1. Type the return value of the handler function explicitly: convex/myFunctions.ts ``` export const myAction = action({ args: {}, handler: async (ctx): Promise => { const result = await ctx.runQuery(api.myFunctions.getSomething); return result; }, }); ``` 2. Type the the result of the `ctx.runQuery` or `ctx.runMutation` call explicitly: convex/myFunctions.ts ``` export const myAction = action({ args: {}, handler: async (ctx) => { const result: null = await ctx.runQuery(api.myFunctions.getSomething); return result; }, }); ``` TypeScript will check that the type annotation matches what the called query or mutation returns, so you don't lose any type safety. In this trivial example the return type of the query was `null`. See the [TypeScript](/understanding/best-practices/typescript.md#type-annotating-server-side-helpers) page for other types which might be helpful when annotating the result. ## Choosing the runtime ("use node")[​](#choosing-the-runtime-use-node "Direct link to Choosing the runtime (\"use node\")") Actions can run in Convex's custom JavaScript environment or in Node.js. By default, actions run in Convex's environment. This environment supports `fetch`, so actions that simply want to call a third-party API using `fetch` can be run in this environment: convex/myFunctions.ts TS ``` import { action } from "./_generated/server"; export const doSomething = action({ args: {}, handler: async () => { const data = await fetch("https://api.thirdpartyservice.com"); // do something with data }, }); ``` Actions running in Convex's environment are faster compared to Node.js, since they don't require extra time to start up before running your action (cold starts). They can also be defined in the same file as other Convex functions. Like queries and mutations they can import NPM packages, but not all are supported. Actions needing unsupported NPM packages or Node.js APIs can be configured to run in Node.js by adding the `"use node"` directive at the top of the file. Note that other Convex functions cannot be defined in files with the `"use node";` directive. convex/myAction.ts TS ``` "use node"; import { action } from "./_generated/server"; import SomeNpmPackage from "some-npm-package"; export const doSomething = action({ args: {}, handler: () => { // do something with SomeNpmPackage }, }); ``` Learn more about the two [Convex Runtimes](/functions/runtimes.md). ## Splitting up action code via helpers[​](#splitting-up-action-code-via-helpers "Direct link to Splitting up action code via helpers") Just like with [queries](/functions/query-functions.md#splitting-up-query-code-via-helpers) and [mutations](/functions/mutation-functions.md#splitting-up-mutation-code-via-helpers) you can define and call helper TypeScript functions to split up the code in your actions or reuse logic across multiple Convex functions. But note that the [ActionCtx](/api/interfaces/server.GenericActionCtx.md) only has the `auth` field in common with [QueryCtx](/generated-api/server.md#queryctx) and [MutationCtx](/generated-api/server.md#mutationctx). ## Calling actions from clients[​](#calling-actions-from-clients "Direct link to Calling actions from clients") To call an action from [React](/client/react.md) use the [`useAction`](/api/modules/react.md#useaction) hook along with the generated [`api`](/generated-api/api.md) object. src/myApp.tsx TS ``` import { useAction } from "convex/react"; import { api } from "../convex/_generated/api"; export function MyApp() { const performMyAction = useAction(api.myFunctions.doSomething); const handleClick = () => { performMyAction({ a: 1 }); }; // pass `handleClick` to a button // ... } ``` Unlike [mutations](/functions/mutation-functions.md#calling-mutations-from-clients), actions from a single client are parallelized. Each action will be executed as soon as it reaches the server (even if other actions and mutations from the same client are running). If your app relies on actions running after other actions or mutations, make sure to only trigger the action after the relevant previous function completes. **Note:** In most cases calling an action directly from a client **is an anti-pattern**. Instead, have the client call a [mutation](/functions/mutation-functions.md) which captures the user intent by writing into the database and then [schedules](/scheduling/scheduled-functions.md) an action: convex/myFunctions.ts TS ``` import { v } from "convex/values"; import { internal } from "./_generated/api"; import { internalAction, mutation } from "./_generated/server"; export const mutationThatSchedulesAction = mutation({ args: { text: v.string() }, handler: async (ctx, { text }) => { const taskId = await ctx.db.insert("tasks", { text }); await ctx.scheduler.runAfter(0, internal.myFunctions.actionThatCallsAPI, { taskId, text, }); }, }); export const actionThatCallsAPI = internalAction({ args: { taskId: v.id("tasks"), text: v.string() }, handler: (_, args): void => { // do something with `taskId` and `text`, like call an API // then run another mutation to store the result }, }); ``` This way the mutation can enforce invariants, such as preventing the user from executing the same action twice. ## Limits[​](#limits "Direct link to Limits") Actions time out after 10 minutes. [Node.js](/functions/runtimes.md#nodejs-runtime) and [Convex runtime](/functions/runtimes.md#default-convex-runtime) have 512MB and 64MB memory limit respectively. Please [contact us](/production/contact.md) if you have a use case that requires configuring higher limits. Actions can do up to 1000 concurrent operations, such as executing queries, mutations or performing fetch requests. For information on other limits, see [here](/production/state/limits.md). ## Error handling[​](#error-handling "Direct link to Error handling") Unlike queries and mutations, actions may have side-effects and therefore can't be automatically retried by Convex when errors occur. For example, say your action calls Stripe to send a customer invoice. If the HTTP request fails, Convex has no way of knowing if the invoice was already sent. Like in normal backend code, it is the responsibility of the caller to handle errors raised by actions and retry the action call if appropriate. ## Dangling promises[​](#dangling-promises "Direct link to Dangling promises") Make sure to await all promises created within an action. Async tasks still running when the function returns might or might not complete. In addition, since the Node.js execution environment might be reused between action calls, dangling promises might result in errors in subsequent action invocations. ## Best practices[​](#best-practices "Direct link to Best practices") ### `await ctx.runAction` should only be used for crossing JS runtimes[​](#await-ctxrunaction-should-only-be-used-for-crossing-js-runtimes "Direct link to await-ctxrunaction-should-only-be-used-for-crossing-js-runtimes") **Why?** `await ctx.runAction` incurs to overhead of another Convex server function. It counts as an extra function call, it allocates its own system resources, and while you're awaiting this call the parent action call is frozen holding all it's resources. If you pile enough of these calls on top of each other, your app may slow down significantly. **Fix:** The reason this api exists is to let you run code in the [Node.js environment](/functions/runtimes.md). If you want to call an action from another action that's in the same runtime, which is the normal case, the best way to do this is to pull the code you want to call into a TypeScript [helper function](/understanding/best-practices/.md#use-helper-functions-to-write-shared-code) and call the helper instead. ### Avoid `await ctx.runMutation` / `await ctx.runQuery`[​](#avoid-await-ctxrunmutation--await-ctxrunquery "Direct link to avoid-await-ctxrunmutation--await-ctxrunquery") ``` // ❌ const foo = await ctx.runQuery(...) const bar = await ctx.runQuery(...) // ✅ const fooAndBar = await ctx.runQuery(...) ``` **Why?** Multiple runQuery / runMutations execute in separate transactions and aren’t guaranteed to be consistent with each other (e.g. foo and bar could read the same document and return two different results), while a single runQuery / runMutation will always be consistent. Additionally, you’re paying for multiple function calls when you don’t have to. **Fix:** Make a new internal query / mutation that does both things. Refactoring the code for the two functions into helpers will make it easy to create a new internal function that does both things while still keeping around the original functions. Potentially try and refactor your action code to “batch” all the database access. Caveats: Separate runQuery / runMutation calls are valid when intentionally trying to process more data than fits in a single transaction (e.g. running a migration, doing a live aggregate). ## Related Components[​](#related-components "Direct link to Related Components") [Convex Component](https://www.convex.dev/components/action-cache) ### [Action Cache](https://www.convex.dev/components/action-cache) [Cache expensive or frequently run actions. Allows configurable cache duration and forcing updates.](https://www.convex.dev/components/action-cache) [Convex Component](https://www.convex.dev/components/workpool) ### [Workpool](https://www.convex.dev/components/workpool) [Workpool give critical tasks priority by organizing async operations into separate, customizable queues. Supports retries and parallelism limits.](https://www.convex.dev/components/workpool) [Convex Component](https://www.convex.dev/components/workflow) ### [Workflow](https://www.convex.dev/components/workflow) [Similar to Actions, Workflows can call queries, mutations, and actions. However, they are durable functions that can suspend, survive server crashes, specify retries for action calls, and more.](https://www.convex.dev/components/workflow) --- # Source: https://docs.convex.dev/cli/agent-mode.md # Agent Mode When logged in on your own machine, agents like Cursor and Claude Code can run CLI commands like `npx convex env list` that use your logged-in credentials run commands against your personal dev environment as if you ran the commands yourself. This works well when you're collaborating with an agent; just like when the agent runs `git commit -am "Fix."`, the commit will use your local git credentials. But when cloud-based coding agents like Jules, Devin, Codex, or Cursor Cloud Agents run Convex CLI commands, they can't log in. And if you do log in for them, the agent will use your default dev deployment to develop, conflicting with your own changes! Instead, set `CONVEX_AGENT_MODE=anonymous` in this environment, causing the agent to use [anonymous development](/cli/local-deployments.md) to run a separate Convex backend on the VM where the agent is working. Convex Agent Mode is in beta Convex Agent Mode is currently a [beta feature](/production/state/.md#beta-features). If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community)! You can set this variable in .env.local or set it in the agent's environment. ``` CONVEX_AGENT_MODE=anonymous npx convex dev ``` In the future `CONVEX_AGENT_MODE` may support other behaviors like allowing agents to provision their own short-lived cloud deployments. --- # Source: https://docs.convex.dev/agents/agent-usage.md # Agent Definition and Usage Agents encapsulate models, prompting, tools, and other configuration. They can be defined as globals, or at runtime. They use threads to contain a series of messages used along the way, whether those messages are from a user, another Agent / LLM, or elsewhere. A thread can have multiple Agents responding, or be used by a single Agent. Agentic workflows are built up by combining contextual prompting (threads, messages, tool responses, RAG, etc.) and dynamic routing via LLM tool calls, structured LLM outputs, or a myriad of other techniques via custom code. ## Basic Agent definition[​](#basic-agent-definition "Direct link to Basic Agent definition") ``` import { components } from "./_generated/api"; import { Agent } from "@convex-dev/agent"; import { openai } from "@ai-sdk/openai"; const agent = new Agent(components.agent, { name: "Basic Agent", languageModel: openai.chat("gpt-4o-mini"), }); ``` See [below](#customizing-the-agent) for more configuration options. Everything except the name can be overridden at the call site when calling the LLM, and many features available on the agent can be used without an Agent, if this way of organizing the work is not needed for your use case. ## Dynamic Agent definition[​](#dynamic-agent-definition "Direct link to Dynamic Agent definition") You can define an Agent at runtime, which is useful if you want to create an Agent for a specific context. This allows the LLM to call tools without requiring the LLM to always pass through full context to each tool call. It also allows dynamically choosing a model or other options for the Agent. ``` import { Agent } from "@convex-dev/agent"; import { type LanguageModel } from "ai"; import type { ActionCtx } from "./_generated/server"; import type { Id } from "./_generated/dataModel"; import { components } from "./_generated/api"; function createAuthorAgent( ctx: ActionCtx, bookId: Id<"books">, model: LanguageModel, ) { return new Agent(components.agent, { name: "Author", languageModel: model, tools: { // See https://docs.convex.dev/agents/tools getChapter: getChapterTool(ctx, bookId), researchCharacter: researchCharacterTool(ctx, bookId), writeChapter: writeChapterTool(ctx, bookId), }, maxSteps: 10, // Alternative to stopWhen: stepCountIs(10) }); } ``` ## Generating text with an Agent[​](#generating-text-with-an-agent "Direct link to Generating text with an Agent") To generate a message, you provide a prompt (as a string or a list of messages) to be used as context to generate one or more messages via an LLM, using calls like `agent.streamText` or `agent.generateObject`. The arguments to `generateText` and others are the same as the AI SDK, except you don't have to provide a model. By default it will use the agent's language model. There are also extra arguments that are specific to the Agent component, such as the `promptMessageId` which we'll see below. [**See the full list of AI SDK arguments here**](https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text) The message history will be provided by default as context from the given [thread](/agents/threads.md). See [LLM Context](/agents/context.md) for details on how to configuring the context provided. Note: `authorizeThreadAccess` referenced below is a function you would write to authenticate and authorize the user to access the thread. You can see an example implementation in [threads.ts](https://github.com/get-convex/agent/blob/main/example/convex/threads.ts). See [chat/basic.ts](https://github.com/get-convex/agent/blob/main/example/convex/chat/basic.ts) or [chat/streaming.ts](https://github.com/get-convex/agent/blob/main/example/convex/chat/streaming.ts) for live code examples. ### Streaming text[​](#streaming-text "Direct link to Streaming text") Streaming text follows the same pattern as the approach below, but with a few differences, depending on the type of streaming you're doing. See [streaming](/agents/streaming.md) for more details. ### Basic approach (synchronous)[​](#basic-approach-synchronous "Direct link to Basic approach (synchronous)") ``` export const generateReplyToPrompt = action({ args: { prompt: v.string(), threadId: v.string() }, handler: async (ctx, { prompt, threadId }) => { // await authorizeThreadAccess(ctx, threadId); const result = await agent.generateText(ctx, { threadId }, { prompt }); return result.text; }, }); ``` Note: best practice is to not rely on returning data from the action. Instead, query for the thread messages via the `useThreadMessages` hook and receive the new message automatically. See below. ### Saving the prompt then generating response(s) asynchronously[​](#saving-the-prompt-then-generating-responses-asynchronously "Direct link to Saving the prompt then generating response(s) asynchronously") While the above approach is simple, generating responses asynchronously provide a few benefits: * You can set up optimistic UI updates on mutations that are transactional, so the message will be shown optimistically on the client until the message is saved and present in your message query. * You can save the message in the same mutation (transaction) as other writes to the database. This message can then be used and re-used in an action with retries, without duplicating the prompt message in the history. If the `promptMessageId` is used for multiple generations, any previous responses will automatically be included as context, so the LLM can continue where it left off. See [workflows](/agents/workflows.md) for more details. * Thanks to the idempotent guarantees of mutations, the client can safely retry mutations for days until they run exactly once. Actions can transiently fail. Any clients listing the messages will automatically get the new messages as they are created asynchronously. To generate responses asynchronously, you need to first save the message, then pass the `messageId` as `promptMessageId` to generate / stream text. ``` import { components, internal } from "./_generated/api"; import { saveMessage } from "@convex-dev/agent"; import { internalAction, mutation } from "./_generated/server"; import { v } from "convex/values"; // Step 1: Save a user message, and kick off an async response. export const sendMessage = mutation({ args: { threadId: v.id("threads"), prompt: v.string() }, handler: async (ctx, { threadId, prompt }) => { const { messageId } = await saveMessage(ctx, components.agent, { threadId, prompt, }); await ctx.scheduler.runAfter(0, internal.example.generateResponseAsync, { threadId, promptMessageId: messageId, }); }, }); // Step 2: Generate a response to a user message. export const generateResponseAsync = internalAction({ args: { threadId: v.string(), promptMessageId: v.string() }, handler: async (ctx, { threadId, promptMessageId }) => { await agent.generateText(ctx, { threadId }, { promptMessageId }); }, }); ``` Note that the action doesn't need to return anything. All messages are saved by default, so any client subscribed to the thread messages will receive the new message as it is generated asynchronously. ### Generating an object[​](#generating-an-object "Direct link to Generating an object") Similar to the AI SDK, you can generate or stream an object. The same arguments apply, except you don't have to provide a model. It will use the agent's default language model. ``` import { z } from "zod/v3"; const result = await thread.generateObject({ prompt: "Generate a plan based on the conversation so far", schema: z.object({...}), }); ``` Unfortunately, object generation doesn't support using tools. One, however, is to structure your object as arguments to a tool call that returns the object. You can use a custom `stopWhen` to stop the generation when the tool call produces the result and use `toolChoice: "required"` to prevent the LLM from returning a text response. ## Customizing the agent[​](#customizing-the-agent "Direct link to Customizing the agent") The agent by default only needs a `chat` model to be configured. However, for vector search, you'll need a `textEmbeddingModel` model. A `name` is helpful to attribute each message to a specific agent. Other options are defaults that can be over-ridden at each LLM call-site. ``` import { tool, stepCountIs } from "ai"; import { openai } from "@ai-sdk/openai"; import { z } from "zod/v3"; import { Agent, createTool, type Config } from "@convex-dev/agent"; import { components } from "./_generated/api"; const sharedDefaults = { // The language model to use for the agent. languageModel: openai.chat("gpt-4o-mini"), // Embedding model to power vector search of message history (RAG). textEmbeddingModel: openai.embedding("text-embedding-3-small"), // Used for fetching context messages. See https://docs.convex.dev/agents/context contextOptions, // Used for storing messages. See https://docs.convex.dev/agents/messages storageOptions, // Used for tracking token usage. See https://docs.convex.dev/agents/usage-tracking usageHandler: async (ctx, args) => { const { usage, model, provider, agentName, threadId, userId } = args; // ... log, save usage to your database, etc. }, // Used for filtering, modifying, or enriching the context messages. See https://docs.convex.dev/agents/context contextHandler: async (ctx, args) => { return [...customMessages, args.allMessages]; }, // Useful if you want to log or record every request and response. rawResponseHandler: async (ctx, args) => { const { request, response, agentName, threadId, userId } = args; // ... log, save request/response to your database, etc. }, // Used for limiting the number of retries when a tool call fails. Default: 3. callSettings: { maxRetries: 3, temperature: 1.0 }, } satisfies Config; const supportAgent = new Agent(components.agent, { // The default system prompt if not over-ridden. instructions: "You are a helpful assistant.", tools: { // Convex tool. See https://docs.convex.dev/agents/tools myConvexTool: createTool({ description: "My Convex tool", args: z.object({...}), // Note: annotate the return type of the handler to avoid type cycles. handler: async (ctx, args): Promise => { return "Hello, world!"; }, }), // Standard AI SDK tool myTool: tool({ description, parameters, execute: () => {}}), }, // Used for limiting the number of steps when tool calls are involved. // NOTE: if you want tool calls to happen automatically with a single call, // you need to set this to something greater than 1 (the default). stopWhen: stepCountIs(5), ...sharedDefaults, }); ``` --- # Source: https://docs.convex.dev/agents.md # AI Agents ## Building AI Agents with Convex[​](#building-ai-agents-with-convex "Direct link to Building AI Agents with Convex") Convex provides powerful building blocks for building agentic AI applications, leveraging Components and existing Convex features. With Convex, you can separate your long-running agentic workflows from your UI, without the user losing reactivity and interactivity. The message history with an LLM is persisted by default, live updating on every client, and easily composed with other Convex features using code rather than configuration. ## Agent Component[​](#agent-component "Direct link to Agent Component") The Agent component is a core building block for building AI agents. It manages threads and messages, around which your Agents can cooperate in static or dynamic workflows. [Agent Component YouTube Video](https://www.youtube.com/embed/tUKMPUlOCHY?si=ce-M8pt6EWDZ8tfd) [Agent Component YouTube Video](https://www.youtube.com/embed/tUKMPUlOCHY?si=ce-M8pt6EWDZ8tfd) ### Core Concepts[​](#core-concepts "Direct link to Core Concepts") * Agents organize LLM prompting with associated models, prompts, and [Tools](/agents/tools.md). They can generate and stream both text and objects. * Agents can be used in any Convex action, letting you write your agentic code alongside your other business logic with all the abstraction benefits of using code rather than static configuration. * [Threads](/agents/threads.md) persist [messages](/agents/messages.md) and can be shared by multiple users and agents (including [human agents](/agents/human-agents.md)). * [Conversation context](/agents/context.md) is automatically included in each LLM call, including built-in hybrid vector/text search for messages. ### Advanced Features[​](#advanced-features "Direct link to Advanced Features") * [Workflows](/agents/workflows.md) allow building multi-step operations that can span agents, users, durably and reliably. * [RAG](/agents/rag.md) techniques are also supported for prompt augmentation either up front or as tool calls using the [RAG Component](https://www.convex.dev/components/rag). * [Files](/agents/files.md) can be used in the chat history with automatic saving to [file storage](/file-storage.md). ### Debugging and Tracking[​](#debugging-and-tracking "Direct link to Debugging and Tracking") * [Debugging](/agents/debugging.md) is supported, including the [agent playground](/agents/playground.md) where you can inspect all metadata and iterate on prompts and context settings. * [Usage tracking](/agents/usage-tracking.md) enables usage billing for users and teams. * [Rate limiting](/agents/rate-limiting.md) helps control the rate at which users can interact with agents and keep you from exceeding your LLM provider's limits. ## [Build your first Agent](/agents/getting-started.md) Learn more about the motivation by reading: [AI Agents with Built-in Memory](https://stack.convex.dev/ai-agents). Sample code: ``` import { Agent } from "@convex-dev/agents"; import { openai } from "@ai-sdk/openai"; import { components } from "./_generated/api"; import { action } from "./_generated/server"; // Define an agent const supportAgent = new Agent(components.agent, { name: "Support Agent", chat: openai.chat("gpt-4o-mini"), instructions: "You are a helpful assistant.", tools: { accountLookup, fileTicket, sendEmail }, }); // Use the agent from within a normal action: export const createThread = action({ args: { prompt: v.string() }, handler: async (ctx, { prompt }) => { const { threadId, thread } = await supportAgent.createThread(ctx); const result = await thread.generateText({ prompt }); return { threadId, text: result.text }; }, }); // Pick up where you left off, with the same or a different agent: export const continueThread = action({ args: { prompt: v.string(), threadId: v.string() }, handler: async (ctx, { prompt, threadId }) => { // This includes previous message history from the thread automatically. const { thread } = await anotherAgent.continueThread(ctx, { threadId }); const result = await thread.generateText({ prompt }); return result.text; }, }); ``` --- # Source: https://docs.convex.dev/ai.md # AI Code Generation ## [Prompt to build an app with Convex Chef](https://chef.convex.dev) Convex is designed around a small set of composable abstractions with strong guarantees that result in code that is not only faster to write, but easier to read and maintain, whether written by a team member or an LLM. Key features make sure you get bug-free AI generated code: 1. **Queries are Just TypeScript** Your database queries are pure TypeScript functions with end-to-end type safety and IDE support. This means AI can generate database code using the large training set of TypeScript code without switching to SQL. 2. **Less Code for the Same Work** Since so much infrastructure and boiler plate is automatically managed by Convex there is less code to write, and thus less code to get wrong. 3. **Automatic Reactivity** The reactive system automatically tracks data dependencies and updates your UI. AI doesn't need to manually manage subscriptions, WebSocket connections, or complex state synchronization—Convex handles all of this automatically. 4. **Transactional Guarantees** Queries are read-only and mutations run in transactions. These constraints make it nearly impossible for AI to write code that could corrupt your data or leave your app in an inconsistent state. Together, these features mean AI can focus on your business logic while Convex's guarantees prevent common failure modes. For up-to-date information on which models work best with Convex, check out our LLM [leaderboard](https://convex.dev/llm-leaderboard). ## Convex AI rules[​](#convex-ai-rules "Direct link to Convex AI rules") AI code generation is most effective when you provide it with a set of rules to follow. See these documents for install instructions: * [Cursor](/ai/using-cursor.md#add-convex-cursorrules) * [Windsurf](/ai/using-windsurf.md#add-convex-rules) * [GitHub Copilot](/ai/using-github-copilot.md#add-convex-instructions) For all other IDEs, add the following rules file to your project and refer to it when prompting for changes: * [convex\_rules.txt](https://convex.link/convex_rules.txt) We're constantly working on improving the quality of these rules for Convex by using rigorous evals. You can help by [contributing to our evals repo](https://github.com/get-convex/convex-evals). ## Using Convex with Background Agents[​](#using-convex-with-background-agents "Direct link to Using Convex with Background Agents") Remote cloud-based coding agents like Jules, Devin, Codex, and Cursor background agents can use Convex deployments when the CLI is in [Agent Mode](/cli/agent-mode.md). This limits the permissions necessary for these remote dev environments while letting agents run codegen, iterate on code, run tests, run one-off functions. A good setup script for e.g. ChatGPT Codex might include ``` npm i CONVEX_AGENT_MODE=anonymous npx convex dev --once ``` or ``` bun i CONVEX_AGENT_MODE=anonymous bun x convex dev --once ``` This command requires "full" internet access to download the binary. ## Convex MCP Server[​](#convex-mcp-server "Direct link to Convex MCP Server") [Setup the Convex MCP server](/ai/convex-mcp-server.md) to give your AI coding agent access to your Convex deployment to query and optimize your project. --- # Source: https://docs.convex.dev/client/android.md # Source: https://docs.convex.dev/quickstart/android.md # Android Kotlin Quickstart Learn how to query data from Convex in a Android Kotlin project. This quickstart assumes that you have Android Studio, node and npm installed. If you don’t have those tools, take time to install them first. 1. Create a new Android app in Android Studio Choose the following options in the wizard. ``` 1. Choose the "Empty Activity" template 2. Name it "Convex Quickstart" 3. Choose min SDK as 26 4. Choose Kotlin as the Gradle DSL ``` 2. Configure the AndroidManifest Add the following to your `AndroidManifest.xml`. ``` ``` 3. Configure your dependencies Add the following entries to the `:app` `build.gradle.kts` file (ignore IDE suggestion to move them to version catalog for now, if present). Ensure that you sync Gradle when all of the above is complete (Android Studio should prompt you to do so). ``` plugins { // ... existing plugins kotlin("plugin.serialization") version "1.9.0" } dependencies { // ... existing dependencies implementation("dev.convex:android-convexmobile:0.4.1@aar") { isTransitive = true } implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") } ``` 4. Install the Convex Backend Open a terminal in your Android Studio instance and install the Convex client and server library. ``` npm init -y npm install convex ``` 5. Start Convex Start a Convex dev deployment. Follow the command line instructions. ``` npx convex dev ``` 6. Create a sample data for your database Create a new `sampleData.jsonl` file with these contents. ``` {"text": "Buy groceries", "isCompleted": true} {"text": "Go for a swim", "isCompleted": true} {"text": "Integrate Convex", "isCompleted": false} ``` 7. Add the sample data to your database Open another terminal tab and run. ``` npx convex import --table tasks sampleData.jsonl ``` 8. Expose a database query Create a `tasks.ts` file in your `convex/` directory with the following contents. ``` import { query } from "./_generated/server"; export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); }, }); ``` 9. Create a data class Add a new `data class` to your `MainActivity` to support the task data defined above. Import whatever it asks you to. ``` @Serializable data class Task(val text: String, val isCompleted: Boolean) ``` 10. Create your UI Delete the template `@Composable` functions that Android Studio created and add a new one to display data from your Convex deployment. Again, import whatever it asks you to. ``` @Composable fun Tasks(client: ConvexClient, modifier: Modifier = Modifier) { var tasks: List by remember { mutableStateOf(listOf()) } LaunchedEffect(key1 = "launch") { client.subscribe>("tasks:get").collect { result -> result.onSuccess { remoteTasks -> tasks = remoteTasks } } } LazyColumn( modifier = modifier ) { items(tasks) { task -> Text(text = "Text: ${task.text}, Completed?: ${task.isCompleted}") } } } ``` 11. Connect the app to your backend 1. Get the deployment URL of your dev server with `cat .env.local | grep CONVEX_URL` 2. Update the `onCreate` method in your `MainActivity.kt` to look like ``` override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { ConvexQuickstartTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Tasks( client = ConvexClient($YOUR_CONVEX_URL), modifier = Modifier.padding(innerPadding) ) } } } } ``` 12. Fix any missing imports Fix up any missing imports (your import declarations should look something like this): ``` import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import dev.convex.android.ConvexClient import kotlinx.serialization.Serializable ``` 13. Run the app You can also try adding, updating or deleting documents in your `tasks` table at `dashboard.convex.dev` - the app will update with the changes in real-time. ``` From the IDE menu choose "Run" > "Run 'app'" ``` See the complete [Android Kotlin documentation](/client/android.md). --- # Source: https://docs.convex.dev/generated-api/api.md # Source: https://docs.convex.dev/api.md # Convex TypeScript backend SDK, client libraries, and CLI for Convex. Convex is the backend application platform with everything you need to build your product. Get started at [docs.convex.dev](https://docs.convex.dev)! Or see [Convex demos](https://github.com/get-convex/convex-demos). Open discussions and issues in this repository about Convex TypeScript/JavaScript clients, the Convex CLI, or the Convex platform in general. Also feel free to share feature requests, product feedback, or general questions in the [Convex Discord Community](https://convex.dev/community). # Structure This package includes several entry points for building apps on Convex: * [`convex/server`](https://docs.convex.dev/api/modules/server): SDK for defining a Convex backend functions, defining a database schema, etc. * [`convex/react`](https://docs.convex.dev/api/modules/react): Hooks and a `ConvexReactClient` for integrating Convex into React applications. * [`convex/browser`](https://docs.convex.dev/api/modules/browser): A `ConvexHttpClient` for using Convex in other browser environments. * [`convex/values`](https://docs.convex.dev/api/modules/values): Utilities for working with values stored in Convex. * [`convex/react-auth0`](https://docs.convex.dev/api/modules/react_auth0): A React component for authenticating users with Auth0. * [`convex/react-clerk`](https://docs.convex.dev/api/modules/react_clerk): A React component for authenticating users with Clerk. * [`convex/nextjs`](https://docs.convex.dev/api/modules/nextjs): Server-side helpers for SSR, usable by Next.js and other React frameworks. This package also includes [`convex`](https://docs.convex.dev/using/cli), the command-line interface for managing Convex projects. --- # Source: https://docs.convex.dev/client/nextjs/app-router.md # Next.js [Next.js](https://nextjs.org/) is a React web development framework. When used with Convex, Next.js provides: * File-system based routing * Fast refresh in development * Font and image optimization and more! This page covers the App Router variant of Next.js. Alternatively see the [Pages Router](/client/nextjs/pages-router/.md) version of this page. ## Getting started[​](#getting-started "Direct link to Getting started") Follow the [Next.js Quickstart](/quickstart/nextjs.md) to add Convex to a new or existing Next.js project. ## Calling Convex functions from client code[​](#calling-convex-functions-from-client-code "Direct link to Calling Convex functions from client code") To fetch and edit the data in your database from client code, use hooks of the [Convex React library](/client/react.md). ## [Convex React library documentation](/client/react.md) ## Server rendering (SSR)[​](#server-rendering-ssr "Direct link to Server rendering (SSR)") Next.js automatically renders both Client and Server Components on the server during the initial page load. To keep your UI [automatically reactive](/functions/query-functions.md#caching--reactivity--consistency) to changes in your Convex database it needs to use Client Components. The `ConvexReactClient` will maintain a connection to your deployment and will get updates as data changes and that must happen on the client. See the dedicated [Server Rendering](/client/nextjs/app-router/server-rendering.md) page for more details about preloading data for Client Components, fetching data and authentication in Server Components, and implementing Route Handlers. ## Adding authentication[​](#adding-authentication "Direct link to Adding authentication") ### Client-side only[​](#client-side-only "Direct link to Client-side only") The simplest way to add user authentication to your Next.js app is to follow our React-based authentication guides for [Clerk](/auth/clerk.md) or [Auth0](/auth/auth0.md), inside your `app/ConvexClientProvider.tsx` file. For example this is what the file would look like for Auth0: app/ConvexClientProvider.tsx TS ``` "use client"; import { Auth0Provider } from "@auth0/auth0-react"; import { ConvexReactClient } from "convex/react"; import { ConvexProviderWithAuth0 } from "convex/react-auth0"; import { ReactNode } from "react"; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export function ConvexClientProvider({ children }: { children: ReactNode }) { return ( {children} ); } ``` Custom loading and logged out views can be built with the helper `Authenticated`, `Unauthenticated` and `AuthLoading` components from `convex/react`, see the [Convex Next.js demo](https://github.com/get-convex/convex-demos/tree/main/nextjs-pages-router/pages/_app.tsx) for an example. If only some routes of your app require login, the same helpers can be used directly in page components that do require login instead of being shared between all pages from `app/ConvexClientProvider.tsx`. Share a single [ConvexReactClient](/api/classes/react.ConvexReactClient.md) instance between pages to avoid needing to reconnect to Convex on client-side page navigation. ### Server and client side[​](#server-and-client-side "Direct link to Server and client side") To access user information or load Convex data requiring `ctx.auth` from Server Components, Server Actions, or Route Handlers you need to use the Next.js specific SDKs provided by Clerk and Auth0. Additional `.env.local` configuration is needed for these hybrid SDKs. #### Clerk[​](#clerk "Direct link to Clerk") For an example of using Convex and with Next.js 15, run **`npm create convex@latest -- -t nextjs-clerk`** **``** Otherwise, follow the [Clerk Next.js quickstart](https://clerk.com/docs/quickstarts/nextjs), a guide from Clerk that includes steps for adding `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` to the .env.local file. In Next.js 15, the `` component imported from the `@clerk/nextjs` v6 package functions as both a client and a server context provider so you probably won't need the `ClerkProvider` from `@clerk/clerk-react`. #### Auth0[​](#auth0 "Direct link to Auth0") See the [Auth0 Next.js](https://auth0.com/docs/quickstart/webapp/nextjs/01-login) guide. #### Other providers[​](#other-providers "Direct link to Other providers") Convex uses JWT identity tokens on the client for live query subscriptions and running mutations and actions, and on the Next.js backend for running queries, mutations, and actions in server components and API routes. Obtain the appropriate OpenID Identity JWT in both locations and you should be able to use any auth provider. See [Custom Auth](https://docs.convex.dev/auth/advanced/custom-auth) for more. --- # Source: https://docs.convex.dev/functions/error-handling/application-errors.md # Application Errors If you have expected ways your functions might fail, you can either return different values or throw `ConvexError`s. ## Returning different values[​](#returning-different-values "Direct link to Returning different values") If you're using TypeScript different return types can enforce that you're handling error scenarios. For example, a `createUser` mutation could return ``` Id<"users"> | { error: "EMAIL_ADDRESS_IN_USE" }; ``` to express that either the mutation succeeded or the email address was already taken. This ensures that you remember to handle these cases in your UI. ## Throwing application errors[​](#throwing-application-errors "Direct link to Throwing application errors") You might prefer to throw errors for the following reasons: * You can use the exception bubbling mechanism to throw from a deeply nested function call, instead of manually propagating error results up the call stack. This will work for `runQuery`, `runMutation` and `runAction` calls in [actions](/functions/actions.md) too. * In [mutations](/functions/mutation-functions.md), throwing an error will prevent the mutation transaction from committing * On the client, it might be simpler to handle all kinds of errors, both expected and unexpected, uniformly Convex provides an error subclass, [`ConvexError`](/api/classes/values.ConvexError.md), which can be used to carry information from the backend to the client: convex/myFunctions.ts TS ``` import { ConvexError } from "convex/values"; import { mutation } from "./_generated/server"; export const assignRole = mutation({ args: { // ... }, handler: (ctx, args) => { const isTaken = isRoleTaken(/* ... */); if (isTaken) { throw new ConvexError("Role is already taken"); } // ... }, }); ``` ### Application error `data` payload[​](#application-error-data-payload "Direct link to application-error-data-payload") You can pass the same [data types](/database/types.md) supported by function arguments, return types and the database, to the `ConvexError` constructor. This data will be stored on the `data` property of the error: ``` // error.data === "My fancy error message" throw new ConvexError("My fancy error message"); // error.data === {message: "My fancy error message", code: 123, severity: "high"} throw new ConvexError({ message: "My fancy error message", code: 123, severity: "high", }); // error.data === {code: 123, severity: "high"} throw new ConvexError({ code: 123, severity: "high", }); ``` Error payloads more complicated than a simple `string` are helpful for more structured error logging, or for handling sets of errors differently on the client. ## Handling application errors on the client[​](#handling-application-errors-on-the-client "Direct link to Handling application errors on the client") On the client, application errors also use the `ConvexError` class, and the data they carry can be accessed via the `data` property: src/App.tsx TS ``` import { ConvexError } from "convex/values"; import { useMutation } from "convex/react"; import { api } from "../convex/_generated/api"; export function MyApp() { const doSomething = useMutation(api.myFunctions.mutateSomething); const handleSomething = async () => { try { await doSomething({ a: 1, b: 2 }); } catch (error) { const errorMessage = // Check whether the error is an application error error instanceof ConvexError ? // Access data and cast it to the type we expect (error.data as { message: string }).message : // Must be some developer error, // and prod deployments will not // reveal any more information about it // to the client "Unexpected error occurred"; // do something with `errorMessage` } }; // ... } ``` --- # Source: https://docs.convex.dev/auth.md # Authentication Convex deployment endpoints are exposed to the open internet and the claims clients make about who they are must be authenticated to identify users and restrict what data they can see and edit. Convex is compatible with most authentication providers because it uses OpenID Connect (based on OAuth) ID tokens in the form of JWTs to authenticate WebSocket connections or RPCs. These JWTs can be provided by any service (including your own Convex backend) that implement the appropriate OAuth endpoints to verify them. ## Third-party authentication platforms[​](#third-party-authentication-platforms "Direct link to Third-party authentication platforms") Leveraging a Convex integration with a third-party auth provider provides the most comprehensive authentication solutions. Integrating another service provides a ton of functionality like passkeys, two-factor auth, spam protection, and more on top of the authentication basics. * [Clerk](/auth/clerk.md) has great Next.js and React Native support * [WorkOS AuthKit](/auth/authkit/.md) is built for B2B apps and free for up to 1M users * [Auth0](/auth/auth0.md) is more established with more bells and whistles * [Custom Auth Integration](/auth/advanced/custom-auth.md) allow any OpenID Connect-compatible identity provider to be used for authentication After you integrate one of these, learn more about accessing authentication information in [Functions](/auth/functions-auth.md) and storing user information in the [Database](/auth/database-auth.md). ## The Convex Auth Library[​](#the-convex-auth-library "Direct link to The Convex Auth Library") For client-side React and React Native mobile apps you can implement auth directly in Convex with the [Convex Auth](/auth/convex-auth.md) library. This [npm package](https://github.com/get-convex/convex-auth) runs on your Convex deployment and helps you build a custom sign-up/sign-in flow via social identity providers, one-time email or SMS access codes, or via passwords. Convex Auth is in beta (it isn't complete and may change in backward-incompatible ways) and doesn't provide as many features as third party auth integrations. Since it doesn't require signing up for another service it's the quickest way to get auth up and running. Convex Auth is in beta Convex Auth is currently a [beta feature](/production/state/.md#beta-features). If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community)! Support for Next.js is under active development. If you'd like to help test this experimental support please [give it a try](https://labs.convex.dev/auth)! ## Debugging[​](#debugging "Direct link to Debugging") If you run into issues consult the [Debugging](/auth/debug.md) guide. ## Service Authentication[​](#service-authentication "Direct link to Service Authentication") Servers you control or third party services can call Convex functions but may not be able to obtain OpenID JWTs and often do not represent the actions of a specific user. Say you're running some inference on a [Modal](https://modal.com/) server written in Python. When that server subscribes to a Convex query it doesn't do so with credentials of a particular end-user, rather it's looking for relevant tasks for any users that need that inference task, say summarizing and translating a conversation, completed. To provide access to Convex queries, mutations, and actions to an external service you can write public functions accessible to the internet that check a shared secret, for example from an environment variable, before doing anything else. ## Authorization[​](#authorization "Direct link to Authorization") Convex enables a traditional three tier application structure: a client/UI for your app, a backend that handles user requests, and a database for queries. This architecture lets you check every public request against any authorization rules you can define in code. This means Convex doesn't need an opinionated authorization framework like RLS, which is required in client oriented databases like Firebase or Supabase. This flexibility lets you build and use an [authorization framework](https://en.wikipedia.org/wiki/Authorization) for your needs. That said, the most common way is to simply write code that checks if the user is logged in and if they are allowed to do the requested action at the beginning of each public function. For example, the following function enforces that only the currently authenticated user can remove their own user image: ``` export const removeUserImage = mutation({ args: {}, handler: async (ctx) => { const userId = await getAuthUserId(ctx); if (!userId) { return; } ctx.db.patch("users", userId, { imageId: undefined, image: undefined }); }, }); ``` Related posts from [![Stack](/img/stack-logo-dark.svg)![Stack](/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # Source: https://docs.convex.dev/auth/auth0.md # Convex & Auth0 [Auth0](https://auth0.com) is an authentication platform providing login via passwords, social identity providers, one-time email or SMS access codes, multi-factor authentication, and single sign on and basic user management. **Example:** [Convex Authentication with Auth0](https://github.com/get-convex/convex-demos/tree/main/users-and-auth) If you're using Next.js see the [Next.js setup guide](https://docs.convex.dev/client/nextjs). ## Get started[​](#get-started "Direct link to Get started") This guide assumes you already have a working React app with Convex. If not follow the [Convex React Quickstart](/quickstart/react.md) first. Then: 1. Follow the Auth0 React quickstart Follow the [Auth0 React Quickstart](https://auth0.com/docs/quickstart/spa/react/interactive). Sign up for a free Auth0 account. Configure your application, using `http://localhost:3000, http://localhost:5173` for Callback and Logout URLs and Allowed Web Origins. Come back when you finish the *Install the Auth0 React SDK* step. ![Sign up to Auth0](/screenshots/auth0-signup.png) 2. Create the auth config In the `convex` folder create a new file `auth.config.ts` with the server-side configuration for validating access tokens. Paste in the `domain` and `clientId` values shown in *Install the Auth0 React SDK* step of the Auth0 quickstart or in your Auth0 application's Settings dashboard. convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { domain: "your-domain.us.auth0.com", applicationID: "yourclientid", }, ] } satisfies AuthConfig; ``` 3. Deploy your changes Run `npx convex dev` to automatically sync your configuration to your backend. ``` npx convex dev ``` 4. Configure ConvexProviderWithAuth0 Now replace your `ConvexProvider` with an `Auth0Provider` wrapping `ConvexProviderWithAuth0`. Add the `domain` and `clientId` as props to the `Auth0Provider`. Paste in the `domain` and `clientId` values shown in *Install the Auth0 React SDK* step of the Auth0 quickstart or in your Auth0 application's Settings dashboard as props to `Auth0Provider`. src/main.tsx TS ``` import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { ConvexReactClient } from "convex/react"; import { ConvexProviderWithAuth0 } from "convex/react-auth0"; import { Auth0Provider } from "@auth0/auth0-react"; const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string); ReactDOM.createRoot(document.getElementById("root")!).render( , ); ``` ## Login and logout flows[​](#login-and-logout-flows "Direct link to Login and logout flows") Now that you have everything set up, you can use the [`useAuth0()`](https://auth0.github.io/auth0-react/functions/useAuth0.html) hook to create login and logout buttons for your app. The login button will redirect the user to the Auth0 universal login page. For details see [Add Login to Your Application](https://auth0.com/docs/quickstart/spa/react/interactive#add-login-to-your-application) in the Auth0 React Quickstart. src/login.ts TS ``` import { useAuth0 } from "@auth0/auth0-react"; export default function LoginButton() { const { loginWithRedirect } = useAuth0(); return ; } ``` The logout button will redirect the user to the Auth0 logout endpoint. For details see [Add Logout to your Application](https://auth0.com/docs/quickstart/spa/react/interactive#add-logout-to-your-application) in the Auth0 React Quickstart. src/logout.ts TS ``` import { useAuth0 } from "@auth0/auth0-react"; export default function LogoutButton() { const { logout } = useAuth0(); return ( ); } ``` ## Logged-in and logged-out views[​](#logged-in-and-logged-out-views "Direct link to Logged-in and logged-out views") Use the [`useConvexAuth()`](/api/modules/react.md#useconvexauth) hook instead of the `useAuth0` hook when you need to check whether the user is logged in or not. The `useConvex` hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend: src/App.ts TS ``` import { useConvexAuth } from "convex/react"; function App() { const { isLoading, isAuthenticated } = useConvexAuth(); return (
{isAuthenticated ? "Logged in" : "Logged out or still loading"}
); } ``` You can also use the `Authenticated`, `Unauthenticated` and `AuthLoading` helper components which use the `useConvexAuth` hook under the hood: src/App.ts TS ``` import { Authenticated, Unauthenticated, AuthLoading } from "convex/react"; function App() { return (
Logged in Logged out Still loading
); } ``` ## User information in React[​](#user-information-in-react "Direct link to User information in React") You can access information about the authenticated user like their name from the `useAuth0` hook: src/badge.ts TS ``` import { useAuth0 } from "@auth0/auth0-react"; export default function Badge() { const { user } = useAuth0(); return Logged in as {user.name}; } ``` ## User information in functions[​](#user-information-in-functions "Direct link to User information in functions") See [Auth in Functions](/auth/functions-auth.md) to learn about how to access information about the authenticated user in your queries, mutations and actions. See [Storing Users in the Convex Database](/auth/database-auth.md) to learn about how to store user information in the Convex database. ## Configuring dev and prod tenants[​](#configuring-dev-and-prod-tenants "Direct link to Configuring dev and prod tenants") To configure a different Auth0 tenant (environment) between your Convex development and production deployments you can use environment variables configured on the Convex dashboard. ### Configuring the backend[​](#configuring-the-backend "Direct link to Configuring the backend") First, change your `auth.config.ts` file to use environment variables: convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { domain: process.env.AUTH0_DOMAIN!, applicationID: process.env.AUTH0_CLIENT_ID!, }, ], } satisfies AuthConfig; ``` **Development configuration** Open the Settings for your dev deployment on the Convex [dashboard](https://dashboard.convex.dev) and add the variables there: ![Convex dashboard dev deployment settings](/screenshots/auth0-convex-dashboard.png) Now switch to the new configuration by running `npx convex dev`. **Production configuration** Similarly on the Convex [dashboard](https://dashboard.convex.dev) switch to your production deployment in the left side menu and set the values for your production Auth0 tenant there. Now switch to the new configuration by running `npx convex deploy`. ### Configuring a React client[​](#configuring-a-react-client "Direct link to Configuring a React client") To configure your client you can use environment variables as well. The exact name of the environment variables and the way to refer to them depends on each client platform (Vite vs Next.js etc.), refer to our corresponding [Quickstart](/quickstarts) or the relevant documentation for the platform you're using. Change the props to `Auth0Provider` to take in environment variables: src/main.tsx TS ``` import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { ConvexReactClient } from "convex/react"; import { ConvexProviderWithAuth0 } from "convex/react-auth0"; import { Auth0Provider } from "@auth0/auth0-react"; const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string); ReactDOM.createRoot(document.getElementById("root")!).render( , ); ``` **Development configuration** Use the `.env.local` or `.env` file to configure your client when running locally. The name of the environment variables file depends on each client platform (Vite vs Next.js etc.), refer to our corresponding [Quickstart](/quickstarts) or the relevant documentation for the platform you're using: .env.local ``` VITE_AUTH0_DOMAIN="your-domain.us.auth0.com" VITE_AUTH0_CLIENT_ID="yourclientid" ``` **Production configuration** Set the environment variables in your production environment depending on your hosting platform. See [Hosting](/production/hosting/.md). ## Debugging authentication[​](#debugging-authentication "Direct link to Debugging authentication") If a user goes through the Auth0 login flow successfully, and after being redirected back to your page `useConvexAuth` gives `isAuthenticated: false`, it's possible that your backend isn't correctly configured. The `auth.config.ts` file in your `convex/` directory contains a list of configured authentication providers. You must run `npx convex dev` or `npx convex deploy` after adding a new provider to sync the configuration to your backend. For more thorough debugging steps, see [Debugging Authentication](/auth/debug.md). ## Under the hood[​](#under-the-hood "Direct link to Under the hood") The authentication flow looks like this under the hood: 1. The user clicks a login button 2. The user is redirected to a page where they log in via whatever method you configure in Auth0 3. After a successful login Auth0 redirects back to your page, or a different page which you configure via the [`authorizationParams`](https://auth0.github.io/auth0-react/interfaces/AuthorizationParams.html) prop . 4. The `Auth0Provider` now knows that the user is authenticated. 5. The `ConvexProviderWithAuth0` fetches an auth token from Auth0 . 6. The `ConvexReactClient` passes this token down to your Convex backend to validate 7. Your Convex backend retrieves the public key from Auth0 to check that the token's signature is valid. 8. The `ConvexReactClient` is notified of successful authentication, and `ConvexProviderWithAuth0` now knows that the user is authenticated with Convex. `useConvexAuth` returns `isAuthenticated: true` and the `Authenticated` component renders its children. `ConvexProviderWithAuth0` takes care of refetching the token when needed to make sure the user stays authenticated with your backend. --- # Source: https://docs.convex.dev/auth/authkit.md # Convex & WorkOS AuthKit [WorkOS AuthKit](https://authkit.com) is an authentication solution that enables sign-in using passwords, social login providers, email one-time codes, two-factor authentication, and user management capabilities. You can use your own WorkOS account with AuthKit or [create a WorkOS account with Convex](/auth/authkit/auto-provision.md) to create and do some configuration of AuthKit environments automatically. ## Get started[​](#get-started "Direct link to Get started") The quickest way to get started is with a template: ``` npm create convex@latest -- -t react-vite-authkit cd my-app # or whatever you name the directory npm run dev ``` Follow the prompts to create a WorkOS team that will be associated with your Convex team. After this Convex deployments for projects in this team will be able to automatically provision and configure their own WorkOS environments. That's it! After this you and other members of your Convex team can create and configure development WorkOS environments without visiting [workos.com](https://workos.com). See [AuthKit configuration in convex.json](/auth/authkit/auto-provision.md) to modify the convex.json file in this template for your needs. ### Configuring an existing WorkOS account[​](#configuring-an-existing-workos-account "Direct link to Configuring an existing WorkOS account") To use AuthKit with an existing WorkOS account you'll need to configure the account and copy credentials into the Convex deployment and your local `.env.local` file. 1. Sign up for WorkOS Sign up for a free WorkOS account at [workos.com/sign-up](https://signin.workos.com/sign-up). ![Sign up for a WorkOS account](/screenshots/workos-signup.png) 2. Set up AuthKit In the WorkOS Dashboard, navigate to **Authentication** and then to **AuthKit**. From here, click the **Set up AuthKit** button to enable AuthKit in your account. ![Set up AuthKit in your account](/screenshots/workos-setup-authkit.png) 3. Complete AuthKit setup Press the **Begin setup** button with **Use AuthKit's customizable hosted UI** selected. These options can be filled out however you like until you get to step 4, **Add default redirect endpoint URI**. The Redirect URI is the endpoint that WorkOS will return an authorization code to after signing in. This should match your application's domain and port, with `/callback` as the route. For example, if your application is running at `localhost:5173` then the value here should be `http://localhost:5173/callback`. Complete the AuthKit setup. ![Set the redirect URI endpoint](/screenshots/workos-redirect-uri.png) 4. Copy your Client ID and API Key From the [get started](https://dashboard.workos.com/get-started) page under **Quick start**, find your `WORKOS_CLIENT_ID` and copy it. ![Getting your WorkOS Client ID](/screenshots/workos-client-id.png) ## Client configuration[​](#client-configuration "Direct link to Client configuration") Convex offers a provider that is specifically for integrating with WorkOS AuthKit called ``. It works using WorkOS's [authkit-react](https://github.com/workos/authkit-react) SDK. Once you've completed the WorkOS setup above, choose your framework below to continue with the integration. See the following sections for the WorkOS SDK that you're using: * [React](#react) - Use this as a starting point if your SDK is not listed * [Next.js](#nextjs) ### React[​](#react "Direct link to React") **Example:** [React with Convex and AuthKit](https://github.com/workos/template-convex-react-vite-authkit) This guide assumes you have [AuthKit set up](#configuring-an-existing-workos-account) and have a working React app with Convex. If not follow the [Convex React Quickstart](/quickstart/react.md) first. Then: 1. Set up CORS in the WorkOS Dashboard In your WorkOS Dashboard, go to [*Authentication* > *Sessions*](https://dashboard.workos.com/environment/authentication/sessions) > *Cross-Origin Resource Sharing (CORS)* and click on **Manage**. Add your local development domain (e.g., `http://localhost:5173` for Vite) to the list. You'll also need to add your production domain when you deploy. This enables your application to authenticate users through WorkOS AuthKit. ![Setting up CORS](/screenshots/workos-cors-setup.png) 2. Set up your environment variables In your `.env.local` file, add your `WORKOS_CLIENT_ID` and `WORKOS_REDIRECT_URI` environment variables. If you're using Vite, you'll need to prefix it with `VITE_`. **Note:** These values can be found in your [WorkOS Dashboard](https://dashboard.workos.com/). .env.local ``` # WorkOS AuthKit Configuration VITE_WORKOS_CLIENT_ID=your-workos-client-id-here VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback ``` 3. Configure Convex with the WorkOS Client ID In your app's `convex` folder, create a new file `auth.config.ts` with the following code. This is the server-side configuration for validating access tokens. convex/auth.config.ts TS ``` const clientId = process.env.WORKOS_CLIENT_ID; const authConfig = { providers: [ { type: 'customJwt', issuer: `https://api.workos.com/`, algorithm: 'RS256', jwks: `https://api.workos.com/sso/jwks/${clientId}`, applicationID: clientId, }, { type: 'customJwt', issuer: `https://api.workos.com/user_management/${clientId}`, algorithm: 'RS256', jwks: `https://api.workos.com/sso/jwks/${clientId}`, }, ], }; export default authConfig; ``` 4. Deploy your changes Run `npx convex dev` to automatically sync your configuration to your backend. You'll see an error and a link to click to fill in the WORKOS\_CLIENT\_ID environment variable in your Convex deployment. Follow the link, paste in the WorkOS client ID, save, and you should see the `npx convex dev` command show "Convex functions ready." ``` npx convex dev ``` 5. Install AuthKit In a new terminal window, install the AuthKit React SDK: ``` npm install @workos-inc/authkit-react @convex-dev/workos ``` 6. Configure ConvexProviderWithAuthKit AuthKit and Convex both have provider components that provide authentication and client context to your app. You should already have `` wrapping your app. Replace it with ``, and pass WorkOS's `useAuth()` hook to it. Then, wrap it with ``. `` requires `clientId` and `redirectUri` props, which you can set to `VITE_WORKOS_CLIENT_ID` and `VITE_WORKOS_REDIRECT_URI`, respectively. src/main.tsx TS ``` import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react"; import { ConvexReactClient } from "convex/react"; import { ConvexProviderWithAuthKit } from "@convex-dev/workos"; import "./index.css"; import App from "./App.tsx"; const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL); createRoot(document.getElementById("root")!).render( , ); ``` 7. Show UI based on authentication state You can control which UI is shown when the user is signed in or signed out using Convex's ``, `` and `` helper components. It's important to use the [`useConvexAuth()`](/api/modules/react.md#useconvexauth) hook instead of AuthKit's `useAuth()` hook when you need to check whether the user is logged in or not. The `useConvexAuth()` hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it. In the following example, the `` component is a child of ``, so its content and any of its child components are guaranteed to have an authenticated user, and Convex queries can require authentication. src/App.tsx TS ``` import { Authenticated, Unauthenticated, useQuery } from 'convex/react'; import { api } from '../convex/_generated/api'; import { useAuth } from '@workos-inc/authkit-react'; export default function App() { const { user, signIn, signOut } = useAuth(); return (

Convex + AuthKit

Please sign in to view data

); } function Content() { const data = useQuery(api.myFunctions.listNumbers, { count: 10 }); if (!data) return

Loading...

; return (

Welcome {data.viewer}!

Numbers: {data.numbers?.join(', ') || 'None'}

); } ``` 8. Use authentication state in your Convex functions If the client is authenticated, you can access the information stored in the JWT via `ctx.auth.getUserIdentity`. If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`. **Make sure that the component calling this query is a child of `` from `convex/react`**. Otherwise, it will throw on page load. convex/myFunctions.ts TS ``` import { v } from "convex/values"; import { query } from "./_generated/server"; export const listNumbers = query({ args: { count: v.number(), }, handler: async (ctx, args) => { const numbers = await ctx.db .query("numbers") // Ordered by _creationTime, return most recent .order("desc") .take(args.count); return { viewer: (await ctx.auth.getUserIdentity())?.name ?? null, numbers: numbers.reverse().map((number) => number.value), }; }, }); ``` **Note:** The [React template repository](https://github.com/workos/template-convex-react-vite-authkit) includes additional features and functions for a complete working application. This tutorial covers the core integration steps, but the template provides a more comprehensive implementation. ### Next.js[​](#nextjs "Direct link to Next.js") **Example:** [Next.js with Convex and AuthKit](https://github.com/workos/template-convex-nextjs-authkit) This guide assumes you have [AuthKit set up](#configuring-an-existing-workos-account) and have a working Next.js app with Convex. If not follow the [Convex Next.js Quickstart](/quickstart/nextjs.md) first. Then: 1. Set up your environment variables In your `.env.local` file, add the following environment variables: .env.local ``` # WorkOS AuthKit Configuration WORKOS_CLIENT_ID=client_your_client_id_here WORKOS_API_KEY=sk_test_your_api_key_here WORKOS_COOKIE_PASSWORD=your_secure_password_here_must_be_at_least_32_characters_long NEXT_PUBLIC_WORKOS_REDIRECT_URI=http://localhost:3000/callback # Convex Configuration (you don't have to fill these out, they're generated by Convex) # Deployment used by `npx convex dev` CONVEX_DEPLOY_KEY=your_convex_deploy_key_here NEXT_PUBLIC_CONVEX_URL=https://your-convex-url.convex.cloud ``` 2. Configure Convex with the WorkOS Client ID In your app's `convex` folder, create a new file `auth.config.ts` with the following code. This is the server-side configuration for validating access tokens. convex/auth.config.ts TS ``` const clientId = process.env.WORKOS_CLIENT_ID; const authConfig = { providers: [ { type: 'customJwt', issuer: `https://api.workos.com/`, algorithm: 'RS256', applicationID: clientId, jwks: `https://api.workos.com/sso/jwks/${clientId}`, }, { type: 'customJwt', issuer: `https://api.workos.com/user_management/${clientId}`, algorithm: 'RS256', jwks: `https://api.workos.com/sso/jwks/${clientId}`, }, ], }; export default authConfig; ``` 3. Deploy your changes Run `npx convex dev` to automatically sync your configuration to your backend. You'll see an error and a link to click to fill in the WORKOS\_CLIENT\_ID environment variable in your Convex deployment. Follow the link, paste in the WorkOS client ID, save, and you should see the `npx convex dev` command show "Convex functions ready." ``` npx convex dev ``` 4. Install AuthKit In a new terminal window, install the AuthKit Next.js SDK: ``` npm install @workos-inc/authkit-nextjs @convex-dev/workos ``` 5. Add AuthKit middleware AuthKit's `authkitMiddleware()` helper grants you access to user authentication state throughout your app. Create a `middleware.ts` file. In your `middleware.ts` file, export the `authkitMiddleware()` helper: ``` import { authkitMiddleware } from '@workos-inc/authkit-nextjs'; export default authkitMiddleware({ middlewareAuth: { enabled: true, unauthenticatedPaths: ['/', '/sign-in', '/sign-up'], }, }); export const config = { matcher: [ // Skip Next.js internals and all static files, unless found in search params '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', // Always run for API routes '/(api|trpc)(.*)', ], }; ``` 6. Add authentication routes Create the required authentication routes for WorkOS AuthKit to handle sign-in, sign-up, and callback flows. These routes enable the authentication flow by providing endpoints for users to sign in, sign up, and return after authentication. **Create the callback route** to handle OAuth callbacks: app/callback/route.ts TS ``` import { handleAuth } from '@workos-inc/authkit-nextjs'; export const GET = handleAuth(); ``` 7. Create the sign-in route app/sign-in/route.ts TS ``` import { redirect } from 'next/navigation'; import { getSignInUrl } from '@workos-inc/authkit-nextjs'; export async function GET() { const authorizationUrl = await getSignInUrl(); return redirect(authorizationUrl); } ``` 8. Create the sign-up route To redirect users to WorkOS sign-up: app/sign-up/route.ts TS ``` import { redirect } from 'next/navigation'; import { getSignUpUrl } from '@workos-inc/authkit-nextjs'; export async function GET() { const authorizationUrl = await getSignUpUrl(); return redirect(authorizationUrl); } ``` 9. Configure ConvexProviderWithAuthKit Your Next.js app needs to connect AuthKit authentication with Convex for real-time data. We'll create a single provider component that handles both. **Create the Provider Component** This single component handles: * WorkOS authentication setup * Convex client initialization * Token management between WorkOS and Convex * Loading states and error handling Create `components/ConvexClientProvider.tsx`: components/ConvexClientProvider.tsx TS ``` 'use client'; import { ReactNode, useCallback, useRef } from 'react'; import { ConvexReactClient } from 'convex/react'; import { ConvexProviderWithAuth } from 'convex/react'; import { AuthKitProvider, useAuth, useAccessToken } from '@workos-inc/authkit-nextjs/components'; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); export function ConvexClientProvider({ children }: { children: ReactNode }) { return ( {children} ); } function useAuthFromAuthKit() { const { user, loading: isLoading } = useAuth(); const { accessToken, loading: tokenLoading, error: tokenError } = useAccessToken(); const loading = (isLoading ?? false) || (tokenLoading ?? false); const authenticated = !!user && !!accessToken && !loading; const stableAccessToken = useRef(null); if (accessToken && !tokenError) { stableAccessToken.current = accessToken; } const fetchAccessToken = useCallback(async () => { if (stableAccessToken.current && !tokenError) { return stableAccessToken.current; } return null; }, [tokenError]); return { isLoading: loading, isAuthenticated: authenticated, fetchAccessToken, }; } ``` 10. Add to your layout Update `app/layout.tsx` to use the provider: app/layout.tsx TS ``` import type { Metadata } from 'next'; import { Geist, Geist_Mono } from 'next/font/google'; import './globals.css'; import { ConvexClientProvider } from '@/components/ConvexClientProvider'; const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'], }); const geistMono = Geist_Mono({ variable: '--font-geist-mono', subsets: ['latin'], }); export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app', icons: { icon: '/convex.svg', }, }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( {children} ); } ``` 11. Show UI based on authentication state You can control which UI is shown when the user is signed in or signed out using Convex's ``, `` and `` helper components. These should be used instead of WorkOS AuthKit's `useAuth()` loading states and manual authentication checks. It's important to use the [`useConvexAuth()`](/api/modules/react.md#useconvexauth) hook instead of WorkOS AuthKit's `useAuth()` hook when you need to check whether the user is logged in or not. The `useConvexAuth()` hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it. In the following example, the `` component is a child of ``, so its content and any of its child components are guaranteed to have an authenticated user, and Convex queries can require authentication. app/page.tsx TS ``` "use client"; import { Authenticated, Unauthenticated, useQuery } from "convex/react"; import { useAuth } from "@workos-inc/authkit-nextjs/components"; import { api } from "../convex/_generated/api"; import Link from "next/link"; export default function Home() { const { user, signOut } = useAuth(); return (

Convex + AuthKit

{user ? ( ) : ( <> )}

Please sign in to view data

); } function Content() { const data = useQuery(api.myFunctions.listNumbers, { count: 10 }); if (!data) return

Loading...

; return (

Welcome {data.viewer}!

Numbers: {data.numbers?.join(', ') || 'None'}

); } ``` 12. Use authentication state in your Convex functions If the client is authenticated, you can access the information stored in the JWT via `ctx.auth.getUserIdentity`. If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`. **Make sure that the component calling this query is a child of `` from `convex/react`**. Otherwise, it will throw on page load. convex/myFunctions.ts TS ``` import { v } from "convex/values"; import { query } from "./_generated/server"; export const listNumbers = query({ args: { count: v.number(), }, handler: async (ctx, args) => { const numbers = await ctx.db .query("numbers") // Ordered by _creationTime, return most recent .order("desc") .take(args.count); return { viewer: (await ctx.auth.getUserIdentity())?.name ?? null, numbers: numbers.reverse().map((number) => number.value), }; }, }); ``` **Note:** The [Next.js template repository](https://github.com/workos/template-convex-nextjs-authkit) includes additional features and functions for a complete working application. This tutorial covers the core integration steps, but the template provides a more comprehensive implementation. ## Next steps[​](#next-steps "Direct link to Next steps") ### Accessing user information in functions[​](#accessing-user-information-in-functions "Direct link to Accessing user information in functions") See [Auth in Functions](/auth/functions-auth.md) to learn about how to access information about the authenticated user in your queries, mutations and actions. See [Storing Users in the Convex Database](/auth/database-auth.md) to learn about how to store user information in the Convex database. ### Accessing user information client-side[​](#accessing-user-information-client-side "Direct link to Accessing user information client-side") To access the authenticated user's information, use AuthKit's `User` object, which can be accessed using AuthKit's [`useAuth()`](https://github.com/workos/authkit-react?tab=readme-ov-file#useauth) hook. For more information on the `User` object, see the [WorkOS docs](https://workos.com/docs/reference/user-management/user). components/Badge.tsx TS ``` export default function Badge() { const { user } = useAuth(); return Logged in as {user.firstName}; } ``` ## Configuring dev and prod instances[​](#configuring-dev-and-prod-instances "Direct link to Configuring dev and prod instances") To configure a different AuthKit instance between your Convex development and production deployments, you can use environment variables configured on the Convex dashboard. ### Configuring the backend[​](#configuring-the-backend "Direct link to Configuring the backend") In the WorkOS Dashboard, navigate to the [**API keys**](https://dashboard.workos.com/api-keys) page. Copy your WorkOS Client ID. This Client ID is necessary for Convex to validate access tokens from WorkOS AuthKit. In development, its format will be `client_01XXXXXXXXXXXXXXXXXXXXXXXX`. In production, it will follow the same format but represent your production WorkOS application. Paste your WorkOS Client ID into your `.env` file, set it as the `WORKOS_CLIENT_ID` environment variable. Note that this environment variable is used server-side and does not need a `NEXT_PUBLIC_` prefix. .env ``` WORKOS_CLIENT_ID=client_01XXXXXXXXXXXXXXXXXXXXXXXX ``` Then, update your `convex/auth.config.ts` file to use the environment variable: convex/auth.config.ts TS ``` const clientId = process.env.WORKOS_CLIENT_ID; export default { providers: [ { type: "customJwt", issuer: `https://api.workos.com/`, algorithm: "RS256", applicationID: clientId, jwks: `https://api.workos.com/sso/jwks/${clientId}`, }, { type: "customJwt", issuer: `https://api.workos.com/user_management/${clientId}`, algorithm: "RS256", jwks: `https://api.workos.com/sso/jwks/${clientId}`, }, ], }; ``` **Development configuration** In the left sidenav of the Convex [dashboard](https://dashboard.convex.dev), switch to your development deployment and set the `WORKOS_CLIENT_ID` environment variable to your development WorkOS Client ID. Then, to switch your deployment to the new configuration, run `npx convex dev`. **Production configuration** In the left sidenav of the Convex [dashboard](https://dashboard.convex.dev), switch to your production deployment and set the `WORKOS_CLIENT_ID` environment variable to your production WorkOS Client ID. Then, to switch your deployment to the new configuration, run `npx convex deploy`. ### Configuring WorkOS AuthKit's API keys[​](#configuring-workos-authkits-api-keys "Direct link to Configuring WorkOS AuthKit's API keys") WorkOS AuthKit's API keys differ depending on whether they are for development or production. Don't forget to update the environment variables in your `.env` file as well as your hosting platform, such as Vercel or Netlify. **Development configuration** WorkOS API Key for development follows the format `sk_test_...`. WorkOS Client ID for development follows the format `client_01...`. .env.local ``` WORKOS_CLIENT_ID="client_01XXXXXXXXXXXXXXXXXXXXXXXX" WORKOS_API_KEY="sk_test_..." WORKOS_COOKIE_PASSWORD="your_secure_password_here_must_be_at_least_32_characters_long" NEXT_PUBLIC_WORKOS_REDIRECT_URI="http://localhost:3000/callback" ``` **Production configuration** WorkOS API Key for production follows the format `sk_live_...`. WorkOS Client ID for production follows the format `client_01...`. .env ``` WORKOS_CLIENT_ID="client_01XXXXXXXXXXXXXXXXXXXXXXXX" WORKOS_API_KEY="sk_live_..." WORKOS_COOKIE_PASSWORD="your_secure_password_here_must_be_at_least_32_characters_long" NEXT_PUBLIC_WORKOS_REDIRECT_URI="https://your-domain.com/callback" ``` ### Additional WorkOS AuthKit Configuration[​](#additional-workos-authkit-configuration "Direct link to Additional WorkOS AuthKit Configuration") WorkOS AuthKit requires additional configuration: **Cookie Password**: A secure password used to encrypt session cookies. This must be at least 32 characters long. You can generate a random one with `openssl rand -base64 24`. **Redirect URI**: The URL where users are redirected after authentication. This must be configured in both your environment variables and your WorkOS Dashboard application settings. ## Debugging authentication[​](#debugging-authentication "Direct link to Debugging authentication") If a user goes through the WorkOS AuthKit login flow successfully, and after being redirected back to your page, `useConvexAuth()` returns `isAuthenticated: false`, it's possible that your backend isn't correctly configured. The `convex/auth.config.ts` file contains a list of configured authentication providers. You must run `npx convex dev` or `npx convex deploy` after adding a new provider to sync the configuration to your backend. Common issues with WorkOS AuthKit integration: 1. **Incorrect Client ID**: Ensure the `WORKOS_CLIENT_ID` in your Convex environment matches your WorkOS application 2. **Missing Environment Variables**: Verify all required WorkOS environment variables are set in both your local environment and Convex dashboard 3. **Redirect URI Mismatch**: Ensure the `NEXT_PUBLIC_WORKOS_REDIRECT_URI` matches what's configured in your WorkOS Dashboard 4. **Missing `aud` claim**: WorkOS JWTs may not include the `aud` (audience) claim by default, which Convex requires for token validation. Check your WorkOS Dashboard JWT configuration to ensure the audience claim is properly set to your Client ID For more thorough debugging steps, see the WorkOS AuthKit documentation or [Debugging Authentication](/auth/debug.md). ## Under the hood[​](#under-the-hood "Direct link to Under the hood") The authentication flow looks like this under the hood: 1. The user clicks a login button 2. The user is redirected to a page where they log in via whatever method you configure in AuthKit 3. After a successful login AuthKit redirects back to your page, or a different page which you configure via the [`redirectUri`](https://workos.com/docs/user-management/vanilla/nodejs/1-configure-your-project/configure-a-redirect-uri) prop . 4. The `AuthKitProvider` now knows that the user is authenticated. 5. The `ConvexProviderWithAuthKit` fetches an auth token from AuthKit . 6. The `ConvexReactClient` passes this token down to your Convex backend to validate 7. Your Convex backend retrieves the public key from AuthKit to check that the token's signature is valid. 8. The `ConvexReactClient` is notified of successful authentication, and `ConvexProviderWithAuthKit` now knows that the user is authenticated with Convex. `useConvexAuth` returns `isAuthenticated: true` and the `Authenticated` component renders its children. `ConvexProviderWithAuthKit` takes care of refetching the token when needed to make sure the user stays authenticated with your backend. --- # Source: https://docs.convex.dev/components/authoring.md # Authoring Components Building a Convex Component lets you package up Convex functions, schemas, and persistent state into a reusable module that you or other developers can drop into their projects. They differ from regular libraries in that they have their own database tables, sub-transactions, and can define functions that run in an isolated environment. Trying to decide between writing a library or a component? Building it as a component allows you to: * Persist data to tables where you control the schema. * Isolate access to data behind an API boundary. * Define queries, mutations, and actions that can run asynchronously to manage complex workflows. * Share functionality between apps in a predictable way. ## Anatomy of a component[​](#anatomy-of-a-component "Direct link to Anatomy of a component") Practically speaking, a component is defined in a folder containing a `convex.config.ts`. The component's folder has the same structure as a normal `convex/` folder: ``` component/ ├── _generated/ # Generated code for the component's API and data model. ├── convex.config.ts # Defines the component and its child components. ├── schema.ts # Defines a schema only accessible by the component └-- …folders/files.ts # Queries, mutations, and actions for the component. ``` The component's `convex.config.ts` file configures the component's default name and child components. component/convex.config.ts TS ``` import { defineComponent } from "convex/server"; // import workpool from "@convex-dev/workpool/convex.config.js"; // import localComponent from "../localComponent/convex.config.js"; const component = defineComponent("myComponent"); // component.use(workpool); // component.use(localComponent, { name: "customName" }); export default component; ``` Instances of the component are configured when used by the main app or other components in their `convex.config.ts` files, forming a tree of components, with the main app at the root. ## Getting started[​](#getting-started "Direct link to Getting started") The source code for components can be a local folder or bundled into an NPM package. ### Local components[​](#local-components "Direct link to Local components") The easiest way to get started is by creating a new folder for your component and adding a `convex.config.ts` file to it (like the one above). Then import it in your app's `convex/convex.config.ts` file: convex/convex.config.ts TS ``` import { defineApp } from "convex/server"; import myComponent from "./components/myComponent/convex.config.js"; const app = defineApp(); app.use(myComponent); export default app; ``` Once installed, `npx convex dev` will generate code in `./components/myComponent/_generated/` and re-generate it whenever you make changes to the component's code. Tip: The recommended pattern for local components is to organize them in a `convex/components` folder, but they can be stored anywhere in your project. ### Packaged components[​](#packaged-components "Direct link to Packaged components") Components can be distributed and installed as NPM packages, enabling you to share solutions to common problems with the broader developer community via the [Convex Components directory](https://convex.dev/components). Get started with a new project using the [component template](https://github.com/get-convex/templates/tree/main/template-component): ``` npx create-convex@latest --component ``` Follow the CLI's instructions to get started and keep the component's generated code up-to-date. [See below for more information on building and publishing NPM package components.](#building-and-publishing-npm-package-components) ### Hybrid components[​](#hybrid-components "Direct link to Hybrid components") Hybrid components define a local component, but use shared library code for some of the functionality. This allows you to provide a extra flexibility when users need to override or extend the schema or functions. An example of a hybrid component is the [Better Auth Component](https://convex-better-auth.netlify.app/features/local-install). Note: in general, components should be composed or designed to be extended explicitly, as hybrid components introduce a lot of complexity for maintaining and updating the component in backwards-compatible ways. ## Hello world[​](#hello-world "Direct link to Hello world") To try adding a new function, create a file `hello.ts` in your component's folder (e.g. `src/component/hello.ts` in the template): ./path/to/component/hello.ts TS ``` import { v } from "convex/values"; import { query } from "./_generated/server.js"; export const world = query({ args: {}, returns: v.string(), handler: async () => { return "hello world"; }, }); ``` After it deploys, you can run `npx convex run --component myComponent hello:world`. You can now also run it from a function in your app: convex/sayHi.ts TS ``` import { components } from "./_generated/api"; import { query } from "./_generated/server"; export default query({ handler: async (ctx) => { return await ctx.runQuery(components.myComponent.hello.world); }, }); ``` Try it out: `npx convex run sayHi`. ## Key differences from regular Convex development[​](#key-differences-from-regular-convex-development "Direct link to Key differences from regular Convex development") Developing a component is similar to developing the rest of your Convex backend. This section is a guide to the key concepts and differences. ### The Component API[​](#the-component-api "Direct link to The Component API") When you access a component reference like `components.foo`, you're working with the `ComponentApi` type which has some key differences from the regular `api` object: * **Only public functions are accessible**: Internal functions are not exposed to the parent app. * **Functions become internal references**: The component's "public" queries, mutations, and actions are turned into references with "internal" visibility. They can be called with `ctx.runQuery`, `ctx.runMutation`, etc. but **not** directly accessible from clients via HTTP or WebSockets. See below for patterns to re-export functions from the component. * **IDs become strings**: Any `Id<"tableName">` arguments or return values become plain strings in the `ComponentApi`. See next section for details. Similar to regular Convex functions, you can call both public and internal functions via `npx convex run` and the Convex dashboard. ### `Id` types and validation[​](#id-types-and-validation "Direct link to id-types-and-validation") All `Id<"table_name">` types within a component become simple string types outside of the component (in the `ComponentApi` type). In addition, you cannot currently have a `v.id("table_name")` validator that represents a table in another component / app. Why? In Convex, a `v.id("table_name")` validator will check that an ID in an argument, return value, or database document matches the named table's format. Under the hood, this is currently encoded as a number assigned to each table in your schema. Within a component’s implementation, the same applies to the component's tables. However, a `v.id("users")` within the component is not the same as `v.id("users")` in another component or in the main app, as each "users" table can have a different table number representation. For this reason, all `Id` types in the `ComponentApi` become simple strings. ### Generated code[​](#generated-code "Direct link to Generated code") Each component has its own `_generated` directory in addition to the `convex/_generated` directory. They are similar, but its contents are specific to the component and its schema. In general, code outside of the component should not import from this directory with the exception of `_generated/component`. * `component.ts` is only generated for components, and contains the component's `ComponentApi` type. * `server.ts` contains function builders like `query` and `mutation` to define your component's API. It's important that you import these when defining your component's functions, and not from `convex/_generated/server`. See below for more information on function visibility. * `api.ts` contains the component's `api` and `internal` objects to reference the component's functions. It also includes the `components` object with references to its child components, if any. In general, no code outside of the component should import from this file. Instead, they should use their own `components` object which includes this component keyed by whatever name they chose to install it with. * `dataModel.ts` contains the types for the component's data model. Note that the `Id` and `Doc` types here are not useful outside of the component, since the API turns all ID types into strings at the boundary. ### Environment variables[​](#environment-variables "Direct link to Environment variables") The component's functions are isolated from the app's environment variables, so they cannot access `process.env`. Instead, you can pass environment variables as arguments to the component's functions. ``` return await ctx.runAction(components.sampleComponent.lib.translate, { baseUrl: process.env.BASE_URL, ...otherArgs, }); ``` See below for other strategies for static configuration. ### HTTP Actions[​](#http-actions "Direct link to HTTP Actions") A component cannot expose HTTP actions itself because the routes could conflict with the main app's routes. Similar to other functions (queries, mutations, and actions), a component can define HTTP action handlers which the app can choose to mount. There’s an example in the [Twilio component](https://github.com/get-convex/twilio/blob/0bdf7fd4ee7dd46d442be3693280564eea597b68/src/client/index.ts#L71). All HTTP actions need to be mounted in the main app’s `convex/http.ts` file. ### Authentication via ctx.auth[​](#authentication-via-ctxauth "Direct link to Authentication via ctx.auth") Within a component, `ctx.auth` is not available. You typically will do authentication in the app, then pass around identifiers like `userId` or other identifying information to the component. This explicit passing makes it clear what data flows between the app and component, making your component easier to understand and test. convex/myFunctions.ts TS ``` import { getAuthUserId } from "@convex-dev/auth/server"; export const someMutation = mutation({ args: {}, handler: async (ctx) => { const userId = await getAuthUserId(ctx); await ctx.runMutation(components.myComponent.foo.bar, { userId, ...otherArgs, }); }, }); ``` ### Function Handles[​](#function-handles "Direct link to Function Handles") Sometimes you want the app to call a component and the component should call back into the app. For example, when using the Migrations component, the app defines a function that modifies a document, and the component runs this function on every document. As another example, an app using the Twilio component gives it a function to run whenever the phone number receives a text message. These features are implemented using function handles. A function reference is something like api.foo.bar or `internal.foo.bar` or `components.counter.foo.bar`. Function references are restricted as described above (a component can only use references to its own functions or the public functions of its children). If you have a valid function reference, you can turn it into something that can be called from anywhere: ``` const handle = await createFunctionHandle(api.foo.bar); ``` This handle is a string. Since it’s a string, you can pass it between functions and even store it in a table. You would use `v.string()` in args/schema validators. When you want to use it, cast it back to FunctionHandle and use it as you would use a function reference. Note argument and return value validation still run at runtime, so don't worry about losing guarantees. ``` const handle = handleString as FunctionHandle<"mutation">; const result = await ctx.runMutation(handle, args); // or run it asynchronously via the scheduler: await ctx.scheduler.runAfter(0, handle, args); ``` [Here](https://github.com/get-convex/workpool/blob/aebe2db49fc3ec50ded6892ed27f464450b3d31e/src/component/worker.ts#L26-L28) is an example of using function handles in the [Workpool](https://www.convex.dev/components/workpool) component. ### Pagination[​](#pagination "Direct link to Pagination") The built-in `.paginate()` doesn't work in components, because of how Convex keeps track of reactive pagination. Therefore we recommend you use `paginator` from [`convex-helpers`](https://npmjs.com/package/convex-helpers) if you need pagination within your component. If you expose a pagination API that wants to be used with `usePaginatedQuery`, in a React context, use the `usePaginatedQuery` from `convex-helpers` instead of the default one from `convex/react`. It will have a fixed first page size until you hit “load more,” at which point the first page will grow if anything before the second page is added. [Here](https://github.com/get-convex/rag/blob/23fb22d593682e23d9134304e823f7532cbc7e67/src/component/chunks.ts#L437-L462) is an example of pagination in the [RAG](https://www.convex.dev/components/rag) component. ## Tips and best practices[​](#tips-and-best-practices "Direct link to Tips and best practices") ### Validation[​](#validation "Direct link to Validation") All public component functions should have argument and return validators. Otherwise, the argument and return values will be typed as `any`. Below is an example of using validators. ``` import schema from "./schema"; const messageDoc = schema.tables.messages.validator.extend({ _id: v.id("messages), _creationTime: v.number(), }); export const getLatestMessage = query({ args: {}, returns: v.nullable(messageDoc), handler: async (ctx) => { return await ctx.db.query("messages").order("desc).first(); }, }); ``` Find out more information about function validation [here](/functions/validation.md). ### Static configuration[​](#static-configuration "Direct link to Static configuration") A common pattern to track configuration in a component is to have a "globals" table with a single document that contains the configuration. You can then define functions to update this document from the CLI or from the app. To read the values, you can query them with `ctx.db.query("globals").first();`. ## Wrapping the component with client code[​](#wrapping-the-component-with-client-code "Direct link to Wrapping the component with client code") When building a component, sometimes you want to provide a simpler API than directly calling `ctx.runMutation(components.foo.bar, ...)`, add more type safety, or provide functionality that spans the component's boundary. You can hide calls to the component's functions behind a more ergonomic client API that runs within the app's environment and calls into the component. This section covers conventions and approaches to writing client code. These aren't hard and fast rules; choose the pattern that best fits your component's needs. Note: An important aspect of this pattern is that the code running in the app has access to `ctx.auth`, `process.env`, and other app-level resources. For many use-cases, this is important, such as running code to define migrations in the app, which are then run from the Migrations Component. On the other hand, apps that want really tight control over what code runs in their app may prefer to call the component's functions directly. ### Simple Function Wrappers[​](#simple-function-wrappers "Direct link to Simple Function Wrappers") The simplest approach is to define standalone functions that wrap calls to the component. This works well for straightforward operations and utilities. ``` import type { GenericActionCtx, GenericDataModel, GenericMutationCtx, } from "convex/server"; import type { ComponentApi } from "../component/_generated/component.js"; export async function callMyFunction( ctx: MutationCtx | ActionCtx, component: ComponentApi, args: ... ) { // You can create function handles, add shared utilities, // or do any processing that needs to run in the app's environment. const functionHandle = await createFunctionHandle(args.someFn); const someArg = process.env.SOME_ARG; await ctx.runMutation(component.call.fn, { ...args, someArg, functionHandle, }); } // Useful types for functions that only need certain capabilities. type MutationCtx = Pick, "runMutation">; type ActionCtx = Pick< GenericActionCtx, "runQuery" | "runMutation" | "runAction" >; ``` Note: we only use `ctx.runMutation`, so we can use `Pick` to select a type that only includes that function. This allows users to call it even if their `ctx` is not exactly the standard MutationCtx. It also means it can be called from an Action, as ActionCtx also includes `ctx.runMutation`. If your function also needs auth or storage, you can adjust what you `Pick`. ### Re-exporting component functions[​](#re-exporting-component-functions "Direct link to Re-exporting component functions") Sometimes you want to provide ready-made functions that apps can directly re-export to their public API. This is useful when you want to give apps the ability to expose your component's functionality to React clients or the public internet. The most straightforward way to do this is have the user define their own functions that call into the component. This allows the app to choose to add auth, rate limiting, etc. convex/counter.ts TS ``` export const add = mutation({ args: { value: v.number() }, returns: v.null(), handler: async (ctx, args) => { // The app can authenticate the user here if needed await ctx.runMutation(components.counter.add, args); }, }); ``` This is the recommended pattern, as it makes it clear to the user how the request is being authenticated. However, if you need to re-export a lot of functions, you can use the next pattern. #### Re-mounting an API[​](#re-mounting-an-api "Direct link to Re-mounting an API") Code in your `src/client/index.ts` can export these functions: ``` import type { Auth } from "convex/server"; // In your component's src/client/index.ts export function makeCounterAPI( component: ComponentApi, options: { // Important: provide a way for the user to authenticate these requests auth: (ctx: { auth: Auth }, operation: "read" | "write") => Promise; }, ) { return { add: mutation({ args: { value: v.number() }, handler: async (ctx, args) => { await options.auth(ctx, "write"); return await ctx.runMutation(component.public.add, args); }, }), get: query({ args: {}, handler: async (ctx) => { await options.auth(ctx, "read"); return await ctx.runQuery(component.public.get, {}); }, }), }; } ``` Then apps can mount these in their own API: ``` // In the app's convex/counter.ts import { makeCounterAPI } from "@convex-dev/counter"; import { components } from "./_generated/server.js"; export const { add, get } = makeCounterAPI(components.counter, { auth: async (ctx, operation) => { const userId = await getAuthUserId(ctx); // Check if the user has permission to perform the operation if (operation === "write" && !userId) { throw new Error("User not authenticated"); } return userId; }, }); ``` This pattern is also useful for components that need to provide functions with specific signatures for integration purposes. Here's a real-world [example](https://github.com/get-convex/prosemirror-sync/blob/91e19d5e5a2a272d44f3a31c9171e111dc98676c/src/client/index.ts#L171C4-L173C6) from the [ProseMirror component](https://www.convex.dev/components/prosemirror-sync) that exports ready-made functions. ### Class-Based Clients[​](#class-based-clients "Direct link to Class-Based Clients") For more complex components, a class-based client provides a stateful interface that can hold configuration and provide multiple methods. **Basic class pattern:** ``` import Foo from "@convex-dev/foo"; import { components } from "./_generated/server.js"; const foo = new Foo(components.foo, { maxShards: 10, }); ``` **With configuration options:** Classes typically accept the component reference as their first argument, with optional configuration as the second: ``` export class Foo { private apiKey: string; constructor( public component: ComponentApi, options?: { maxShards?: number; // Named after the environment variable it overrides, for clarity. FOO_AUTH_KEY?: string; }, ) { this.apiKey = options?.FOO_AUTH_KEY ?? process.env.FOO_AUTH_KEY!; } async count(ctx: GenericQueryCtx) { return await ctx.runQuery(this.component.public.count, { API_KEY: this.apiKey, }); } } ``` **Dynamic instantiation:** Note that clients don't need to be instantiated statically. If you need runtime values, you can create instances dynamically: ``` export const myQuery = query({ handler: async (ctx, args) => { const foo = new Foo(components.foo, { apiKey: args.customApiKey, }); await foo.count(ctx); }, }); ``` ## Building and publishing NPM package components[​](#building-and-publishing-npm-package-components "Direct link to Building and publishing NPM package components") ### Build process[​](#build-process "Direct link to Build process") While developing a component that will be bundled, the example app that installs and exercises it will import the bundled version of the component. This helps ensure that the code you are testing matches the code that will be published. However, that means `npx convex dev` cannot detect where the original source code is located for the component, and will not automatically generate the code for the component. When developing a component that will be bundled, you need to run a separate build process to generate the component's `_generated` directory. The component authoring template will automatically generate the code for the component when running `npm run dev`. You can see the setup in the [template's `package.json` scripts](https://github.com/get-convex/templates/blob/main/template-component/package.json). If you're setting up your own build process, you'll need to run the following commands with their own file watchers: 1. **Component codegen**: Generate code for the component itself ``` npx convex codegen --component-dir ./path/to/component ``` 2. **Build the package**: Build the NPM package ``` npm run build # Your build command (e.g., tsc, esbuild, etc.) ``` 3. **Example app codegen & deploy**: Generate code for the example app and deploy it app ``` npx convex dev --typecheck-components # optionally type-check the components ``` **Note on ordering:** The ideal ordering is: component codegen → build the package → example app `convex dev` runs. This is a recommended convention followed by the template to avoid builds racing with each other, but the key requirement is that the component must be built and available before the example app tries to import it. ### Entry points[​](#entry-points "Direct link to Entry points") When publishing a component on NPM, you will need to expose all the relevant entry points to be used in your project: * `@your/package` exports types, classes, and constants used to interact with the component from within their app's code. This is optional, but common. * `@your/package/convex.config.js` exposes the component's config. * `@your/package/_generated/component.js` exports the `ComponentApi` type, which describes the component's types from the point of view of app it's used in. * `@your/package/test` for utilities to use the component with `convex-test`. [The template’s package.json](https://github.com/get-convex/templates/blob/main/template-component/package.json) does this for you, but if you're setting up your own build process, you'll need to set this up in your package.json. ### Local package resolution for development[​](#local-package-resolution-for-development "Direct link to Local package resolution for development") When developing a component, you generally want to be importing the component's code in the same way that apps will import it, e.g. `import {} from "@your/package"`. To achieve this without having to install the package from NPM in the example app, follow the template's project structure: 1. In the root of the project, have the `package.json` with the package name matching the `@your/package` name. This causes imports for that name to resolve to the `package.json`’s `exports`. 2. In the `exports` section of the `package.json`, map the aforementioned entry points to the bundled files, generally in the `dist` directory. This means imports from the package name will resolve to the bundled files. 3. Have a single package.json file and node\_modules directory in the root of the project, so the example app will resolve to the package name by default. This will also avoid having multiple versions of `convex` referenced by the library vs. the example app. To add dependencies used only by the example app, add them as `devDependencies` in the `package.json`. ### Publishing to NPM[​](#publishing-to-npm "Direct link to Publishing to NPM") To publish a component on NPM, check out [PUBLISHING.md](https://github.com/get-convex/templates/blob/main/template-component/PUBLISHING.md). ## Testing[​](#testing "Direct link to Testing") ### Testing implementations[​](#testing-implementations "Direct link to Testing implementations") To test components, you can use the [`convex-test` library](/testing/convex-test.md). The main difference is that you must provide the schema and modules to the test instance. component/some.test.ts TS ``` import { test } from "vitest"; import { convexTest } from "convex-test"; import schema from "./schema.ts"; const modules = import.meta.glob("./**/*.ts"); export function initConvexTest() { const t = convexTest(schema, modules); return t; } test("Test something with a local component", async () => { const t = initConvexTest(); // Test like you would normally. await t.run(async (ctx) => { await ctx.db.insert("myComponentTable", { name: "test" }); }); }); ``` If your component has child components, see the [Testing components](/components/using.md#testing-components) section in the Using Components documentation. ### Testing the API and client code[​](#testing-the-api-and-client-code "Direct link to Testing the API and client code") To test the functions that are exported from the component to run in the app's environment, you can follow the same approach as in [Using Components](/components/using.md#testing-components) and test it from an app that uses the component. The template component includes an example app in part for this purpose: to exercise the component's bundled code as it will be used by apps installing it. ### Exporting test helpers[​](#exporting-test-helpers "Direct link to Exporting test helpers") Most components export testing helpers to make it easy to register the component with the test instance. Here is an example from the [template component’s `/test` entrypoint](https://github.com/get-convex/templates/blob/main/template-component/src/test.ts): ``` /// import type { TestConvex } from "convex-test"; import type { GenericSchema, SchemaDefinition } from "convex/server"; import schema from "./component/schema.js"; const modules = import.meta.glob("./component/**/*.ts"); /** * Register the component with the test convex instance. * @param t - The test convex instance, e.g. from calling `convexTest`. * @param name - The name of the component, as registered in convex.config.ts. */ export function register( t: TestConvex>, name: string = "sampleComponent", ) { t.registerComponent(name, schema, modules); } export default { register, schema, modules }; ``` For NPM packages, this is exposed as `@your/package/test` in the package's `package.json`: ``` { ... "exports": { ... "./test": "./src/test.ts", ... } } ``` --- # Source: https://docs.convex.dev/auth/authkit/auto-provision.md # Automatic AuthKit Configuration Convex can **create** AuthKit environments in a WorkOS account made on your behalf. By default WorkOS gives you only two environments, but giving each Convex dev deployment its own AuthKit environment is useful for isolating development user data and configuration changes between multiple developers or agents working in parallel. The Convex CLI will **configure** AuthKit environments, regardless of whether Convex or you created them, if the `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` environment variables are present in the build environment or the Convex deployment. While developing locally, Convex can write environment variables to `.env.local` to make setting up an AuthKit environment a breeze. ## Getting Started[​](#getting-started "Direct link to Getting Started") Choose AuthKit as your authentication option in the create convex tool. ``` npm create convex@latest ``` These templates include a `convex.json` which cause an AuthKit environment for this deployment to be created and configured. With just `npx convex dev` you'll get a WorkOS environment all hooked up! ### Going to production[​](#going-to-production "Direct link to Going to production") In the Convex dashboard settings for your production deployment, create an AuthKit environment in the WorkOS Authentication integration under settings, integrations. Copy these credentials to your hosting provider environment variables (in addition to other setup, like adding a production `CONVEX_DEPLOY_KEY`, setting the build command, and setting other framework-specific AuthKit environment variables). ### Preview deployments[​](#preview-deployments "Direct link to Preview deployments") In the Convex dashboard settings for any deployment in your project, create a new project-level AuthKit environment in the WorkOS Authentication integration under settings, integrations. Copy these credentials to your hosting provider environment variables (in addition to other setup, like adding a preview `CONVEX_DEPLOY_KEY`, setting the build command, and setting other framework-specific AuthKit environment variables). ## How it works[​](#how-it-works "Direct link to How it works") AuthKit provisioning and configuration is triggered by the presence of a `convex.json` file with an `authKit` section with a property corresponding to the type of code push: `dev`, `preview`, or `prod`. If this section is present, an AuthKit environment may be provisioned (dev only), local environment variables set (dev only), and configured (all code push types). ### Finding the AuthKit environment[​](#finding-the-authkit-environment "Direct link to Finding the AuthKit environment") The CLI looks for WorkOS credentials `WORKOS_CLIENT_ID` and `WORKOS_API_KEY` in the following order: 1. Environment variables in the build environment shell or `.env.local` file 2. Convex deployment environment variables In remote build environments (e.g. building a project in Vercel, Netlify, Cloudflare) if these two environment variables are not found, the build will fail. During local dev, credentials are next fetched from the Convex Cloud API for a new or existing AuthKit environment. A link to this deployment in the WorkOS dashboard can be found in the Convex dashboard under the WorkOS integration. ### Configuring the AuthKit environment[​](#configuring-the-authkit-environment "Direct link to Configuring the AuthKit environment") Once credentials are found, the `WORKOS_API_KEY` is used to configure the environment based on the `configure` section of the relevant `authKit` object. This sets things like an environment's [redirect URIs](https://workos.com/docs/sso/redirect-uris), [allowed CORS origins](https://workos.com/docs/authkit/client-only). ### Setting local environment variables[​](#setting-local-environment-variables "Direct link to Setting local environment variables") For dev deployments only, environment variables are written to `.env.local` based on the `localEnvVars` section of the relevant `authKit` config. ## Project-level vs deployment level AuthKit environments[​](#project-level-vs-deployment-level-authkit-environments "Direct link to Project-level vs deployment level AuthKit environments") In hosting providers with remote build pipelines like Vercel, it's difficult to set environment variables like `WORKOS_API_KEY` at build time in a way that's available to server-side code like Next.js middleware. This makes it necessary set the `WORKOS_*` environment variables in advance for preview and production deployments built on these platforms. After creating the WorkOS AuthKit environments for production and preview deployments in the dashboard, copy relevant environment variables like `WORKOS_CLIENT_ID`, `WORKOS_API_KEY`, `WORKOS_REDIRECT_URI`, and `WORKOS_COOKIE_PASSWORD` to the preview and production environment variables in your hosting provider. Deployment-specific AuthKit environments can be created for any deployment are difficult set up automatically so shared project-level environments are generally a better fit. In the `authKit` section of `convex.json`, `localEnvVars` `automate setting up dev environments by automatically setting the right environment variables in .env.local and automatically configuring the environment with a `redirectUri\`. Environments for hosting providers in build environments like Vercel (production and preview deploys) can be configured at build time, but the environment variables for these build environments must be set manually in the build settings. ## Recommended Configuration[​](#recommended-configuration "Direct link to Recommended Configuration") Here's a common setup for a project where production and preview deploys are deployed to from Vercel. Check your hosting provider's docs to substitute the right environment variables, and check the guide for using AuthKit with your framework of choice to customize this example. convex.json ``` { "authKit": { "dev": { "configure": { "redirectUris": ["http://localhost:3000/callback"], "appHomepageUrl": "http://localhost:3000", "corsOrigins": ["http://localhost:3000"] }, "localEnvVars": { "WORKOS_CLIENT_ID": "${authEnv.WORKOS_CLIENT_ID}", "WORKOS_API_KEY": "${authEnv.WORKOS_API_KEY}", "NEXT_PUBLIC_WORKOS_REDIRECT_URI": "http://localhost:3000/callback" } }, "preview": { "configure": { "redirectUris": ["https://${buildEnv.VERCEL_BRANCH_URL}/callback"], "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}", "corsOrigins": ["https://${buildEnv.VERCEL_BRANCH_URL}"] } }, "prod": { "environmentType": "production", "configure": { "redirectUris": [ "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}/callback" ], "appHomepageUrl": "https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}", "corsOrigins": ["https://${buildEnv.VERCEL_PROJECT_PRODUCTION_URL}"] } } } } ``` Additionally, for local dev in **Next.js** and **TanStack Start**, Convex automatically generates a `WORKOS_COOKIE_PASSWORD` if it's not already in `.env.local`. --- # Source: https://docs.convex.dev/database/backup-restore.md Convex supports Backup & Restore of data via the [dashboard](https://dashboard.convex.dev/deployment/settings/backups). ![Backups Page](/assets/images/backups-7e17da1541fc3eb26194a96ab33414ea.png) # Backups A backup is a consistent snapshot of your table data made at the time of your request. Backups can be configured to include file storage. Take a manual backup by pressing the "Backup Now" button. This may take a few seconds to a few hours, depending on how much data is in your deployment. Manual backups are stored for 7 days. You can download or delete backups via this page. Deployment configuration and other data (code, environment variables, scheduled functions, etc.) will not be included. ### Periodic Backups[​](#periodic-backups "Direct link to Periodic Backups") Schedule a periodic daily or weekly backup by checking the "Backup automatically" box. You can select what time of day / day of week to have the backup occur and whether to include file storage or not. Daily backups are stored for 7 days. Weekly backups are stored for 14 days. Periodic backups require a Convex Pro plan. Periodic backups require a Convex Pro plan. [Learn more](https://convex.dev/pricing) about our plans or [upgrade](https://dashboard.convex.dev/team/settings/billing). ### Restoring from backup[​](#restoring-from-backup "Direct link to Restoring from backup") Restore from a backup by selecting "Restore" from the submenu of an individual backup. You can restore from backups in the same deployment or from other deployments on the same team by using the deployment selector on the backups page. Restores may take a few seconds to a few hours depending on how much data is in your backup. Note that restoring is a destructive operation that wipes your existing data and replaces it with that from the backup. It's recommended that you generate an additional backup before doing a restore. Existing files in the deployment will not be deleted when restoring from a backup, but any files in the backup that do not currently exist in the deployment will be uploaded to the deployment. ### Restoring in an emergency[​](#restoring-in-an-emergency "Direct link to Restoring in an emergency") If your production deployment ends up in a bad state, you may want to consider doing a restore to return to a good state. Note that getting your data to a good state may not be enough. Consider whether you may need each of the following actions. Depending on the nature of your emergency, these may be required. * Take an additional backup prior to restore, since restores are destructive * Do a restore from a good backup - to restore data * Use `npx convex dev` to push a known version of good code. * Use `npx convex env` or the dashboard to restore to a good set of env vars * Use the dashboard to make any manual fixes to the database for your app. * Write mutations to make required (more programmatic) manual fixes to the database for your app. # Downloading a backup You can download your manual and periodic backups from the dashboard via the download button in the menu. Alternatively, you can generate an export in the same format with the [command line](/cli.md#export-data-to-a-file): ``` npx convex export --path ~/Downloads ``` The backup comes as a generated a ZIP file with all documents in all Convex tables in your deployment. The ZIP file's name has the format `snapshot_{ts}.zip` where `ts` is a UNIX timestamp of the snapshot in nanoseconds. The export ZIP file contains documents for each table at `/documents.jsonl`, with one document per line. Exported ZIP files that include [file storage](/file-storage.md) will contain storage data in a `_storage` folder, with metadata like IDs and checksums in `_storage/documents.jsonl` and each file as `_storage/`. ### Using the downloaded backup.[​](#using-the-downloaded-backup "Direct link to Using the downloaded backup.") Downloaded ZIP files can be imported into the same deployment or a different deployment [with the CLI](/database/import-export/import.md#restore-data-from-a-backup-zip-file). ## FAQ[​](#faq "Direct link to FAQ") ### Are there any limitations?[​](#are-there-any-limitations "Direct link to Are there any limitations?") Each backup is accessible for up to 7 days. On the Free/Starter plan, up to two backups can stored per deployment at a time. Deployments on Convex Professional plan can have many backups with standard usage based pricing. ### How are they priced?[​](#how-are-they-priced "Direct link to How are they priced?") Backups uses database bandwidth to read all documents, and file bandwidth to include user files. The generation and storage of the backup itself is billed with the same bandwidth and storage pricing as user file storage. You can observe this bandwidth and storage cost in the [usage dashboard](https://dashboard.convex.dev/team/settings/usage). Check the [limits docs](/production/state/limits.md#database) for pricing details. ### What does the backup not contain?[​](#what-does-the-backup-not-contain "Direct link to What does the backup not contain?") The backup only contains the documents for your tables and files in file storage. In particular it lacks: 1. Your deployment's code and configuration. Convex functions, crons.ts, auth.config.js, schema.ts, etc. are configured in your source code. 2. Pending scheduled functions. You can access pending scheduled functions in the [`_scheduled_functions`](/database/advanced/system-tables.md) system table. 3. Environment variables. Environment variables can be copied from Settings in the Convex dashboard. --- # Source: https://docs.convex.dev/understanding/best-practices.md # Best Practices This is a list of best practices and common anti-patterns around using Convex. We recommend going through this list before broadly releasing your app to production. You may choose to try using all of these best practices from the start, or you may wait until you've gotten major parts of your app working before going through and adopting the best practices here. ## Await all Promises[​](#await-all-promises "Direct link to Await all Promises") ### Why?[​](#why "Direct link to Why?") Convex functions use async / await. If you don't await all your promises (e.g. `await ctx.scheduler.runAfter`, `await ctx.db.patch`), you may run into unexpected behavior (e.g. failing to schedule a function) or miss handling errors. ### How?[​](#how "Direct link to How?") We recommend the [no-floating-promises](https://typescript-eslint.io/rules/no-floating-promises/) rule of typescript-eslint. ## Avoid `.filter` on database queries[​](#avoid-filter-on-database-queries "Direct link to avoid-filter-on-database-queries") ### Why?[​](#why-1 "Direct link to Why?") Filtering in code instead of using the `.filter` syntax has the same performance, and is generally easier code to write. Conditions in `.withIndex` or `.withSearchIndex` are more efficient than `.filter` or filtering in code, so almost all uses of `.filter` should either be replaced with a `.withIndex` or `.withSearchIndex` condition, or written as TypeScript code. Read through the [indexes documentation](/database/reading-data/indexes/indexes-and-query-perf.md) for an overview of how to define indexes and how they work. ### Examples[​](#examples "Direct link to Examples") convex/messages.ts TS ``` // ❌ const tomsMessages = ctx.db .query("messages") .filter((q) => q.eq(q.field("author"), "Tom")) .collect(); // ✅ // Option 1: Use an index const tomsMessages = await ctx.db .query("messages") .withIndex("by_author", (q) => q.eq("author", "Tom")) .collect(); // Option 2: Filter in code const allMessages = await ctx.db.query("messages").collect(); const tomsMessages = allMessages.filter((m) => m.author === "Tom"); ``` ### How?[​](#how-1 "Direct link to How?") Search for `.filter` in your Convex codebase — a regex like `\.filter\(\(?q` will probably find all the ones on database queries. Decide whether they should be replaced with a `.withIndex` condition — per [this section](/understanding/best-practices/.md#only-use-collect-with-a-small-number-of-results), if you are filtering over a large (1000+) or potentially unbounded number of documents, you should use an index. If not using a `.withIndex` / `.withSearchIndex` condition, consider replacing them with a filter in code for more readability and flexibility. See [this article](https://stack.convex.dev/complex-filters-in-convex) for more strategies for filtering. ### Exceptions[​](#exceptions "Direct link to Exceptions") Using `.filter` on a paginated query (`.paginate`) has advantages over filtering in code. The paginated query will return the number of documents requested, including the `.filter` condition, so filtering in code afterwards can result in a smaller page or even an empty page. Using `.withIndex` on a paginated query will still be more efficient than a `.filter`. ## Only use `.collect` with a small number of results[​](#only-use-collect-with-a-small-number-of-results "Direct link to only-use-collect-with-a-small-number-of-results") ### Why?[​](#why-2 "Direct link to Why?") All results returned from `.collect` count towards database bandwidth (even ones filtered out by `.filter`). It also means that if any document in the result changes, the query will re-run or the mutation will hit a conflict. If there's a chance the number of results is large (say 1000+ documents), you should use an index to filter the results further before calling `.collect`, or find some other way to avoid loading all the documents such as using pagination, denormalizing data, or changing the product feature. ### Example[​](#example "Direct link to Example") **Using an index:** convex/movies.ts TS ``` // ❌ -- potentially unbounded const allMovies = await ctx.db.query("movies").collect(); const moviesByDirector = allMovies.filter( (m) => m.director === "Steven Spielberg", ); // ✅ -- small number of results, so `collect` is fine const moviesByDirector = await ctx.db .query("movies") .withIndex("by_director", (q) => q.eq("director", "Steven Spielberg")) .collect(); ``` **Using pagination:** convex/movies.ts TS ``` // ❌ -- potentially unbounded const watchedMovies = await ctx.db .query("watchedMovies") .withIndex("by_user", (q) => q.eq("user", "Tom")) .collect(); // ✅ -- using pagination, showing recently watched movies first const watchedMovies = await ctx.db .query("watchedMovies") .withIndex("by_user", (q) => q.eq("user", "Tom")) .order("desc") .paginate(paginationOptions); ``` **Using a limit or denormalizing:** convex/movies.ts TS ``` // ❌ -- potentially unbounded const watchedMovies = await ctx.db .query("watchedMovies") .withIndex("by_user", (q) => q.eq("user", "Tom")) .collect(); const numberOfWatchedMovies = watchedMovies.length; // ✅ -- Show "99+" instead of needing to load all documents const watchedMovies = await ctx.db .query("watchedMovies") .withIndex("by_user", (q) => q.eq("user", "Tom")) .take(100); const numberOfWatchedMovies = watchedMovies.length === 100 ? "99+" : watchedMovies.length.toString(); // ✅ -- Denormalize the number of watched movies in a separate table const watchedMoviesCount = await ctx.db .query("watchedMoviesCount") .withIndex("by_user", (q) => q.eq("user", "Tom")) .unique(); ``` ### How?[​](#how-2 "Direct link to How?") Search for `.collect` in your Convex codebase (a regex like `\.collect\(` will probably find these). And think through whether the number of results is small. This function health page in the dashboard can also help surface these. The [aggregate component](https://www.npmjs.com/package/@convex-dev/aggregate) or [database triggers](https://stack.convex.dev/triggers) can be helpful patterns for denormalizing data. ### Exceptions[​](#exceptions-1 "Direct link to Exceptions") If you're doing something that requires loading a large number of documents (e.g. performing a migration, making a summary), you may want to use an action to load them in batches via separate queries / mutations. ## Check for redundant indexes[​](#check-for-redundant-indexes "Direct link to Check for redundant indexes") ### Why?[​](#why-3 "Direct link to Why?") Indexes like `by_foo` and `by_foo_and_bar` are usually redundant (you only need `by_foo_and_bar`). Reducing the number of indexes saves on database storage and reduces the overhead of writing to the table. convex/teams.ts TS ``` // ❌ const allTeamMembers = await ctx.db .query("teamMembers") .withIndex("by_team", (q) => q.eq("team", teamId)) .collect(); const currentUserId = /* get current user id from `ctx.auth` */ const currentTeamMember = await ctx.db .query("teamMembers") .withIndex("by_team_and_user", (q) => q.eq("team", teamId).eq("user", currentUserId), ) .unique(); // ✅ // Just don't include a condition on `user` when querying for results on `team` const allTeamMembers = await ctx.db .query("teamMembers") .withIndex("by_team_and_user", (q) => q.eq("team", teamId)) .collect(); const currentUserId = /* get current user id from `ctx.auth` */ const currentTeamMember = await ctx.db .query("teamMembers") .withIndex("by_team_and_user", (q) => q.eq("team", teamId).eq("user", currentUserId), ) .unique(); ``` ### How?[​](#how-3 "Direct link to How?") Look through your indexes, either in your `schema.ts` file or in the dashboard, and look for any indexes where one is a prefix of another. ### Exceptions[​](#exceptions-2 "Direct link to Exceptions") `.index("by_foo", ["foo"])` is really an index on the properties `foo` and `_creationTime`, while `.index("by_foo_and_bar", ["foo", "bar"])` is an index on the properties `foo`, `bar`, and `_creationTime`. If you have queries that need to be sorted by `foo` and then `_creationTime`, then you need both indexes. For example, `.index("by_channel", ["channel"])` on a table of messages can be used to query for the most recent messages in a channel, but `.index("by_channel_and_author", ["channel", "author"])` could not be used for this since it would first sort the messages by `author`. ## Use argument validators for all public functions[​](#use-argument-validators-for-all-public-functions "Direct link to Use argument validators for all public functions") ### Why?[​](#why-4 "Direct link to Why?") Public functions can be called by anyone, including potentially malicious attackers trying to break your app. [Argument validators](/functions/validation.md) (as well as return value validators) help ensure you're getting the traffic you expect. ### Example[​](#example-1 "Direct link to Example") convex/movies.ts TS ``` // ❌ -- `id` and `update` are not validated, so a client could pass // any Convex value (the type at runtime could mismatch the // TypeScript type). In particular, `update` could contain // fields other than `title` and `director`. export const updateMovie = mutation({ handler: async ( ctx, { id, update, }: { id: Id<"movies">; update: Pick, "title" | "director">; }, ) => { await ctx.db.patch("movies", id, update); }, }); // ✅ -- This can only be called with an ID from the movies table, // and an `update` object with only the `title`/`director` fields export const updateMovie = mutation({ args: { id: v.id("movies"), update: v.object({ title: v.string(), director: v.string(), }), }, handler: async (ctx, { id, update }) => { await ctx.db.patch("movies", id, update); }, }); ``` ### How?[​](#how-4 "Direct link to How?") Search for `query`, `mutation`, and `action` in your Convex codebase, and ensure that all of them have argument validators (and optionally return value validators). You can also check automatically that your functions have argument validators with the [`@convex-dev/require-argument-validators` ESLint rule](/eslint.md#require-argument-validators). If you use HTTP actions, you may want to use an argument validation library like [Zod](https://zod.dev) to validate that the HTTP request is the shape you expect. ## Use some form of access control for all public functions[​](#use-some-form-of-access-control-for-all-public-functions "Direct link to Use some form of access control for all public functions") ### Why?[​](#why-5 "Direct link to Why?") Public functions can be called by anyone, including potentially malicious attackers trying to break your app. If portions of your app should only be accessible when the user is signed in, make sure all these Convex functions check that `ctx.auth.getUserIdentity()` is set. You may also have specific checks, like only loading messages that were sent to or from the current user, which you'll want to apply in every relevant public function. Favoring more granular functions like `setTeamOwner` over `updateTeam` allows more granular checks for which users can do what. Access control checks should either use `ctx.auth.getUserIdentity()` or a function argument that is unguessable (e.g. a UUID, or a Convex ID, provided that this ID is never exposed to any client but the one user). In particular, don't use a function argument which could be spoofed (e.g. email) for access control checks. ### Example[​](#example-2 "Direct link to Example") convex/teams.ts TS ``` // ❌ -- no checks! anyone can update any team if they get the ID export const updateTeam = mutation({ args: { id: v.id("teams"), update: v.object({ name: v.optional(v.string()), owner: v.optional(v.id("users")), }), }, handler: async (ctx, { id, update }) => { await ctx.db.patch("teams", id, update); }, }); // ❌ -- checks access, but uses `email` which could be spoofed export const updateTeam = mutation({ args: { id: v.id("teams"), update: v.object({ name: v.optional(v.string()), owner: v.optional(v.id("users")), }), email: v.string(), }, handler: async (ctx, { id, update, email }) => { const teamMembers = /* load team members */ if (!teamMembers.some((m) => m.email === email)) { throw new Error("Unauthorized"); } await ctx.db.patch("teams", id, update); }, }); // ✅ -- checks access, and uses `ctx.auth`, which cannot be spoofed export const updateTeam = mutation({ args: { id: v.id("teams"), update: v.object({ name: v.optional(v.string()), owner: v.optional(v.id("users")), }), }, handler: async (ctx, { id, update }) => { const user = await ctx.auth.getUserIdentity(); if (user === null) { throw new Error("Unauthorized"); } const isTeamMember = /* check if user is a member of the team */ if (!isTeamMember) { throw new Error("Unauthorized"); } await ctx.db.patch("teams", id, update); }, }); // ✅ -- separate functions which have different access control export const setTeamOwner = mutation({ args: { id: v.id("teams"), owner: v.id("users"), }, handler: async (ctx, { id, owner }) => { const user = await ctx.auth.getUserIdentity(); if (user === null) { throw new Error("Unauthorized"); } const isTeamOwner = /* check if user is the owner of the team */ if (!isTeamOwner) { throw new Error("Unauthorized"); } await ctx.db.patch("teams", id, { owner: owner }); }, }); export const setTeamName = mutation({ args: { id: v.id("teams"), name: v.string(), }, handler: async (ctx, { id, name }) => { const user = await ctx.auth.getUserIdentity(); if (user === null) { throw new Error("Unauthorized"); } const isTeamMember = /* check if user is a member of the team */ if (!isTeamMember) { throw new Error("Unauthorized"); } await ctx.db.patch("teams", id, { name: name }); }, }); ``` ### How?[​](#how-5 "Direct link to How?") Search for `query`, `mutation`, `action`, and `httpAction` in your Convex codebase, and ensure that all of them have some form of access control. [Custom functions](https://github.com/get-convex/convex-helpers/blob/main/packages/convex-helpers/README.md#custom-functions) like [`authenticatedQuery`](https://stack.convex.dev/custom-functions#modifying-the-ctx-argument-to-a-server-function-for-user-auth) can be helpful. Some apps use Row Level Security (RLS) to check access to each document automatically whenever it's loaded, as described in [this article](https://stack.convex.dev/row-level-security). Alternatively, you can check access in each Convex function instead of checking access for each document. Helper functions for common checks and common operations can also be useful -- e.g. `isTeamMember`, `isTeamAdmin`, `loadTeam` (which throws if the current user does not have access to the team). ## Only schedule and `ctx.run*` internal functions[​](#only-schedule-and-ctxrun-internal-functions "Direct link to only-schedule-and-ctxrun-internal-functions") ### Why?[​](#why-6 "Direct link to Why?") Public functions can be called by anyone, including potentially malicious attackers trying to break your app, and should be carefully audited to ensure they can't be used maliciously. Functions that are only called within Convex can be marked as internal, and relax these checks since Convex will ensure that internal functions can only be called within Convex. ### How?[​](#how-6 "Direct link to How?") Search for `ctx.runQuery`, `ctx.runMutation`, and `ctx.runAction` in your Convex codebase. Also search for `ctx.scheduler` and check the `crons.ts` file. Ensure all of these use `internal.foo.bar` functions instead of `api.foo.bar` functions. If you have code you want to share between a public Convex function and an internal Convex function, create a helper function that can be called from both. The public function will likely have additional access control checks. Alternatively, make sure that `api` from `_generated/api.ts` is never used in your Convex functions directory. ### Examples[​](#examples-1 "Direct link to Examples") convex/teams.ts TS ``` // ❌ -- using `api` export const sendMessage = mutation({ args: { body: v.string(), author: v.string(), }, handler: async (ctx, { body, author }) => { // add message to the database }, }); // crons.ts crons.daily( "send daily reminder", { hourUTC: 17, minuteUTC: 30 }, api.messages.sendMessage, { author: "System", body: "Share your daily update!" }, ); // ✅ Using `internal` import { MutationCtx } from './_generated/server'; async function sendMessageHelper( ctx: MutationCtx, args: { body: string; author: string }, ) { // add message to the database } export const sendMessage = mutation({ args: { body: v.string(), }, handler: async (ctx, { body }) => { const user = await ctx.auth.getUserIdentity(); if (user === null) { throw new Error("Unauthorized"); } await sendMessageHelper(ctx, { body, author: user.name ?? "Anonymous" }); }, }); export const sendInternalMessage = internalMutation({ args: { body: v.string(), // don't need to worry about `author` being spoofed since this is an internal function author: v.string(), }, handler: async (ctx, { body, author }) => { await sendMessageHelper(ctx, { body, author }); }, }); // crons.ts crons.daily( "send daily reminder", { hourUTC: 17, minuteUTC: 30 }, internal.messages.sendInternalMessage, { author: "System", body: "Share your daily update!" }, ); ``` ## Use helper functions to write shared code[​](#use-helper-functions-to-write-shared-code "Direct link to Use helper functions to write shared code") ### Why?[​](#why-7 "Direct link to Why?") Most logic should be written as plain TypeScript functions, with the `query`, `mutation`, and `action` wrapper functions being a thin wrapper around one or more helper function. Concretely, most of your code should live in a directory like `convex/model`, and your public API, which is defined with `query`, `mutation`, and `action`, should have very short functions that mostly just call into `convex/model`. Organizing your code this way makes several of the refactors mentioned in this list easier to do. See the [TypeScript page](/understanding/best-practices/typescript.md) for useful types. ### Example[​](#example-3 "Direct link to Example") **❌** This example overuses `ctx.runQuery` and `ctx.runMutation`, which is discussed more in the [Avoid sequential `ctx.runMutation` / `ctx.runQuery` from actions](/understanding/best-practices/.md#avoid-sequential-ctxrunmutation--ctxrunquery-calls-from-actions) section. convex/users.ts TS ``` export const getCurrentUser = query({ args: {}, handler: async (ctx) => { const userIdentity = await ctx.auth.getUserIdentity(); if (userIdentity === null) { throw new Error("Unauthorized"); } const user = /* query ctx.db to load the user */ const userSettings = /* load other documents related to the user */ return { user, settings: userSettings }; }, }); ``` convex/conversations.ts TS ``` export const listMessages = query({ args: { conversationId: v.id("conversations"), }, handler: async (ctx, { conversationId }) => { const user = await ctx.runQuery(api.users.getCurrentUser); const conversation = await ctx.db.get("conversations", conversationId); if (conversation === null || !conversation.members.includes(user._id)) { throw new Error("Unauthorized"); } const messages = /* query ctx.db to load the messages */ return messages; }, }); export const summarizeConversation = action({ args: { conversationId: v.id("conversations"), }, handler: async (ctx, { conversationId }) => { const messages = await ctx.runQuery(api.conversations.listMessages, { conversationId, }); const summary = /* call some external service to summarize the conversation */ await ctx.runMutation(api.conversations.addSummary, { conversationId, summary, }); }, }); ``` **✅** Most of the code here is now in the `convex/model` directory. The API for this application is in `convex/conversations.ts`, which contains very little code itself. convex/model/users.ts TS ``` import { QueryCtx } from '../_generated/server'; export async function getCurrentUser(ctx: QueryCtx) { const userIdentity = await ctx.auth.getUserIdentity(); if (userIdentity === null) { throw new Error("Unauthorized"); } const user = /* query ctx.db to load the user */ const userSettings = /* load other documents related to the user */ return { user, settings: userSettings }; } ``` convex/model/conversations.ts TS ``` import { QueryCtx, MutationCtx } from '../_generated/server'; import * as Users from './users'; export async function ensureHasAccess( ctx: QueryCtx, { conversationId }: { conversationId: Id<"conversations"> }, ) { const user = await Users.getCurrentUser(ctx); const conversation = await ctx.db.get("conversations", conversationId); if (conversation === null || !conversation.members.includes(user._id)) { throw new Error("Unauthorized"); } return conversation; } export async function listMessages( ctx: QueryCtx, { conversationId }: { conversationId: Id<"conversations"> }, ) { await ensureHasAccess(ctx, { conversationId }); const messages = /* query ctx.db to load the messages */ return messages; } export async function addSummary( ctx: MutationCtx, { conversationId, summary, }: { conversationId: Id<"conversations">; summary: string }, ) { await ensureHasAccess(ctx, { conversationId }); await ctx.db.patch("conversations", conversationId, { summary }); } export async function generateSummary( messages: Doc<"messages">[], conversationId: Id<"conversations">, ) { const summary = /* call some external service to summarize the conversation */ return summary; } ``` convex/conversations.ts TS ``` import * as Conversations from './model/conversations'; export const addSummary = internalMutation({ args: { conversationId: v.id("conversations"), summary: v.string(), }, handler: async (ctx, { conversationId, summary }) => { await Conversations.addSummary(ctx, { conversationId, summary }); }, }); export const listMessages = internalQuery({ args: { conversationId: v.id("conversations"), }, handler: async (ctx, { conversationId }) => { return Conversations.listMessages(ctx, { conversationId }); }, }); export const summarizeConversation = action({ args: { conversationId: v.id("conversations"), }, handler: async (ctx, { conversationId }) => { const messages = await ctx.runQuery(internal.conversations.listMessages, { conversationId, }); const summary = await Conversations.generateSummary( messages, conversationId, ); await ctx.runMutation(internal.conversations.addSummary, { conversationId, summary, }); }, }); ``` ## Use `runAction` only when using a different runtime[​](#use-runaction-only-when-using-a-different-runtime "Direct link to use-runaction-only-when-using-a-different-runtime") ### Why?[​](#why-8 "Direct link to Why?") Calling `runAction` has more overhead than calling a plain TypeScript function. It counts as an extra function call with its own memory and CPU usage, while the parent action is doing nothing except waiting for the result. Therefore, `runAction` should almost always be replaced with calling a plain TypeScript function. However, if you want to call code that requires Node.js from a function in the Convex runtime (e.g. using a library that requires Node.js), then you can use `runAction` to call the Node.js code. ### Example[​](#example-4 "Direct link to Example") convex/scrape.ts TS ``` // ❌ -- using `runAction` export const scrapeWebsite = action({ args: { siteMapUrl: v.string(), }, handler: async (ctx, { siteMapUrl }) => { const siteMap = await fetch(siteMapUrl); const pages = /* parse the site map */ await Promise.all( pages.map((page) => ctx.runAction(internal.scrape.scrapeSinglePage, { url: page }), ), ); }, }); ``` convex/model/scrape.ts TS ``` import { ActionCtx } from '../_generated/server'; // ✅ -- using a plain TypeScript function export async function scrapeSinglePage( ctx: ActionCtx, { url }: { url: string }, ) { const page = await fetch(url); const text = /* parse the page */ await ctx.runMutation(internal.scrape.addPage, { url, text }); } ``` convex/scrape.ts TS ``` import * as Scrape from './model/scrape'; export const scrapeWebsite = action({ args: { siteMapUrl: v.string(), }, handler: async (ctx, { siteMapUrl }) => { const siteMap = await fetch(siteMapUrl); const pages = /* parse the site map */ await Promise.all( pages.map((page) => Scrape.scrapeSinglePage(ctx, { url: page })), ); }, }); ``` ### How?[​](#how-7 "Direct link to How?") Search for `runAction` in your Convex codebase, and see if the function it calls uses the same runtime as the parent function. If so, replace the `runAction` with a plain TypeScript function. You may want to structure your functions so the Node.js functions are in a separate directory so it's easier to spot these. ## Avoid sequential `ctx.runMutation` / `ctx.runQuery` calls from actions[​](#avoid-sequential-ctxrunmutation--ctxrunquery-calls-from-actions "Direct link to avoid-sequential-ctxrunmutation--ctxrunquery-calls-from-actions") ### Why?[​](#why-9 "Direct link to Why?") Each `ctx.runMutation` or `ctx.runQuery` runs in its own transaction, which means if they're called separately, they may not be consistent with each other. If instead we call a single `ctx.runQuery` or `ctx.runMutation`, we're guaranteed that the results we get are consistent. ### How?[​](#how-8 "Direct link to How?") Audit your calls to `ctx.runQuery` and `ctx.runMutation` in actions. If you see multiple in a row with no other code between them, replace them with a single `ctx.runQuery` or `ctx.runMutation` that handles both things. Refactoring your code to use helper functions will make this easier. ### Example: Queries[​](#example-queries "Direct link to Example: Queries") convex/teams.ts TS ``` // ❌ -- this assertion could fail if the team changed between running the two queries const team = await ctx.runQuery(internal.teams.getTeam, { teamId }); const teamOwner = await ctx.runQuery(internal.teams.getTeamOwner, { teamId }); assert(team.owner === teamOwner._id); ``` convex/teams.ts TS ``` import * as Teams from './model/teams'; import * as Users from './model/users'; export const sendBillingReminder = action({ args: { teamId: v.id("teams"), }, handler: async (ctx, { teamId }) => { // ✅ -- this will always pass const teamAndOwner = await ctx.runQuery(internal.teams.getTeamAndOwner, { teamId, }); assert(teamAndOwner.team.owner === teamAndOwner.owner._id); // send a billing reminder email to the owner }, }); export const getTeamAndOwner = internalQuery({ args: { teamId: v.id("teams"), }, handler: async (ctx, { teamId }) => { const team = await Teams.load(ctx, { teamId }); const owner = await Users.load(ctx, { userId: team.owner }); return { team, owner }; }, }); ``` ### Example: Loops[​](#example-loops "Direct link to Example: Loops") convex/teams.ts TS ``` import * as Users from './model/users'; export const importTeams = action({ args: { teamId: v.id("teams"), }, handler: async (ctx, { teamId }) => { // Fetch team members from an external API const teamMembers = await fetchTeamMemberData(teamId); // ❌ This will run a separate mutation for inserting each user, // which means you lose transaction guarantees like atomicity. for (const member of teamMembers) { await ctx.runMutation(internal.teams.insertUser, member); } }, }); export const insertUser = internalMutation({ args: { name: v.string(), email: v.string() }, handler: async (ctx, { name, email }) => { await Users.insert(ctx, { name, email }); }, }); ``` convex/teams.ts TS ``` import * as Users from './model/users'; export const importTeams = action({ args: { teamId: v.id("teams"), }, handler: async (ctx, { teamId }) => { // Fetch team members from an external API const teamMembers = await fetchTeamMemberData(teamId); // ✅ This action runs a single mutation that inserts all users in the same transaction. await ctx.runMutation(internal.teams.insertUsers, teamMembers); }, }); export const insertUsers = internalMutation({ args: { users: v.array(v.object({ name: v.string(), email: v.string() })) }, handler: async (ctx, { users }) => { for (const { name, email } of users) { await Users.insert(ctx, { name, email }); } }, }); ``` ### Exceptions[​](#exceptions-3 "Direct link to Exceptions") If you're intentionally trying to process more data than fits in a single transaction, like running a migration or aggregating data, then it makes sense to have multiple sequential `ctx.runMutation` / `ctx.runQuery` calls. Multiple `ctx.runQuery` / `ctx.runMutation` calls are often necessary because the action does a side effect in between them. For example, reading some data, feeding it to an external service, and then writing the result back to the database. ## Use `ctx.runQuery` and `ctx.runMutation` sparingly in queries and mutations[​](#use-ctxrunquery-and-ctxrunmutation-sparingly-in-queries-and-mutations "Direct link to use-ctxrunquery-and-ctxrunmutation-sparingly-in-queries-and-mutations") ### Why?[​](#why-10 "Direct link to Why?") While these queries and mutations run in the same transaction, and will give consistent results, they have extra overhead compared to plain TypeScript functions. Wanting a TypeScript helper function is much more common than needing `ctx.runQuery` or `ctx.runMutation`. ### How?[​](#how-9 "Direct link to How?") Audit your calls to `ctx.runQuery` and `ctx.runMutation` in queries and mutations. Unless one of the exceptions below applies, replace them with a plain TypeScript function. ### Exceptions[​](#exceptions-4 "Direct link to Exceptions") * If you're using components, these require `ctx.runQuery` or `ctx.runMutation`. * If you want partial rollback on an error, you will want `ctx.runMutation` instead of a plain TypeScript function. convex/messages.ts TS ``` export const trySendMessage = mutation({ args: { body: v.string(), author: v.string(), }, handler: async (ctx, { body, author }) => { try { await ctx.runMutation(internal.messages.sendMessage, { body, author }); } catch (e) { // Record the failure, but rollback any writes from `sendMessage` await ctx.db.insert("failures", { kind: "MessageFailed", body, author, error: `Error: ${e}`, }); } }, }); ``` ## Always include the table name when calling `ctx.db` functions[​](#always-include-the-table-name-when-calling-ctxdb-functions "Direct link to always-include-the-table-name-when-calling-ctxdb-functions") ### Why?[​](#why-11 "Direct link to Why?") Since version 1.31.0 of the `convex` NPM package, the `ctx.db` functions accept a table name as the first argument. While this first argument is currently optional, passing the table name adds an additional safeguard which will be required for custom ID generation in the future. ### Example[​](#example-5 "Direct link to Example") convex/movies.ts TS ``` // ❌ await ctx.db.get(movieId); await ctx.db.patch(movieId, { title: "Whiplash" }); await ctx.db.replace(movieId, { title: "Whiplash", director: "Damien Chazelle", votes: 0, }); await ctx.db.delete(movieId); // ✅ vvvvvvvv await ctx.db.get("movies", movieId); await ctx.db.patch("movies", movieId, { title: "Whiplash" }); await ctx.db.replace("movies", movieId, { title: "Whiplash", director: "Damien Chazelle", votes: 0, }); await ctx.db.delete("movies", movieId); ``` ### How?[​](#how-10 "Direct link to How?") Search for calls of `db.get`, `db.patch`, `db.replace` and `db.delete` in your Convex codebase, and ensure that all of them pass a table name as the first argument. You can also check automatically that a table name argument is passed with the [`@convex-dev/explicit-table-ids` ESLint rule](/eslint.md#explicit-table-ids). You can migrate existing code automatically by using the autofix in the ESLint rule, or with the `@convex-dev/codemod` standalone tool. [Learn more on news.convex.dev →](https://news.convex.dev/db-table-name/) ## Don’t use `Date.now()` in queries[​](#date-in-queries "Direct link to date-in-queries") ### Why?[​](#why-12 "Direct link to Why?") When you subscribe to a query, Convex [will automatically run it again](/realtime.md) if the data that it accesses in the database change. The query is not re-run when `Date.now()` changes, because it wouldn’t be desirable to re-run a query every millisecond. So, if your query depends on the current time, it might return stale results. Also, using `Date.now()` in a query can cause the Convex query cache to be invalidated more frequently than necessary. In general, Convex will automatically re-use Convex query results if the query is called with the same arguments. However, when using `Date.now()` in a query, the query cache will be invalidated frequently in order to avoid showing results that are too old. This will unnecessarily increase the work that the database has to do. ### Example[​](#example-6 "Direct link to Example") convex/posts.ts TS ``` // ❌ const releasedPosts = await ctx.db .query("posts") .withIndex("by_released_at", (q) => q.lte("releasedAt", Date.now())) .take(100); // ✅ const releasedPosts = await ctx.db .query("posts") // `isReleased` is set to `true` by a scheduled function after `releasedAt` is reached .withIndex("by_is_released", (q) => q.eq("isReleased", true)) .take(100); ``` ### How?[​](#how-11 "Direct link to How?") Search for usages of `Date.now()` in your Convex queries, or in functions that are called from a Convex query. If you want to compare the current time with a timestamp stored in a database document, consider adding a coarser field to the document that you update from a [scheduled function](/scheduling/scheduled-functions.md) (see the example above). This way, the query cache is only invalidated explicitly when data changes. Alternatively, you can pass in the target time in as an explicit argument from the client. For best caching results, the client should avoid changing this argument frequently, for instance by rounding the time down to the most recent minute, so all client requests within that minute use the same arguments. --- # Source: https://docs.convex.dev/api/classes/browser.BaseConvexClient.md # Class: BaseConvexClient [browser](/api/modules/browser.md).BaseConvexClient Low-level client for directly integrating state management libraries with Convex. Most developers should use higher level clients, like the [ConvexHttpClient](/api/classes/browser.ConvexHttpClient.md) or the React hook based [ConvexReactClient](/api/classes/react.ConvexReactClient.md). ## Constructors[​](#constructors "Direct link to Constructors") ### constructor[​](#constructor "Direct link to constructor") • **new BaseConvexClient**(`address`, `onTransition`, `options?`) #### Parameters[​](#parameters "Direct link to Parameters") | Name | Type | Description | | -------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `string` | The url of your Convex deployment, often provided by an environment variable. E.g. `https://small-mouse-123.convex.cloud`. | | `onTransition` | (`updatedQueries`: [`QueryToken`](/api/modules/browser.md#querytoken)\[]) => `void` | A callback receiving an array of query tokens corresponding to query results that have changed -- additional handlers can be added via `addOnTransitionHandler`. | | `options?` | [`BaseConvexClientOptions`](/api/interfaces/browser.BaseConvexClientOptions.md) | See [BaseConvexClientOptions](/api/interfaces/browser.BaseConvexClientOptions.md) for a full description. | #### Defined in[​](#defined-in "Direct link to Defined in") [browser/sync/client.ts:277](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L277) ## Accessors[​](#accessors "Direct link to Accessors") ### url[​](#url "Direct link to url") • `get` **url**(): `string` Return the address for this client, useful for creating a new client. Not guaranteed to match the address with which this client was constructed: it may be canonicalized. #### Returns[​](#returns "Direct link to Returns") `string` #### Defined in[​](#defined-in-1 "Direct link to Defined in") [browser/sync/client.ts:1037](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L1037) ## Methods[​](#methods "Direct link to Methods") ### getMaxObservedTimestamp[​](#getmaxobservedtimestamp "Direct link to getMaxObservedTimestamp") ▸ **getMaxObservedTimestamp**(): `undefined` | `Long` #### Returns[​](#returns-1 "Direct link to Returns") `undefined` | `Long` #### Defined in[​](#defined-in-2 "Direct link to Defined in") [browser/sync/client.ts:542](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L542) *** ### addOnTransitionHandler[​](#addontransitionhandler "Direct link to addOnTransitionHandler") ▸ **addOnTransitionHandler**(`fn`): () => `boolean` Add a handler that will be called on a transition. Any external side effects (e.g. setting React state) should be handled here. #### Parameters[​](#parameters-1 "Direct link to Parameters") | Name | Type | | ---- | -------------------------------------- | | `fn` | (`transition`: `Transition`) => `void` | #### Returns[​](#returns-2 "Direct link to Returns") `fn` ▸ (): `boolean` ##### Returns[​](#returns-3 "Direct link to Returns") `boolean` #### Defined in[​](#defined-in-3 "Direct link to Defined in") [browser/sync/client.ts:621](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L621) *** ### getCurrentAuthClaims[​](#getcurrentauthclaims "Direct link to getCurrentAuthClaims") ▸ **getCurrentAuthClaims**(): `undefined` | { `token`: `string` ; `decoded`: `Record`<`string`, `any`> } Get the current JWT auth token and decoded claims. #### Returns[​](#returns-4 "Direct link to Returns") `undefined` | { `token`: `string` ; `decoded`: `Record`<`string`, `any`> } #### Defined in[​](#defined-in-4 "Direct link to Defined in") [browser/sync/client.ts:630](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L630) *** ### setAuth[​](#setauth "Direct link to setAuth") ▸ **setAuth**(`fetchToken`, `onChange`): `void` Set the authentication token to be used for subsequent queries and mutations. `fetchToken` will be called automatically again if a token expires. `fetchToken` should return `null` if the token cannot be retrieved, for example when the user's rights were permanently revoked. #### Parameters[​](#parameters-2 "Direct link to Parameters") | Name | Type | Description | | ------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------- | | `fetchToken` | [`AuthTokenFetcher`](/api/modules/browser.md#authtokenfetcher) | an async function returning the JWT-encoded OpenID Connect Identity Token | | `onChange` | (`isAuthenticated`: `boolean`) => `void` | a callback that will be called when the authentication status changes | #### Returns[​](#returns-5 "Direct link to Returns") `void` #### Defined in[​](#defined-in-5 "Direct link to Defined in") [browser/sync/client.ts:655](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L655) *** ### hasAuth[​](#hasauth "Direct link to hasAuth") ▸ **hasAuth**(): `boolean` #### Returns[​](#returns-6 "Direct link to Returns") `boolean` #### Defined in[​](#defined-in-6 "Direct link to Defined in") [browser/sync/client.ts:662](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L662) *** ### clearAuth[​](#clearauth "Direct link to clearAuth") ▸ **clearAuth**(): `void` #### Returns[​](#returns-7 "Direct link to Returns") `void` #### Defined in[​](#defined-in-7 "Direct link to Defined in") [browser/sync/client.ts:672](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L672) *** ### subscribe[​](#subscribe "Direct link to subscribe") ▸ **subscribe**(`name`, `args?`, `options?`): `Object` Subscribe to a query function. Whenever this query's result changes, the `onTransition` callback passed into the constructor will be called. #### Parameters[​](#parameters-3 "Direct link to Parameters") | Name | Type | Description | | ---------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `name` | `string` | The name of the query. | | `args?` | `Record`<`string`, [`Value`](/api/modules/values.md#value)> | An arguments object for the query. If this is omitted, the arguments will be `{}`. | | `options?` | [`SubscribeOptions`](/api/interfaces/browser.SubscribeOptions.md) | A [SubscribeOptions](/api/interfaces/browser.SubscribeOptions.md) options object for this query. | #### Returns[​](#returns-8 "Direct link to Returns") `Object` An object containing a [QueryToken](/api/modules/browser.md#querytoken) corresponding to this query and an `unsubscribe` callback. | Name | Type | | ------------- | -------------------------------------------------- | | `queryToken` | [`QueryToken`](/api/modules/browser.md#querytoken) | | `unsubscribe` | () => `void` | #### Defined in[​](#defined-in-8 "Direct link to Defined in") [browser/sync/client.ts:691](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L691) *** ### localQueryResult[​](#localqueryresult "Direct link to localQueryResult") ▸ **localQueryResult**(`udfPath`, `args?`): `undefined` | [`Value`](/api/modules/values.md#value) A query result based only on the current, local state. The only way this will return a value is if we're already subscribed to the query or its value has been set optimistically. #### Parameters[​](#parameters-4 "Direct link to Parameters") | Name | Type | | --------- | ----------------------------------------------------------- | | `udfPath` | `string` | | `args?` | `Record`<`string`, [`Value`](/api/modules/values.md#value)> | #### Returns[​](#returns-9 "Direct link to Returns") `undefined` | [`Value`](/api/modules/values.md#value) #### Defined in[​](#defined-in-9 "Direct link to Defined in") [browser/sync/client.ts:724](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L724) *** ### queryJournal[​](#queryjournal "Direct link to queryJournal") ▸ **queryJournal**(`name`, `args?`): `undefined` | [`QueryJournal`](/api/modules/browser.md#queryjournal) Retrieve the current [QueryJournal](/api/modules/browser.md#queryjournal) for this query function. If we have not yet received a result for this query, this will be `undefined`. #### Parameters[​](#parameters-5 "Direct link to Parameters") | Name | Type | Description | | ------- | ----------------------------------------------------------- | ------------------------------------ | | `name` | `string` | The name of the query. | | `args?` | `Record`<`string`, [`Value`](/api/modules/values.md#value)> | The arguments object for this query. | #### Returns[​](#returns-10 "Direct link to Returns") `undefined` | [`QueryJournal`](/api/modules/browser.md#queryjournal) The query's [QueryJournal](/api/modules/browser.md#queryjournal) or `undefined`. #### Defined in[​](#defined-in-10 "Direct link to Defined in") [browser/sync/client.ts:777](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L777) *** ### connectionState[​](#connectionstate "Direct link to connectionState") ▸ **connectionState**(): [`ConnectionState`](/api/modules/browser.md#connectionstate) Get the current [ConnectionState](/api/modules/browser.md#connectionstate) between the client and the Convex backend. #### Returns[​](#returns-11 "Direct link to Returns") [`ConnectionState`](/api/modules/browser.md#connectionstate) The [ConnectionState](/api/modules/browser.md#connectionstate) with the Convex backend. #### Defined in[​](#defined-in-11 "Direct link to Defined in") [browser/sync/client.ts:792](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L792) *** ### subscribeToConnectionState[​](#subscribetoconnectionstate "Direct link to subscribeToConnectionState") ▸ **subscribeToConnectionState**(`cb`): () => `void` Subscribe to the [ConnectionState](/api/modules/browser.md#connectionstate) between the client and the Convex backend, calling a callback each time it changes. Subscribed callbacks will be called when any part of ConnectionState changes. ConnectionState may grow in future versions (e.g. to provide a array of inflight requests) in which case callbacks would be called more frequently. #### Parameters[​](#parameters-6 "Direct link to Parameters") | Name | Type | | ---- | ------------------------------------------------------------------------------------------- | | `cb` | (`connectionState`: [`ConnectionState`](/api/modules/browser.md#connectionstate)) => `void` | #### Returns[​](#returns-12 "Direct link to Returns") `fn` An unsubscribe function to stop listening. ▸ (): `void` Subscribe to the [ConnectionState](/api/modules/browser.md#connectionstate) between the client and the Convex backend, calling a callback each time it changes. Subscribed callbacks will be called when any part of ConnectionState changes. ConnectionState may grow in future versions (e.g. to provide a array of inflight requests) in which case callbacks would be called more frequently. ##### Returns[​](#returns-13 "Direct link to Returns") `void` An unsubscribe function to stop listening. #### Defined in[​](#defined-in-12 "Direct link to Defined in") [browser/sync/client.ts:838](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L838) *** ### mutation[​](#mutation "Direct link to mutation") ▸ **mutation**(`name`, `args?`, `options?`): `Promise`<`any`> Execute a mutation function. #### Parameters[​](#parameters-7 "Direct link to Parameters") | Name | Type | Description | | ---------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `name` | `string` | The name of the mutation. | | `args?` | `Record`<`string`, [`Value`](/api/modules/values.md#value)> | An arguments object for the mutation. If this is omitted, the arguments will be `{}`. | | `options?` | [`MutationOptions`](/api/interfaces/browser.MutationOptions.md) | A [MutationOptions](/api/interfaces/browser.MutationOptions.md) options object for this mutation. | #### Returns[​](#returns-14 "Direct link to Returns") `Promise`<`any`> * A promise of the mutation's result. #### Defined in[​](#defined-in-13 "Direct link to Defined in") [browser/sync/client.ts:858](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L858) *** ### action[​](#action "Direct link to action") ▸ **action**(`name`, `args?`): `Promise`<`any`> Execute an action function. #### Parameters[​](#parameters-8 "Direct link to Parameters") | Name | Type | Description | | ------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `name` | `string` | The name of the action. | | `args?` | `Record`<`string`, [`Value`](/api/modules/values.md#value)> | An arguments object for the action. If this is omitted, the arguments will be `{}`. | #### Returns[​](#returns-15 "Direct link to Returns") `Promise`<`any`> A promise of the action's result. #### Defined in[​](#defined-in-14 "Direct link to Defined in") [browser/sync/client.ts:979](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L979) *** ### close[​](#close "Direct link to close") ▸ **close**(): `Promise`<`void`> Close any network handles associated with this client and stop all subscriptions. Call this method when you're done with an [BaseConvexClient](/api/classes/browser.BaseConvexClient.md) to dispose of its sockets and resources. #### Returns[​](#returns-16 "Direct link to Returns") `Promise`<`void`> A `Promise` fulfilled when the connection has been completely closed. #### Defined in[​](#defined-in-15 "Direct link to Defined in") [browser/sync/client.ts:1026](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L1026) --- # Source: https://docs.convex.dev/api/interfaces/browser.BaseConvexClientOptions.md # Interface: BaseConvexClientOptions [browser](/api/modules/browser.md).BaseConvexClientOptions Options for [BaseConvexClient](/api/classes/browser.BaseConvexClient.md). ## Hierarchy[​](#hierarchy "Direct link to Hierarchy") * **`BaseConvexClientOptions`** ↳ [`ConvexReactClientOptions`](/api/interfaces/react.ConvexReactClientOptions.md) ## Properties[​](#properties "Direct link to Properties") ### unsavedChangesWarning[​](#unsavedchangeswarning "Direct link to unsavedChangesWarning") • `Optional` **unsavedChangesWarning**: `boolean` Whether to prompt the user if they have unsaved changes pending when navigating away or closing a web page. This is only possible when the `window` object exists, i.e. in a browser. The default value is `true` in browsers. #### Defined in[​](#defined-in "Direct link to Defined in") [browser/sync/client.ts:69](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L69) *** ### webSocketConstructor[​](#websocketconstructor "Direct link to webSocketConstructor") • `Optional` **webSocketConstructor**: `Object` #### Call signature[​](#call-signature "Direct link to Call signature") • **new webSocketConstructor**(`url`, `protocols?`): `WebSocket` Specifies an alternate [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) constructor to use for client communication with the Convex cloud. The default behavior is to use `WebSocket` from the global environment. ##### Parameters[​](#parameters "Direct link to Parameters") | Name | Type | | ------------ | ----------------------- | | `url` | `string` \| `URL` | | `protocols?` | `string` \| `string`\[] | ##### Returns[​](#returns "Direct link to Returns") `WebSocket` #### Type declaration[​](#type-declaration "Direct link to Type declaration") | Name | Type | | ------------ | ----------- | | `prototype` | `WebSocket` | | `CONNECTING` | `0` | | `OPEN` | `1` | | `CLOSING` | `2` | | `CLOSED` | `3` | #### Defined in[​](#defined-in-1 "Direct link to Defined in") [browser/sync/client.ts:76](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L76) *** ### verbose[​](#verbose "Direct link to verbose") • `Optional` **verbose**: `boolean` Adds additional logging for debugging purposes. The default value is `false`. #### Defined in[​](#defined-in-2 "Direct link to Defined in") [browser/sync/client.ts:82](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L82) *** ### logger[​](#logger "Direct link to logger") • `Optional` **logger**: `boolean` | `Logger` A logger, `true`, or `false`. If not provided or `true`, logs to the console. If `false`, logs are not printed anywhere. You can construct your own logger to customize logging to log elsewhere. A logger is an object with 4 methods: log(), warn(), error(), and logVerbose(). These methods can receive multiple arguments of any types, like console.log(). #### Defined in[​](#defined-in-3 "Direct link to Defined in") [browser/sync/client.ts:91](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L91) *** ### reportDebugInfoToConvex[​](#reportdebuginfotoconvex "Direct link to reportDebugInfoToConvex") • `Optional` **reportDebugInfoToConvex**: `boolean` Sends additional metrics to Convex for debugging purposes. The default value is `false`. #### Defined in[​](#defined-in-4 "Direct link to Defined in") [browser/sync/client.ts:97](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L97) *** ### onServerDisconnectError[​](#onserverdisconnecterror "Direct link to onServerDisconnectError") • `Optional` **onServerDisconnectError**: (`message`: `string`) => `void` #### Type declaration[​](#type-declaration-1 "Direct link to Type declaration") ▸ (`message`): `void` This API is experimental: it may change or disappear. A function to call on receiving abnormal WebSocket close messages from the connected Convex deployment. The content of these messages is not stable, it is an implementation detail that may change. Consider this API an observability stopgap until higher level codes with recommendations on what to do are available, which could be a more stable interface instead of `string`. Check `connectionState` for more quantitative metrics about connection status. ##### Parameters[​](#parameters-1 "Direct link to Parameters") | Name | Type | | --------- | -------- | | `message` | `string` | ##### Returns[​](#returns-1 "Direct link to Returns") `void` #### Defined in[​](#defined-in-5 "Direct link to Defined in") [browser/sync/client.ts:111](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L111) *** ### skipConvexDeploymentUrlCheck[​](#skipconvexdeploymenturlcheck "Direct link to skipConvexDeploymentUrlCheck") • `Optional` **skipConvexDeploymentUrlCheck**: `boolean` Skip validating that the Convex deployment URL looks like `https://happy-animal-123.convex.cloud` or localhost. This can be useful if running a self-hosted Convex backend that uses a different URL. The default value is `false` #### Defined in[​](#defined-in-6 "Direct link to Defined in") [browser/sync/client.ts:121](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L121) *** ### authRefreshTokenLeewaySeconds[​](#authrefreshtokenleewayseconds "Direct link to authRefreshTokenLeewaySeconds") • `Optional` **authRefreshTokenLeewaySeconds**: `number` If using auth, the number of seconds before a token expires that we should refresh it. The default value is `2`. #### Defined in[​](#defined-in-7 "Direct link to Defined in") [browser/sync/client.ts:127](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L127) *** ### expectAuth[​](#expectauth "Direct link to expectAuth") • `Optional` **expectAuth**: `boolean` This API is experimental: it may change or disappear. Whether query, mutation, and action requests should be held back until the first auth token can be sent. Opting into this behavior works well for pages that should only be viewed by authenticated clients. Defaults to false, not waiting for an auth token. #### Defined in[​](#defined-in-8 "Direct link to Defined in") [browser/sync/client.ts:139](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L139) --- # Source: https://docs.convex.dev/api/classes/browser.ConvexClient.md # Class: ConvexClient [browser](/api/modules/browser.md).ConvexClient Subscribes to Convex query functions and executes mutations and actions over a WebSocket. Optimistic updates for mutations are not provided for this client. Third party clients may choose to wrap [BaseConvexClient](/api/classes/browser.BaseConvexClient.md) for additional control. ``` const client = new ConvexClient("https://happy-otter-123.convex.cloud"); const unsubscribe = client.onUpdate(api.messages.list, {}, (messages) => { console.log(messages[0].body); }); ``` ## Constructors[​](#constructors "Direct link to Constructors") ### constructor[​](#constructor "Direct link to constructor") • **new ConvexClient**(`address`, `options?`) Construct a client and immediately initiate a WebSocket connection to the passed address. #### Parameters[​](#parameters "Direct link to Parameters") | Name | Type | | --------- | -------------------------------------------------------------------- | | `address` | `string` | | `options` | [`ConvexClientOptions`](/api/modules/browser.md#convexclientoptions) | #### Defined in[​](#defined-in "Direct link to Defined in") [browser/simple\_client.ts:119](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L119) ## Accessors[​](#accessors "Direct link to Accessors") ### closed[​](#closed "Direct link to closed") • `get` **closed**(): `boolean` Once closed no registered callbacks will fire again. #### Returns[​](#returns "Direct link to Returns") `boolean` #### Defined in[​](#defined-in-1 "Direct link to Defined in") [browser/simple\_client.ts:96](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L96) *** ### client[​](#client "Direct link to client") • `get` **client**(): [`BaseConvexClient`](/api/classes/browser.BaseConvexClient.md) #### Returns[​](#returns-1 "Direct link to Returns") [`BaseConvexClient`](/api/classes/browser.BaseConvexClient.md) #### Defined in[​](#defined-in-2 "Direct link to Defined in") [browser/simple\_client.ts:99](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L99) *** ### disabled[​](#disabled "Direct link to disabled") • `get` **disabled**(): `boolean` #### Returns[​](#returns-2 "Direct link to Returns") `boolean` #### Defined in[​](#defined-in-3 "Direct link to Defined in") [browser/simple\_client.ts:110](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L110) ## Methods[​](#methods "Direct link to Methods") ### onUpdate[​](#onupdate "Direct link to onUpdate") ▸ **onUpdate**<`Query`>(`query`, `args`, `callback`, `onError?`): `Unsubscribe`<`Query`\[`"_returnType"`]> Call a callback whenever a new result for a query is received. The callback will run soon after being registered if a result for the query is already in memory. The return value is an Unsubscribe object which is both a function an an object with properties. Both of the patterns below work with this object: ``` // call the return value as a function const unsubscribe = client.onUpdate(api.messages.list, {}, (messages) => { console.log(messages); }); unsubscribe(); // unpack the return value into its properties const { getCurrentValue, unsubscribe, } = client.onUpdate(api.messages.list, {}, (messages) => { console.log(messages); }); ``` #### Type parameters[​](#type-parameters "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-1 "Direct link to Parameters") | Name | Type | Description | | ---------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `query` | `Query` | A [FunctionReference](/api/modules/server.md#functionreference) for the public query to run. | | `args` | [`FunctionArgs`](/api/modules/server.md#functionargs)<`Query`> | The arguments to run the query with. | | `callback` | (`result`: [`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>) => `unknown` | Function to call when the query result updates. | | `onError?` | (`e`: `Error`) => `unknown` | Function to call when the query result updates with an error. If not provided, errors will be thrown instead of calling the callback. | #### Returns[​](#returns-3 "Direct link to Returns") `Unsubscribe`<`Query`\[`"_returnType"`]> an Unsubscribe function to stop calling the onUpdate function. #### Defined in[​](#defined-in-4 "Direct link to Defined in") [browser/simple\_client.ts:185](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L185) *** ### onPaginatedUpdate\_experimental[​](#onpaginatedupdate_experimental "Direct link to onPaginatedUpdate_experimental") ▸ **onPaginatedUpdate\_experimental**<`Query`>(`query`, `args`, `options`, `callback`, `onError?`): `Unsubscribe`<`PaginatedQueryResult`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>\[]>> Call a callback whenever a new result for a paginated query is received. This is an experimental preview: the final API may change. In particular, caching behavior, page splitting, and required paginated query options may change. #### Type parameters[​](#type-parameters-1 "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-2 "Direct link to Parameters") | Name | Type | Description | | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `query` | `Query` | A [FunctionReference](/api/modules/server.md#functionreference) for the public query to run. | | `args` | [`FunctionArgs`](/api/modules/server.md#functionargs)<`Query`> | The arguments to run the query with. | | `options` | `Object` | Options for the paginated query including initialNumItems and id. | | `options.initialNumItems` | `number` | - | | `callback` | (`result`: [`PaginationResult`](/api/interfaces/server.PaginationResult.md)<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>>) => `unknown` | Function to call when the query result updates. | | `onError?` | (`e`: `Error`) => `unknown` | Function to call when the query result updates with an error. | #### Returns[​](#returns-4 "Direct link to Returns") `Unsubscribe`<`PaginatedQueryResult`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>\[]>> an Unsubscribe function to stop calling the callback. #### Defined in[​](#defined-in-5 "Direct link to Defined in") [browser/simple\_client.ts:263](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L263) *** ### close[​](#close "Direct link to close") ▸ **close**(): `Promise`<`void`> #### Returns[​](#returns-5 "Direct link to Returns") `Promise`<`void`> #### Defined in[​](#defined-in-6 "Direct link to Defined in") [browser/simple\_client.ts:366](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L366) *** ### getAuth[​](#getauth "Direct link to getAuth") ▸ **getAuth**(): `undefined` | { `token`: `string` ; `decoded`: `Record`<`string`, `any`> } Get the current JWT auth token and decoded claims. #### Returns[​](#returns-6 "Direct link to Returns") `undefined` | { `token`: `string` ; `decoded`: `Record`<`string`, `any`> } #### Defined in[​](#defined-in-7 "Direct link to Defined in") [browser/simple\_client.ts:380](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L380) *** ### setAuth[​](#setauth "Direct link to setAuth") ▸ **setAuth**(`fetchToken`, `onChange?`): `void` Set the authentication token to be used for subsequent queries and mutations. `fetchToken` will be called automatically again if a token expires. `fetchToken` should return `null` if the token cannot be retrieved, for example when the user's rights were permanently revoked. #### Parameters[​](#parameters-3 "Direct link to Parameters") | Name | Type | Description | | ------------ | -------------------------------------------------------------- | -------------------------------------------------------------------------------- | | `fetchToken` | [`AuthTokenFetcher`](/api/modules/browser.md#authtokenfetcher) | an async function returning the JWT (typically an OpenID Connect Identity Token) | | `onChange?` | (`isAuthenticated`: `boolean`) => `void` | a callback that will be called when the authentication status changes | #### Returns[​](#returns-7 "Direct link to Returns") `void` #### Defined in[​](#defined-in-8 "Direct link to Defined in") [browser/simple\_client.ts:393](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L393) *** ### mutation[​](#mutation "Direct link to mutation") ▸ **mutation**<`Mutation`>(`mutation`, `args`, `options?`): `Promise`<`Awaited`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Mutation`>>> Execute a mutation function. #### Type parameters[​](#type-parameters-2 "Direct link to Type parameters") | Name | Type | | ---------- | ------------------------------------------------------------------------------------- | | `Mutation` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"mutation"`> | #### Parameters[​](#parameters-4 "Direct link to Parameters") | Name | Type | Description | | ---------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | `mutation` | `Mutation` | A [FunctionReference](/api/modules/server.md#functionreference) for the public mutation to run. | | `args` | [`FunctionArgs`](/api/modules/server.md#functionargs)<`Mutation`> | An arguments object for the mutation. | | `options?` | [`MutationOptions`](/api/interfaces/browser.MutationOptions.md) | A [MutationOptions](/api/interfaces/browser.MutationOptions.md) options object for the mutation. | #### Returns[​](#returns-8 "Direct link to Returns") `Promise`<`Awaited`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Mutation`>>> A promise of the mutation's result. #### Defined in[​](#defined-in-9 "Direct link to Defined in") [browser/simple\_client.ts:488](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L488) *** ### action[​](#action "Direct link to action") ▸ **action**<`Action`>(`action`, `args`): `Promise`<`Awaited`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Action`>>> Execute an action function. #### Type parameters[​](#type-parameters-3 "Direct link to Type parameters") | Name | Type | | -------- | ----------------------------------------------------------------------------------- | | `Action` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"action"`> | #### Parameters[​](#parameters-5 "Direct link to Parameters") | Name | Type | Description | | -------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | `action` | `Action` | A [FunctionReference](/api/modules/server.md#functionreference) for the public action to run. | | `args` | [`FunctionArgs`](/api/modules/server.md#functionargs)<`Action`> | An arguments object for the action. | #### Returns[​](#returns-9 "Direct link to Returns") `Promise`<`Awaited`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Action`>>> A promise of the action's result. #### Defined in[​](#defined-in-10 "Direct link to Defined in") [browser/simple\_client.ts:505](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L505) *** ### query[​](#query "Direct link to query") ▸ **query**<`Query`>(`query`, `args`): `Promise`<`Awaited`<`Query`\[`"_returnType"`]>> Fetch a query result once. #### Type parameters[​](#type-parameters-4 "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-6 "Direct link to Parameters") | Name | Type | Description | | ------- | ------------------- | -------------------------------------------------------------------------------------------- | | `query` | `Query` | A [FunctionReference](/api/modules/server.md#functionreference) for the public query to run. | | `args` | `Query`\[`"_args"`] | An arguments object for the query. | #### Returns[​](#returns-10 "Direct link to Returns") `Promise`<`Awaited`<`Query`\[`"_returnType"`]>> A promise of the query's result. #### Defined in[​](#defined-in-11 "Direct link to Defined in") [browser/simple\_client.ts:521](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L521) *** ### connectionState[​](#connectionstate "Direct link to connectionState") ▸ **connectionState**(): [`ConnectionState`](/api/modules/browser.md#connectionstate) Get the current [ConnectionState](/api/modules/browser.md#connectionstate) between the client and the Convex backend. #### Returns[​](#returns-11 "Direct link to Returns") [`ConnectionState`](/api/modules/browser.md#connectionstate) The [ConnectionState](/api/modules/browser.md#connectionstate) with the Convex backend. #### Defined in[​](#defined-in-12 "Direct link to Defined in") [browser/simple\_client.ts:553](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L553) *** ### subscribeToConnectionState[​](#subscribetoconnectionstate "Direct link to subscribeToConnectionState") ▸ **subscribeToConnectionState**(`cb`): () => `void` Subscribe to the [ConnectionState](/api/modules/browser.md#connectionstate) between the client and the Convex backend, calling a callback each time it changes. Subscribed callbacks will be called when any part of ConnectionState changes. ConnectionState may grow in future versions (e.g. to provide a array of inflight requests) in which case callbacks would be called more frequently. #### Parameters[​](#parameters-7 "Direct link to Parameters") | Name | Type | | ---- | ------------------------------------------------------------------------------------------- | | `cb` | (`connectionState`: [`ConnectionState`](/api/modules/browser.md#connectionstate)) => `void` | #### Returns[​](#returns-12 "Direct link to Returns") `fn` An unsubscribe function to stop listening. ▸ (): `void` Subscribe to the [ConnectionState](/api/modules/browser.md#connectionstate) between the client and the Convex backend, calling a callback each time it changes. Subscribed callbacks will be called when any part of ConnectionState changes. ConnectionState may grow in future versions (e.g. to provide a array of inflight requests) in which case callbacks would be called more frequently. ##### Returns[​](#returns-13 "Direct link to Returns") `void` An unsubscribe function to stop listening. #### Defined in[​](#defined-in-13 "Direct link to Defined in") [browser/simple\_client.ts:568](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L568) --- # Source: https://docs.convex.dev/api/classes/browser.ConvexHttpClient.md # Class: ConvexHttpClient [browser](/api/modules/browser.md).ConvexHttpClient A Convex client that runs queries and mutations over HTTP. This client is stateful (it has user credentials and queues mutations) so take care to avoid sharing it between requests in a server. This is appropriate for server-side code (like Netlify Lambdas) or non-reactive webapps. ## Constructors[​](#constructors "Direct link to Constructors") ### constructor[​](#constructor "Direct link to constructor") • **new ConvexHttpClient**(`address`, `options?`) Create a new [ConvexHttpClient](/api/classes/browser.ConvexHttpClient.md). #### Parameters[​](#parameters "Direct link to Parameters") | Name | Type | Description | | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | `string` | The url of your Convex deployment, often provided by an environment variable. E.g. `https://small-mouse-123.convex.cloud`. | | `options?` | `Object` | An object of options. - `skipConvexDeploymentUrlCheck` - Skip validating that the Convex deployment URL looks like `https://happy-animal-123.convex.cloud` or localhost. This can be useful if running a self-hosted Convex backend that uses a different URL. - `logger` - A logger or a boolean. If not provided, logs to the console. You can construct your own logger to customize logging to log elsewhere or not log at all, or use `false` as a shorthand for a no-op logger. A logger is an object with 4 methods: log(), warn(), error(), and logVerbose(). These methods can receive multiple arguments of any types, like console.log(). - `auth` - A JWT containing identity claims accessible in Convex functions. This identity may expire so it may be necessary to call `setAuth()` later, but for short-lived clients it's convenient to specify this value here. - `fetch` - A custom fetch implementation to use for all HTTP requests made by this client. | | `options.skipConvexDeploymentUrlCheck?` | `boolean` | - | | `options.logger?` | `boolean` \| `Logger` | - | | `options.auth?` | `string` | - | | `options.fetch?` | (`input`: `URL` \| `RequestInfo`, `init?`: `RequestInit`) => `Promise`<`Response`>(`input`: `string` \| `URL` \| `Request`, `init?`: `RequestInit`) => `Promise`<`Response`> | - | #### Defined in[​](#defined-in "Direct link to Defined in") [browser/http\_client.ts:97](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L97) ## Accessors[​](#accessors "Direct link to Accessors") ### url[​](#url "Direct link to url") • `get` **url**(): `string` Return the address for this client, useful for creating a new client. Not guaranteed to match the address with which this client was constructed: it may be canonicalized. #### Returns[​](#returns "Direct link to Returns") `string` #### Defined in[​](#defined-in-1 "Direct link to Defined in") [browser/http\_client.ts:147](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L147) ## Methods[​](#methods "Direct link to Methods") ### backendUrl[​](#backendurl "Direct link to backendUrl") ▸ **backendUrl**(): `string` Obtain the [ConvexHttpClient](/api/classes/browser.ConvexHttpClient.md)'s URL to its backend. **`Deprecated`** Use url, which returns the url without /api at the end. #### Returns[​](#returns-1 "Direct link to Returns") `string` The URL to the Convex backend, including the client's API version. #### Defined in[​](#defined-in-2 "Direct link to Defined in") [browser/http\_client.ts:137](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L137) *** ### setAuth[​](#setauth "Direct link to setAuth") ▸ **setAuth**(`value`): `void` Set the authentication token to be used for subsequent queries and mutations. Should be called whenever the token changes (i.e. due to expiration and refresh). #### Parameters[​](#parameters-1 "Direct link to Parameters") | Name | Type | Description | | ------- | -------- | ------------------------------------------ | | `value` | `string` | JWT-encoded OpenID Connect identity token. | #### Returns[​](#returns-2 "Direct link to Returns") `void` #### Defined in[​](#defined-in-3 "Direct link to Defined in") [browser/http\_client.ts:158](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L158) *** ### clearAuth[​](#clearauth "Direct link to clearAuth") ▸ **clearAuth**(): `void` Clear the current authentication token if set. #### Returns[​](#returns-3 "Direct link to Returns") `void` #### Defined in[​](#defined-in-4 "Direct link to Defined in") [browser/http\_client.ts:184](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L184) *** ### consistentQuery[​](#consistentquery "Direct link to consistentQuery") ▸ **consistentQuery**<`Query`>(`query`, `...args`): `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>> This API is experimental: it may change or disappear. Execute a Convex query function at the same timestamp as every other consistent query execution run by this HTTP client. This doesn't make sense for long-lived ConvexHttpClients as Convex backends can read a limited amount into the past: beyond 30 seconds in the past may not be available. Create a new client to use a consistent time. **`Deprecated`** This API is experimental: it may change or disappear. #### Type parameters[​](#type-parameters "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-2 "Direct link to Parameters") | Name | Type | Description | | --------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `query` | `Query` | - | | `...args` | [`OptionalRestArgs`](/api/modules/server.md#optionalrestargs)<`Query`> | The arguments object for the query. If this is omitted, the arguments will be `{}`. | #### Returns[​](#returns-4 "Direct link to Returns") `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>> A promise of the query's result. #### Defined in[​](#defined-in-5 "Direct link to Defined in") [browser/http\_client.ts:226](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L226) *** ### query[​](#query "Direct link to query") ▸ **query**<`Query`>(`query`, `...args`): `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>> Execute a Convex query function. #### Type parameters[​](#type-parameters-1 "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-3 "Direct link to Parameters") | Name | Type | Description | | --------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | `query` | `Query` | - | | `...args` | [`OptionalRestArgs`](/api/modules/server.md#optionalrestargs)<`Query`> | The arguments object for the query. If this is omitted, the arguments will be `{}`. | #### Returns[​](#returns-5 "Direct link to Returns") `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`>> A promise of the query's result. #### Defined in[​](#defined-in-6 "Direct link to Defined in") [browser/http\_client.ts:270](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L270) *** ### mutation[​](#mutation "Direct link to mutation") ▸ **mutation**<`Mutation`>(`mutation`, `...args`): `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Mutation`>> Execute a Convex mutation function. Mutations are queued by default. #### Type parameters[​](#type-parameters-2 "Direct link to Type parameters") | Name | Type | | ---------- | ------------------------------------------------------------------------------------- | | `Mutation` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"mutation"`> | #### Parameters[​](#parameters-4 "Direct link to Parameters") | Name | Type | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | `mutation` | `Mutation` | - | | `...args` | [`ArgsAndOptions`](/api/modules/server.md#argsandoptions)<`Mutation`, [`HttpMutationOptions`](/api/modules/browser.md#httpmutationoptions)> | The arguments object for the mutation. If this is omitted, the arguments will be `{}`. | #### Returns[​](#returns-6 "Direct link to Returns") `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Mutation`>> A promise of the mutation's result. #### Defined in[​](#defined-in-7 "Direct link to Defined in") [browser/http\_client.ts:430](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L430) *** ### action[​](#action "Direct link to action") ▸ **action**<`Action`>(`action`, `...args`): `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Action`>> Execute a Convex action function. Actions are not queued. #### Type parameters[​](#type-parameters-3 "Direct link to Type parameters") | Name | Type | | -------- | ----------------------------------------------------------------------------------- | | `Action` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"action"`> | #### Parameters[​](#parameters-5 "Direct link to Parameters") | Name | Type | Description | | --------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `action` | `Action` | - | | `...args` | [`OptionalRestArgs`](/api/modules/server.md#optionalrestargs)<`Action`> | The arguments object for the action. If this is omitted, the arguments will be `{}`. | #### Returns[​](#returns-7 "Direct link to Returns") `Promise`<[`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Action`>> A promise of the action's result. #### Defined in[​](#defined-in-8 "Direct link to Defined in") [browser/http\_client.ts:453](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L453) --- # Source: https://docs.convex.dev/api/interfaces/browser.MutationOptions.md # Interface: MutationOptions [browser](/api/modules/browser.md).MutationOptions Options for [mutation](/api/classes/browser.BaseConvexClient.md#mutation). ## Properties[​](#properties "Direct link to Properties") ### optimisticUpdate[​](#optimisticupdate "Direct link to optimisticUpdate") • `Optional` **optimisticUpdate**: [`OptimisticUpdate`](/api/modules/browser.md#optimisticupdate)<`any`> An optimistic update to apply along with this mutation. An optimistic update locally updates queries while a mutation is pending. Once the mutation completes, the update will be rolled back. #### Defined in[​](#defined-in "Direct link to Defined in") [browser/sync/client.ts:210](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L210) --- # Source: https://docs.convex.dev/api/interfaces/browser.OptimisticLocalStore.md # Interface: OptimisticLocalStore [browser](/api/modules/browser.md).OptimisticLocalStore A view of the query results currently in the Convex client for use within optimistic updates. ## Methods[​](#methods "Direct link to Methods") ### getQuery[​](#getquery "Direct link to getQuery") ▸ **getQuery**<`Query`>(`query`, `...args`): `undefined` | [`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`> Retrieve the result of a query from the client. Important: Query results should be treated as immutable! Always make new copies of structures within query results to avoid corrupting data within the client. #### Type parameters[​](#type-parameters "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters "Direct link to Parameters") | Name | Type | Description | | --------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `query` | `Query` | A [FunctionReference](/api/modules/server.md#functionreference) for the query to get. | | `...args` | [`OptionalRestArgs`](/api/modules/server.md#optionalrestargs)<`Query`> | The arguments object for this query. | #### Returns[​](#returns "Direct link to Returns") `undefined` | [`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`> The query result or `undefined` if the query is not currently in the client. #### Defined in[​](#defined-in "Direct link to Defined in") [browser/sync/optimistic\_updates.ts:28](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/optimistic_updates.ts#L28) *** ### getAllQueries[​](#getallqueries "Direct link to getAllQueries") ▸ **getAllQueries**<`Query`>(`query`): { `args`: [`FunctionArgs`](/api/modules/server.md#functionargs)<`Query`> ; `value`: `undefined` | [`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`> }\[] Retrieve the results and arguments of all queries with a given name. This is useful for complex optimistic updates that need to inspect and update many query results (for example updating a paginated list). Important: Query results should be treated as immutable! Always make new copies of structures within query results to avoid corrupting data within the client. #### Type parameters[​](#type-parameters-1 "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-1 "Direct link to Parameters") | Name | Type | Description | | ------- | ------- | ------------------------------------------------------------------------------------- | | `query` | `Query` | A [FunctionReference](/api/modules/server.md#functionreference) for the query to get. | #### Returns[​](#returns-1 "Direct link to Returns") { `args`: [`FunctionArgs`](/api/modules/server.md#functionargs)<`Query`> ; `value`: `undefined` | [`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`> }\[] An array of objects, one for each query of the given name. Each object includes: * `args` - The arguments object for the query. * `value` The query result or `undefined` if the query is loading. #### Defined in[​](#defined-in-1 "Direct link to Defined in") [browser/sync/optimistic\_updates.ts:49](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/optimistic_updates.ts#L49) *** ### setQuery[​](#setquery "Direct link to setQuery") ▸ **setQuery**<`Query`>(`query`, `args`, `value`): `void` Optimistically update the result of a query. This can either be a new value (perhaps derived from the old value from [getQuery](/api/interfaces/browser.OptimisticLocalStore.md#getquery)) or `undefined` to remove the query. Removing a query is useful to create loading states while Convex recomputes the query results. #### Type parameters[​](#type-parameters-2 "Direct link to Type parameters") | Name | Type | | ------- | ---------------------------------------------------------------------------------- | | `Query` | extends [`FunctionReference`](/api/modules/server.md#functionreference)<`"query"`> | #### Parameters[​](#parameters-2 "Direct link to Parameters") | Name | Type | Description | | ------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `query` | `Query` | A [FunctionReference](/api/modules/server.md#functionreference) for the query to set. | | `args` | [`FunctionArgs`](/api/modules/server.md#functionargs)<`Query`> | The arguments object for this query. | | `value` | `undefined` \| [`FunctionReturnType`](/api/modules/server.md#functionreturntype)<`Query`> | The new value to set the query to or `undefined` to remove it from the client. | #### Returns[​](#returns-2 "Direct link to Returns") `void` #### Defined in[​](#defined-in-2 "Direct link to Defined in") [browser/sync/optimistic\_updates.ts:69](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/optimistic_updates.ts#L69) --- # Source: https://docs.convex.dev/api/interfaces/browser.SubscribeOptions.md # Interface: SubscribeOptions [browser](/api/modules/browser.md).SubscribeOptions Options for [subscribe](/api/classes/browser.BaseConvexClient.md#subscribe). ## Properties[​](#properties "Direct link to Properties") ### journal[​](#journal "Direct link to journal") • `Optional` **journal**: [`QueryJournal`](/api/modules/browser.md#queryjournal) An (optional) journal produced from a previous execution of this query function. If there is an existing subscription to a query function with the same name and arguments, this journal will have no effect. #### Defined in[​](#defined-in "Direct link to Defined in") [browser/sync/client.ts:190](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L190) --- # Source: https://docs.convex.dev/api/modules/browser.md # Module: browser Tools for accessing Convex in the browser. **If you are using React, use the [react](/api/modules/react.md) module instead.** ## Usage[​](#usage "Direct link to Usage") Create a [ConvexHttpClient](/api/classes/browser.ConvexHttpClient.md) to connect to the Convex Cloud. ``` import { ConvexHttpClient } from "convex/browser"; // typically loaded from an environment variable const address = "https://small-mouse-123.convex.cloud"; const convex = new ConvexHttpClient(address); ``` ## Classes[​](#classes "Direct link to Classes") * [ConvexHttpClient](/api/classes/browser.ConvexHttpClient.md) * [ConvexClient](/api/classes/browser.ConvexClient.md) * [BaseConvexClient](/api/classes/browser.BaseConvexClient.md) ## Interfaces[​](#interfaces "Direct link to Interfaces") * [BaseConvexClientOptions](/api/interfaces/browser.BaseConvexClientOptions.md) * [SubscribeOptions](/api/interfaces/browser.SubscribeOptions.md) * [MutationOptions](/api/interfaces/browser.MutationOptions.md) * [OptimisticLocalStore](/api/interfaces/browser.OptimisticLocalStore.md) ## Type Aliases[​](#type-aliases "Direct link to Type Aliases") ### HttpMutationOptions[​](#httpmutationoptions "Direct link to HttpMutationOptions") Ƭ **HttpMutationOptions**: `Object` #### Type declaration[​](#type-declaration "Direct link to Type declaration") | Name | Type | Description | | ----------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `skipQueue` | `boolean` | Skip the default queue of mutations and run this immediately. This allows the same HttpConvexClient to be used to request multiple mutations in parallel, something not possible with WebSocket-based clients. | #### Defined in[​](#defined-in "Direct link to Defined in") [browser/http\_client.ts:40](https://github.com/get-convex/convex-js/blob/main/src/browser/http_client.ts#L40) *** ### ConvexClientOptions[​](#convexclientoptions "Direct link to ConvexClientOptions") Ƭ **ConvexClientOptions**: [`BaseConvexClientOptions`](/api/interfaces/browser.BaseConvexClientOptions.md) & { `disabled?`: `boolean` ; `unsavedChangesWarning?`: `boolean` } #### Defined in[​](#defined-in-1 "Direct link to Defined in") [browser/simple\_client.ts:36](https://github.com/get-convex/convex-js/blob/main/src/browser/simple_client.ts#L36) *** ### AuthTokenFetcher[​](#authtokenfetcher "Direct link to AuthTokenFetcher") Ƭ **AuthTokenFetcher**: (`args`: { `forceRefreshToken`: `boolean` }) => `Promise`<`string` | `null` | `undefined`> #### Type declaration[​](#type-declaration-1 "Direct link to Type declaration") ▸ (`args`): `Promise`<`string` | `null` | `undefined`> An async function returning a JWT. Depending on the auth providers configured in convex/auth.config.ts, this may be a JWT-encoded OpenID Connect Identity Token or a traditional JWT. `forceRefreshToken` is `true` if the server rejected a previously returned token or the token is anticipated to expiring soon based on its `exp` time. See ConvexReactClient.setAuth. ##### Parameters[​](#parameters "Direct link to Parameters") | Name | Type | | ------------------------ | --------- | | `args` | `Object` | | `args.forceRefreshToken` | `boolean` | ##### Returns[​](#returns "Direct link to Returns") `Promise`<`string` | `null` | `undefined`> #### Defined in[​](#defined-in-2 "Direct link to Defined in") [browser/sync/authentication\_manager.ts:25](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/authentication_manager.ts#L25) *** ### ConnectionState[​](#connectionstate "Direct link to ConnectionState") Ƭ **ConnectionState**: `Object` State describing the client's connection with the Convex backend. #### Type declaration[​](#type-declaration-2 "Direct link to Type declaration") | Name | Type | Description | | ----------------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `hasInflightRequests` | `boolean` | - | | `isWebSocketConnected` | `boolean` | - | | `timeOfOldestInflightRequest` | `Date` \| `null` | - | | `hasEverConnected` | `boolean` | True if the client has ever opened a WebSocket to the "ready" state. | | `connectionCount` | `number` | The number of times this client has connected to the Convex backend. A number of things can cause the client to reconnect -- server errors, bad internet, auth expiring. But this number being high is an indication that the client is having trouble keeping a stable connection. | | `connectionRetries` | `number` | The number of times this client has tried (and failed) to connect to the Convex backend. | | `inflightMutations` | `number` | The number of mutations currently in flight. | | `inflightActions` | `number` | The number of actions currently in flight. | #### Defined in[​](#defined-in-3 "Direct link to Defined in") [browser/sync/client.ts:147](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/client.ts#L147) *** ### FunctionResult[​](#functionresult "Direct link to FunctionResult") Ƭ **FunctionResult**: `FunctionSuccess` | `FunctionFailure` The result of running a function on the server. If the function hit an exception it will have an `errorMessage`. Otherwise it will produce a `Value`. #### Defined in[​](#defined-in-4 "Direct link to Defined in") [browser/sync/function\_result.ts:11](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/function_result.ts#L11) *** ### OptimisticUpdate[​](#optimisticupdate "Direct link to OptimisticUpdate") Ƭ **OptimisticUpdate**<`Args`>: (`localQueryStore`: [`OptimisticLocalStore`](/api/interfaces/browser.OptimisticLocalStore.md), `args`: `Args`) => `void` #### Type parameters[​](#type-parameters "Direct link to Type parameters") | Name | Type | | ------ | ------------------------------------------------------------------- | | `Args` | extends `Record`<`string`, [`Value`](/api/modules/values.md#value)> | #### Type declaration[​](#type-declaration-3 "Direct link to Type declaration") ▸ (`localQueryStore`, `args`): `void` A temporary, local update to query results within this client. This update will always be executed when a mutation is synced to the Convex server and rolled back when the mutation completes. Note that optimistic updates can be called multiple times! If the client loads new data while the mutation is in progress, the update will be replayed again. ##### Parameters[​](#parameters-1 "Direct link to Parameters") | Name | Type | Description | | ----------------- | ------------------------------------------------------------------------- | -------------------------------------------------- | | `localQueryStore` | [`OptimisticLocalStore`](/api/interfaces/browser.OptimisticLocalStore.md) | An interface to read and edit local query results. | | `args` | `Args` | The arguments to the mutation. | ##### Returns[​](#returns-1 "Direct link to Returns") `void` #### Defined in[​](#defined-in-5 "Direct link to Defined in") [browser/sync/optimistic\_updates.ts:90](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/optimistic_updates.ts#L90) *** ### PaginationStatus[​](#paginationstatus "Direct link to PaginationStatus") Ƭ **PaginationStatus**: `"LoadingFirstPage"` | `"CanLoadMore"` | `"LoadingMore"` | `"Exhausted"` #### Defined in[​](#defined-in-6 "Direct link to Defined in") [browser/sync/pagination.ts:5](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/pagination.ts#L5) *** ### QueryJournal[​](#queryjournal "Direct link to QueryJournal") Ƭ **QueryJournal**: `string` | `null` A serialized representation of decisions made during a query's execution. A journal is produced when a query function first executes and is re-used when a query is re-executed. Currently this is used to store pagination end cursors to ensure that pages of paginated queries will always end at the same cursor. This enables gapless, reactive pagination. `null` is used to represent empty journals. #### Defined in[​](#defined-in-7 "Direct link to Defined in") [browser/sync/protocol.ts:113](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/protocol.ts#L113) *** ### QueryToken[​](#querytoken "Direct link to QueryToken") Ƭ **QueryToken**: `string` & { `__queryToken`: `true` } A string representing the name and arguments of a query. This is used by the [BaseConvexClient](/api/classes/browser.BaseConvexClient.md). #### Defined in[​](#defined-in-8 "Direct link to Defined in") [browser/sync/udf\_path\_utils.ts:31](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/udf_path_utils.ts#L31) *** ### PaginatedQueryToken[​](#paginatedquerytoken "Direct link to PaginatedQueryToken") Ƭ **PaginatedQueryToken**: [`QueryToken`](/api/modules/browser.md#querytoken) & { `__paginatedQueryToken`: `true` } A string representing the name and arguments of a paginated query. This is a specialized form of QueryToken used for paginated queries. #### Defined in[​](#defined-in-9 "Direct link to Defined in") [browser/sync/udf\_path\_utils.ts:38](https://github.com/get-convex/convex-js/blob/main/src/browser/sync/udf_path_utils.ts#L38) *** ### UserIdentityAttributes[​](#useridentityattributes "Direct link to UserIdentityAttributes") Ƭ **UserIdentityAttributes**: `Omit`<[`UserIdentity`](/api/interfaces/server.UserIdentity.md), `"tokenIdentifier"`> #### Defined in[​](#defined-in-10 "Direct link to Defined in") [server/authentication.ts:215](https://github.com/get-convex/convex-js/blob/main/src/server/authentication.ts#L215) --- # Source: https://docs.convex.dev/client/javascript/bun.md # Source: https://docs.convex.dev/quickstart/bun.md Learn how to query data from Convex in a Bun project. For instructions for subscriptions instead of point-in-time queries see [Bun notes](/client/javascript/bun.md). # Using Convex with Bun 1. Create a new Bun project Create a new directory for your Bun project. ``` mkdir my-project && cd my-project && bun init -y ``` 2. Install the Convex client and server library Install the `convex` package. ``` bun add convex ``` 3. Set up a Convex dev deployment Next, run `bunx convex dev`. This will prompt you to log in with GitHub, create a project, and save your production and deployment URLs. It will also create a `convex/` folder for you to write your backend API functions in. The `dev` command will then continue running to sync your functions with your dev deployment in the cloud. ``` bunx convex dev ``` 4. Create sample data for your database In a new terminal window, create a `sampleData.jsonl` file with some sample data. sampleData.jsonl ``` {"text": "Buy groceries", "isCompleted": true} {"text": "Go for a swim", "isCompleted": true} {"text": "Integrate Convex", "isCompleted": false} ``` 5. Add the sample data to your database Now that your project is ready, add a `tasks` table with the sample data into your Convex database with the `import` command. ``` bunx convex import --table tasks sampleData.jsonl ``` 6. Expose a database query Add a new file `tasks.js` in the `convex/` folder with a query function that loads the data. Exporting a query function from this file declares an API function named after the file and the export name, `api.tasks.get`. convex/tasks.js ``` import { query } from "./_generated/server"; export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); }, }); ``` 7. Connect the script to your backend In a new file `index.ts`, create a `ConvexClient` using the URL of your development environment. index.ts ``` import { ConvexClient } from "convex/browser"; import { api } from "./convex/_generated/api.js"; const client = new ConvexClient(process.env["CONVEX_URL"]); const unsubscribe = client.onUpdate(api.tasks.get, {}, async (tasks) => { console.log(tasks); }); await Bun.sleep(1000); unsubscribe(); await client.close(); ``` 8. Run the script Run the script from the same directory and see the list of tasks logged to the terminal. ``` bun index.ts ``` See the complete [Bun documentation](/client/javascript/bun.md). --- # Source: https://docs.convex.dev/functions/bundling.md # Bundling Bundling is the process of gathering, optimizing and transpiling the JS/TS source code of [functions](/functions.md) and their dependencies. During development and when deploying, the code is transformed to a format that Convex [runtimes](/functions/runtimes.md) can directly and efficiently execute. Convex currently bundles all dependencies automatically, but for the Node.js runtime you can disable bundling certain packages via the [external packages](#external-packages) config. ## Bundling for Convex[​](#bundling-for-convex "Direct link to Bundling for Convex") When you push code either via `npx convex dev` or `npx convex deploy`, the Convex CLI uses [esbuild](https://esbuild.github.io/) to traverse your `convex/` folder and bundle your functions and all of their used dependencies into a source code bundle. This bundle is then sent to the server. Thanks to bundling you can write your code using both modern ECMAScript Modules (ESM) or the older CommonJS (CJS) syntax. ESM vs. CJS ESM * Is the standard for browser JavaScript * Uses static imports via the `import` and `export` **keywords** (not functions) at the global scope * Also supports dynamic imports via the asynchronous `import` function CJS * Was previously the standard module system for Node.js * Relies on dynamic imports via the `require` and asynchronous `import` functions for fetching external modules * Uses the `module.exports` object for exports ## Bundling limitations[​](#bundling-limitations "Direct link to Bundling limitations") The nature of bundling comes with a few limitations. ### Code size limits[​](#code-size-limits "Direct link to Code size limits") The total size of your bundled function code in your `convex/` folder is **limited to 32MiB (\~33.55MB)**. Other platform limits can be found [here](/production/state/limits.md). While this limit in itself is quite high for just source code, certain dependencies can quickly make your bundle size cross over this limit, particularly if they are not effectively [tree-shakeable](https://webpack.js.org/guides/tree-shaking/) (such as [aws-sdk](https://www.npmjs.com/package/aws-sdk) or [snowflake-sdk](https://www.npmjs.com/package/snowflake-sdk)) You can follow these steps to debug bundle size: 1. Make sure you're using the most recent version of convex ``` npm install convex@latest ``` 2. Generate the bundle Note that this will not push code, and just generated a bundle for debugging purposes. ``` npx convex dev --once --debug-bundle-path /tmp/myBundle ``` 3. Visualize the bundle Use [source-map-explorer](https://github.com/danvk/source-map-explorer/tree/master) to visualize your bundle. ``` npx source-map-explorer /tmp/myBundle/**/*.js ``` Code bundled for the Convex runtime will be in the `isolate` directory while code bundled for node actions will be in the `node` directory. Large node dependencies can be eliminated from the bundle by marking them as [external packages](/functions/bundling.md#external-packages). ### Dynamic dependencies[​](#dynamic-dependencies "Direct link to Dynamic dependencies") Some libraries rely on dynamic imports (via `import`/`require` calls) to avoid always including their dependencies. These imports are not supported by the [default Convex runtime](/functions/runtimes.md#default-convex-runtime) and will throw an error at runtime. Additionally, some libraries rely on local files, which cannot be bundled by esbuild. If bundling is used, irrespective of the choice of runtime, these imports will always fail in Convex. Examples of libraries with dynamic dependencies Consider the following examples of packages relying on dynamic dependencies: * [langchain](https://www.npmjs.com/package/langchain) relying on the presence of peer dependencies that it can dynamically import. These dependencies are not statically `import`ed so will not be bundled by `esbuild`. * [sharp](https://www.npmjs.com/package/sharp) relying on the presence of `libvips` binaries for image-processing operations * [pdf-parse](https://www.npmjs.com/package/pdf-parse) relies on being dynamically imported with `require()` in order to detect if it is being run in test mode. Bundling can eliminate these `require()` calls, making `pdf-parse` assume it is running in test mode. * [tiktoken](https://www.npmjs.com/package/tiktoken) relying on local WASM files ## External packages[​](#external-packages "Direct link to External packages") As a workaround for the bundling limitations above, Convex provides an escape hatch: **external packages**. This feature is currently exclusive to Convex's [Node.js runtime](/functions/runtimes.md#nodejs-runtime). External packages use [`esbuild`'s facility for marking a dependency as external](https://esbuild.github.io/api/#external). This tells `esbuild` to not bundle the external dependency at all and to leave the import as a dynamic runtime import using `require()` or `import()`. Thus, your Convex modules will rely on the underlying system having that dependency made available at execution-time. ### Package installation on the server[​](#package-installation-on-the-server "Direct link to Package installation on the server") Packages marked as external are installed from [npm](https://www.npmjs.com/) the first time you push code that uses them. The version installed matches the version installed in the `node_modules` folder on your local machine. While this comes with a latency penalty the first time you push external packages, your packages are cached and this install step only ever needs to rerun if your external packages change. Once cached, pushes can actually be faster due to smaller source code bundles being sent to the server during pushes! ### Specifying external packages[​](#specifying-external-packages "Direct link to Specifying external packages") Create a [`convex.json`](/production/project-configuration.md#convexjson) file in the same directory as your `package.json` if it does not exist already. Set the `node.externalPackages` field to `["*"]` to mark all dependencies used within your Node actions as external: convex.json ``` { "$schema": "./node_modules/convex/schemas/convex.schema.json", "node": { "externalPackages": ["*"] } } ``` Alternatively, you can explicitly specify which packages to mark as external: convex.json ``` { "$schema": "./node_modules/convex/schemas/convex.schema.json", "node": { "externalPackages": ["aws-sdk", "sharp"] } } ``` The package identifiers should match the string used in `import`/`require` in your [Node.js action](/functions/actions.md#choosing-the-runtime-use-node). ### Troubleshooting external packages[​](#troubleshooting-external-packages "Direct link to Troubleshooting external packages") #### Incorrect package versions[​](#incorrect-package-versions "Direct link to Incorrect package versions") The Convex CLI searches for external packages within your local `node_modules` directory. Thus, changing version of a package in the `package.json` will not affect the version used on the server until you've updated the package version installed in your local `node_modules` folder (e.g. running `npm install`). #### Import errors[​](#import-errors "Direct link to Import errors") Marking a dependency as external may result in errors like this: > The requested module "some-module" is a CommonJs module, which may not support all module.exports as named exports. CommonJs modules can always be imported via the default export This requires rewriting any imports for this module as follows: ``` // ❌ old import { Foo } from "some-module"; // ✅ new import SomeModule from "some-module"; const { Foo } = SomeModule; ``` ### Limitations[​](#limitations "Direct link to Limitations") The total size of your source code bundle and external packages cannot exceed the following: * 45MB zipped * 240MB unzipped Packages that are known not to work at this time: * [Puppeteer](https://www.npmjs.com/package/puppeteer) - browser binary installation exceeds the size limit * [@ffmpeg.wasm](https://www.npmjs.com/package/@ffmpeg/ffmpeg) - since 0.12.0, [no longer supports Node environments](https://ffmpegwasm.netlify.app/docs/faq#why-ffmpegwasm-doesnt-support-nodejs) If there is a package that you would like working in your Convex functions, [let us know](https://convex.dev/community). --- # Source: https://docs.convex.dev/chef.md # Chef Chef is an AI app builder that builds complex full-stack apps. It leverages the full power of the Convex platform to one-shot apps like Slack, Instagram, and Notion. This means Chef can: build real-time apps, upload files, do text search and take advantage of Convex Components. ## [Prompt to start an app with Convex Chef](https://chef.convex.dev) ![Chef Screenshot](/assets/images/chef_preview-dfe305b7d7ebb5910c22cf2c22a6842d.png) ## Deploying to production[​](#deploying-to-production "Direct link to Deploying to production") Chef does have a built in ability to deploy the dev version of your app for you to immediately share with your friends to try. For apps intended to be built and maintained over the long term, we recommend downloading the code and importing it into your preferred IDE. When you download the code from Chef, your project automatically comes with [Cursor rules for Convex](/ai.md), helping you keep coding with confidence. ### Download the code[​](#download-the-code "Direct link to Download the code") ![Chef Screenshot](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbgAAAAxCAYAAACoArwXAAABXWlDQ1BJQ0MgUHJvZmlsZQAAKJF1kE9LAlEUxc+UZZT9ISIIWsymRWExjGnQzgw0cjFZobUbx2kURn2ME9GuDxHt20TQB7DARUTtWgRCQatoVy2j2ZRM92mlFl24nB+Hex/nXaDDpzJmegDkC7aViC6IqY1N0fuEbgxjAD0YU7USCytKnEbwre3l3ELgWp3mb0n9gZeT1Gns8TJ5uHw1//x3vq16M3pJI/2gljVm2YAgESs7NuO8RzxiUSjifc5Gg485pxtcqc+sJSLEN8RDWlbNED8Q+9MtvtHCeXNb+8rA0/v0wvoq6Sj1OOKIQkQMi0iQJrFSZ/yzM1vfiaAIhl1YyMFAFjZthslhMKETL6EADTPwE8uQqIP81r9v2PSKU0DojeCi6an0p7M5ihlqehPXwGAVqASZaqk/lxUcT2krIDe4rwx0HbjuaxLwTgK1O9d9L7tu7QjovAfOnU+RKmSTCMrFsQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAABuKADAAQAAAABAAAAMQAAAAB2UVURAAAgCklEQVR4Ae1dB3hcxbX+pV2terfc5YJtXDDyM2AbMM2EXkMLHRJCQhJMCSm0hBpSCKEE00looZgAMb3XUIwM2Lhgy5J7kWXJsupK29/5R2/E9Xp3tXu14tnrOfpW9+7cqf+dmVPmzGxawO8JwZBBwCBgEDAIGARSDIH0FGuPaY5BwCBgEDAIGAQUAobBmY5gEDAIGAQMAimJgGFwKflaTaMMAgYBg4BBwDA40wcMAgYBg4BBICURMAwuJV+raZRBwCBgEDAIGAZn+oBBwCBgEDAIpCQChsGl5Gs1jTIIGAQMAgYBp4HAIGAQMAgYBAwCyULA7fHjy+WN+GZdK1ZuaEF9swftviAQCCEv24FBBZkYMzALE4dmYuK4IcjMzkxW0dvlkxbvRu+W5ha0u9vh8/kQCpm94dshuYsEpKWlISMjA7k5uSgoLLDV6l29LxkMbXWbmIkMpkAyMIgJcg8P19a78fq8Tfjg680IBkNSH0kQ9MMZEkNhhh8ubxAFPjcKWqqQuXkD1vhC8I2ajgMO3hOHTxmKYWU5PZSQ+OMeGZzP50dDQz3S0tNRVFiIrKysxEsxKVIKgc7OTvDT0dGBfv3KhOHFZwgwfenbbmAw/BaLZN31FlMKbnl5ecjMzFTMIln1suazZs0aDB8+3BqU1Hu7GPS2Ek+8vxavVNYKQwshHSFR1oTByZ8TAWQLDxnVVoW89fNR1dAB956HwFM6GkiXpyEH0tN8CAozPH5aOc6eMay3VdkmfY8Mrra2FtnZ2SgqKtomofliEGhqalJMbtCgQXGBYfrS9jAZDLfHpLchdjDNzRWLRIE9i0Qi9e1rBqfrkigGOl2iV2pt97+2Eis2tiqGlpaWjsygF1nBAIo7N2Jx5evo72mBO38wvMPGAsUjhK+5kJ4lWKe7RJAIIo3MUJS8kLDDyQU+nHlyBcrLchOtSsT4MRkcTUnBUNAwt4jQmUAisKmuDjlZ2T2aK01fit5fDIbRsbH7JBFMvT6vWCL62S0qoXTfFYNjpeLFIKEGWCIv29CK25+vQntnQJatgsgKBVDo34L+tVVoWb0YVa0NmLemCgHR6vxiqvSKSdIlypIodCjsNwxDxkzBoHEHwFVQKvZV0fuE0ZV1NCFQVIpLTx2LsUPyLaXZu41pW+KaW3Fxsb2cTapdAgGarbdu3dojgzN9KXp3MBhGx8buE4Mp1JJSPGPTDsZrG9yY9fxy+Ds7MKCjDrntjUjbuAxr61bivbXV2NLWjk6v+GvAL+txQYiLiVqXa/d2iEkSaFjbiIY1i7C88iUMmbAfdpt+ujib5AmjA9weH26bsxzXnT4B5f2y7VSvO03MbQJ0KDFrbt1YmZsICLB/sJ/0RKYvRUfIYBgdG7tPEsGUa26pSPFiYKftT760GPmbqzGu5kPkLnwL1etXYK67Fa8umY9NLU1o62xDlnhM0tMkKJqZM92BDKestwnL43dR12R1zofWljpUf/4iVnzwOBx+MVdKDIf893cE8dRrVXaqtk2amAzOeEtug5X5EgWBePpJPHGiZL9LBMeDTzxxdgmw4mxkPHgxDr0PU5XiwSDRtn/40jton3Mv6uvWoFq8IBvGn4CtDieWVL6JkM8jvCuAoGwJaG1zK+GXGpxXhGA/bZNC1OsCchXLpbAy0fHEvFkz/z101C2R9Ts3BnZuxfhN/4Xn5Ycx+7kPVRq7/2KaKO1matIZBAwCBgGDQOohsGFjPZ5cLLrXjIuEO2XA626Dv6EaNfPeQCeZmN+PkGwHoNe9Q5hXmtgjveJwgoD4VDpEoxPmR62Kvh2BEP0s6VxChc6Nuf++HeP3noqmgf+DTSMmw19+CGo2OLD/lg6Ul9ozVcbU4FLv9ZgW7QwIVFZWYty4ceDVUHIQIJYGz+RguSvn8u5SN/x5hUgXD8h0YWKuTAdqNyxH89ZNCHj9SBdtrSTTiWkDCnHEbgNw4MBCTOmXhzGyubtI1CmXMwNZYq50StoM+VB7TldmzHS0tG7BF44CeCYciFBeARxpYtYU55N3v260DXlSGdysu2fZrsjOkHD27Nn46KOP+qyqLS0tePzxx/HNN9/0WEZrayuqq6uVxNRj5DgifPLJJ3jqqafiiNm3UTgJn3feeaoQXpM5KTc2NqKqqgoej2ebRtClmrjzWarReeedr4SFWbPuAT8UHHakcZrK2KdaX+rwBvDJ0iakh1zi4i+sg9pZextaG2tFWwsiU1z+jxKmdu2Rk3HnOTNw80nTcc3Re+HCSSNxzPB+mFySj6GyLudyCFOTzd/pXaqb8p6kGTMgf6s31IiyF0IozSUfWb+TsE++aQDLtkNJNVHOumcWZl4y0049tklDz5/99ttvm7C99toL++yzD0499VQMG5bczYDbFBTjy6233ooZM2bgoIMOihHL/iMO9j/+8Y/4/e9/jwkTJkTM6NNPP5WJaha++uqr7ucnnXQSrr32WrVJtTswwZtXXnkF//nPf3DWWWclmDK50cnUyGys12XLlvWqkCVLluBXv/oVVq9e3Z3P97//ffzud79TmNXX1yvcb775ZowdK3t1dkJSGtrnlZg6bWp37YnhzItnCp6PdYfxhkyO8aZO/TbuNhHky3HHHYeampru4BEjRqgxecwxx2DKlCnd4b292dGxpzDAeS1emjp12nZ4x5s2nnh8d70dD/GUEynOwlUtXRqXg64iNDPKWpuYJD2yHcDn9+G8PYfjsiMrUDqiHNn5RWhv2IqAPG8XT8sOOb4rz5mOctlv6EvzYBM84mUpHpZ0NhFG5pSPWCzhcbslZ5W7mEBFwyMjFUMmy542NnGP/qQyuEig2AnTC6NkJmRobmk0X+qTTz6pPo888ggqKirsZL1Tp6GW9eMf/1hNwn/7298wdOhQxej+8pe/KK2PDIpmg52ZrIOXE7D1u512se+ccsopCrN//OMfar/T/PnzccMNNyjJkdjt7KS1XjIzammVlZ+rJlFQiMTEOAnHQwMHDlTCFtdVyOxef/11PP3006AgcNppp8WTxU4dh9rvVGHmifRBxRCFKSZD0A8Hj+853ncXnjYZ36tr24XXBJWbPx3/0+QUksxMlzrVKE/2wl6w3wQMHjUSaUUlCLkykO0NobNtlTAtPwpcDniEg9W1i+ekMENlmhQN0Cn7B5QmxwqK5tYpW9PoSZmRniHbCkRr4w5woepNncLg1G1C/3bo2XDUqFH43ve+h+OPPx6/+c1vlGTf3t6uJifdys2bN+O6667DIYccgqOOOkoNPpr6SL/+9a9x8cUX66iKOTLO8uXLVRjT8jsZA82C+v5nP/sZ9t57byXhxzJbxSqbBaxbtw6XXHIJpk+frup31VVXgaZFTTQx/vKXv1RlUXNaunSpfhTxSqbGExceeOABHHvssZg0aRJ+9KMfqUmI9Zw7d65KFwgE8Pe//x0nn3yyKps4hLfj4YcfxoknnqieMy5NBFbSbWPdGe+ee+4B8+0L4qRAyZSfcOKg1s/smNYWL16ssiSTY1uooZ1xxhm4//77MW3athM9LQd8X3z3p59+Or788svu6sR6l7rvcPKn1sT07KfE6+6771b4sX+yn9JMmkzSzI3MjJPqzJld/V0zNz4nbrwmSiUlJWr8HXnkkWocUcAcPXq06m96jDHPF198sbvdl19+OTTmfEYt+ec//7mKc/jhh6tx8Kc//UlNinweiebMmYMf/vCHCsef/vSneOedd1S0r7/+Wo1RPtfEelDbZB9ONlFQSJRRMX4iGl8idabwot9vIumSFXd9fYcwJmpVdA1xwimaXFZhPwwYtjtGFDjhkqO3gsLEOptkjvMH4OnoRFA8J0vzs5CTl40O0dZaZFN9wB+CxyvLBMoEKYxSriRxQYFDuB2FdAbxqj5S5vr6ThUn0X87NIMLb8wee+yBI444QjEjrqNQOiczevbZZ8HBs++++yomdumll6q1KR4h9e6776p4zOvNN99UZirNCDhgaLbabbfd1IDj/dVXX602t1N7fO6553DLLbeEV0N976lsMoyf/OQnoEnxzDPPVGZNDsw//OEPKj0H5i9+8QslFR999NFgXS+77LKIZTGQ+8g4kR544IGgZG0lMkea4dh+EiXse++9V515Ryb3/vvv44ILLsCmTZvUc6613XbbbeqewsPLL7+sJiAVIP94nh0Fg1dffRU/+MEPsOeee6qJ+s4779RRknrlhEApOZKkrLU4Tth2Jg4KSaS77roLxF8zGDIcYmOl22+/HS6XS/Ux9g1OrsS9p3dJvNh3brzxRpUdTcZOp1OZPSkYUBChkMB+OnPmzLj2DVrrFc99pZgnrcxOa24MJ268auLErZ/rsHiuhbKpn32ZpAWmN954A1deeaVq74UXXqj64fnnn4+NGzeqeLyy/910001qDHCsPfbYY6qPqghh/9jnKAjynFMyOaYnZhxH48ePB02aL730Uneqzz77TGmXEydO7A7b2W+0UGJthxZQ9HuzK7RY80z0vrHNh3SHHw5fEBmSOFe2BJQFW3HgqCGoKB+E9GzRyHKz4CgqFs9JBxZu7cBDi1bhwaoGPF5dj4/qW1Eva2kuObs2P0vO+5Q5ssuPUrRC0QiF/6F0wFA4ZOwI95OrAw7ZS+CU+8Z2e8J1r02UBDrcns+XwQGVqPQTD+Ds5G+99RZWrFiBOjkmipM+Bw8nYlL//v2VNKcn/IceekgNOg4A1ovMgYOCkvbChQtVGjJOfc8BfP3116twTlKcFCnZh5/o8vnnn8csm1rHNddcg/LycowcOVLlx0nw448/VvccsNQKuN529tlnq7DBgweD9dUmWhX4f/+YlkQJOpwoATkclH+A5uZmPPPMM0qq1UyMa4bnnnsuXnvtNcXoyCyIAyVyHi5LLZBrK9Q6SBQAFi1apHCkQEGiNsK6UULXZakH39E/PbATLa60tBRcO/3tb3+rJk6mJ4Y0sVEw4AG7mihoUEsmcTJ/9NFHsWrVKiUoxHqXOj0ZG7UTEgUY4nvOOecoLYZh7AecvNlnyfSSQcSFggHNaWRkWnPTeXNsil7XvTbH8UpTpl3afffdVVJaQbgWd9999ymt+MEHH1RMjpYF9hmaM2lO10SNmfHZty+66CI8//zz3e9Dx+GV+XDMkAlyozLxO+yww9S72H///ZU1h/2b+PLsSDJPWjWonacCcY7S68/W9oRrb3yvOp7dsWHNP557jy+AHNnKVupvRHDtUjQ31QNyzFa7owiOnGK8Wt2A4W1L0K8oHwuWrcdHGxrxxao6sVp1qveujl8WppYla3GcQ8TZUvbNeYW10V9FWJ14V6aJFsh1N6dsI3Dw4/DK3jgf8ju75rd46mmN02sGZwWaGUd7QdZCe3PPjk3iBMTOTaJWo+nggw9WEzMH/QknnKCCKY1zHYHECZqTDL+zrjSBkpFpoiOLpsmTJysGR3NdOIPjJEWKVjYnMDJOao1PPPGEGpAsT5NewD/00EN1kBrIZCKRiIyI1NbWFulxd5jWgqiBaqLJjESmpbUNmul0ngMGDFDmIO0hSuGARE2PExVJX1euXIkxY8aosJ3lH/sBNTZq82TeNKmREVGo0QyNbbG+e73GS4GBJ13EepcahwMOOEDfdmuj//rXv7BlyxYVTuxINEUni8GpDOWfMqcJ4wqf7Phdh7H/aW1Zp0v0qvtffn6+6ktak+MSgpWsZkoyIO2YQmGM1hb2NeKRk5PTnYx9k/mxb5K5kTjuWH89dshAyeAoIDKft99+GxRMUuU0EsXIIrzHLq37sW6siIlec3388ejOQt0JknBTXP0Ocr1erJM1taxxU+AdMw2NngAWvzwLjWtXwet1Y/+KcSjJdeObRVWoaWyRE008Yq2UzdwBmiLFsCmMjet3DvGidImJ0+PnfrkAijJzxMQZQj9xPhnZUoNiMW0OdYjW1t6M5vp1CMlZlsCUhFvx7cyecNKuBASaUiOlCVJfSxVkVhww1Ha0u7dVCtfMis/4Kwg029GpgCZFSoDaA/KLL74A8+K6iJV0eoZZ761xeN9T2ZzUOLHyyrW9IUOGbJOFTk+TmCbdDm2T1uG8UuNiuzUDsz6jOWf9+vUgo9L56rwYj3ZspvVK5+SHZH0e/p0TDYnmJKYjUSon5nriUYF9+C/Zi+mU9qlh8UMzNCdRmsO0WZFNifQuGN7Tu2QckrW/aAypjdAZiMQDfanF9cXPpegxGG5NUQXLPzIIPTZ1mJ2rFn5oSbEe0abbyDwpXFm9oMP7jMaZfdHK4HR+VhyZH5kXrQs0FdObmlo5hVviyXBaH1KFuMbG92R9j3x34eNBCyt8798VeSceIudEivNHhheeYKZoZWmor3oPG1d+LXvgOpQn5TufLBDzI3Do6HIsXF8rJk06i3TVkCZIfpxikKTZMV1MkBwn2TIHpguTyxQNrqO2GmWL5yDHIz/H1dKMPF8QQwJucVwpsNXMXjM4lqqZnFaj+b0viCYfusdzsiUT0K70CxYsUNoPy+Q9iQOQRNMFzSPUwjgQuHDOdHSyINmta09l04TJiZFOBpQ0SWSqWnPTWhDDuIhP0lJqJBMl20sp+IMPPlDSK5m1Jq63UfObPXt2t6OGxolxWCYnAmohnOjJLFkWzY40FfCZxo3xNXaUjLXkTTMtMYz3p3GYT28o3LXdbl50ICI+XD9k+0n86SearGn2JfPviXp6l5HS6+0GXOejkwWJg5mnyScbw64JMLrHKZ+rSTNO78lI7WEYNV5uUaHQQ+GHfYcMnMIbt2FoYn+jhqeJ44BLCno9VK+BMw+9Jsq4TMP85s2b1903iRm/c5zqd0UhhQ491O5Yl3BnIV3uznjVcymXePTcpOdVa3v4PNwcbX3eF/dlRSVY1SSHJcv6miNNTiwRLaxh1VJxGGlVbv5+mhsDHhw8ZjzOrxiKFZu2YGG9nEnpkrMlxSMyKGdNFmVlSFynMlmS2+VnZUtVQyJ4tyM3Mx/Bzha0ymHN8+sasWRLKzb7OzGypEwcVDLwto1G2WZwtPlbJ6GuF7M9YwuPl0gdOUlz4qZZkoOUGteIESO6pW5OvpTmuGbGQUSzIx0sGEcvOrPz0wxF85yW1mnG5NoB0+pBl0i9GLensrWk+8ILLyhtiWtvWvtkemqWHJxc2+FCOqVXOjnEIsblYKenH9ftOIly8qXzAh1BtNmLzI/rR2Rm1Lo0M+daBokOD/TEpAcnNVrugSN+mogZsaHZ6YorrlCayR133KG0YJr5wiVsnW5HvPI9ca2TTjNkNNRyiSHbTJOiVYOIVn+u65KivctI6VgO3wOdW2hJYJ+kuZL9gPloASlS2kTDyLwiafbMRzM3ToacKBMhrnFz/FHTYt/VJux//vOfirkxL65lst9qJxIyL/Y969oy4xF/Op+QwVPoYP9nH7MyOGt+7Ovsm1w3Zt+kJ7AmCl7s0yyHefbVmrAyAXLNUjwj4yXlk9BLQYJzqWZufH8k/V3XI5E66TS9vfJk/83CdJxyrFZxSDSvoByhNWUP3DF/Dvzi4p8hJkeXnHBy2uj+aFhfh/2LXOIVWYjivCx0eoKYPq5c3v8GfLqhCa1isuTGN3piyvHLcjBzDnwS1i6nl6zo8OG1jQ1oFdkzIOtwg+VTUTHZVvVtMzjahOOheONZ89ImOjI4fkg0TdCrjU4gej2MA4SDjWZGMjkSOwLXVzipkCi1azOblqo5uMjgOIB0WeFXpo0UxnBST2Vz/Y6OHVx/ozmFZXK9T0uvnDQpDZPpci+WZnbc6K3L7Srp2//c4M4Jh84jXIzXRDfpG264QX9VkyonCO06zXYznXYQIIOkNsaJhg471DA5aeh1NraNe8aIKZ0zSJyQOVmHm5u6C+3jGz3QEy2GpjNO7myH7iPMg2Y0vQcuGt6Mx2c9vUudXl+ZjkStkRvw//znP6vvxJXCVjKZGzNm+4iPduzSTE1N0OJ4wmvXBCl75CyagapUjH9kLHqNkgyaTjn0utWCFJPSU5hbX8i09Uk4epzqrJmW/UsLmBwLOt9wzJgf1/nohMK1UmJG5katTRPHNDU9Ci40//cVkYmcJ4J8pO0rLFNrUNbnxNoq+Pe2bpG0t97maTd9RXYdPAuehkN+AQCBTjhb3OIoEsL1E0bj1qrlyvvxuPGjUCjHcj362UY0Cg9b39CEvQaNwT6jC7Bo/UbU1DfBHZDjlkX7k63iMr6Ez4mnZEjW6URSQbMcxfxqzQYcPqocb6xag1YxXmbKvrnjZxxkq9ppAb9Hco5MsX6cjy+emyBjSRLKlVWk5WS+8Mg1hXIr5mD5/5iAuQYWrWxKv1wXs5pswttADZUMLhFJlJMA3f7J9PSaRni+1Gi59khNLhKxbowTS4th2xgnVv1j9RNdbqw47CfxbAHg5BGrv+myol05YVNj4ORop5/E8y4jlU38yAS0YBYpTix8dPxocTRD44RLIjNjGCdHjtFKGYN6bUdPyjrPZF1pVie2NP9a+zG3p9BCwe0EHAdcR9PCZ6yymR8dfJhfONGkzmUK9klqeNp0GR4vGl7WePHEscbX91bMY2nQOr6+JloemWc07VznGeuaaHmx8vLLXPLfU07EirZW0cD8aBKN65DiQjmhBCiZNglrGzfj33MXyPoaMG9jEzZ7xAMyIwP5sm+uLE/W7CR+zdZ2eGWeTpODmmW3G4Jy7wt4RftzqjW4WikjIHkXuzJx3KjheKdmDY4aUIK/Vn6BzNwuR7tYdQx/ZluD44DhALJKL+GZc2H0u9qYGM+gCa9fsr7HKpvMJxoD0uVHY0D6eaQrPSAjbRmwxqUpMVbe8dQtVtusZfXmnkyrN4wr3rKpDfBjl+LBK1LefA+xmFukNImEWddj9IRIJqe96yiMchLuK+bGulLA6wnbRDwdmV8k5sZ1eFojKKxQI4zG3BLBz07cLny/da6zk0c8aXrD3OLJP5E4TvF4razYC/c/9CjcaQFMKivEgsY2ZLrScc6gHLw4dzGy5de4j5ooGvu+2Zi3bC1WyL7F1Y1u1Ld7cOq+e8A3X37tm2t3bZ1ifnSoX/p2i8ekx90hR3bx9+K4uduJZtko/vrKtbh9vz0x/dgTbDE3ts02g7MOoERAMnENAgaB5CJgFQ60FmctoUvIvHi7dRxrnL66p7cqrQjJIjI9Ht9HM6d1W0ey8k8kn++KySVSp76Oe7Jo5LMeeRS7yTaOQmTgK9Gyy/Mz8WRlDX1FUJyZgRfmfoPh5SUIpAfR3OqT47naMX5wGQbk5ch6XC7yxGOyQfbGeUVT6xRzpVucU7gJgPvjnMrrMiBmS/lJHhFy6vr3Q+DI42w3yzaDs12iSWgQMAj0GQKcdMMpUlh4nL76TlNiMon74HYkIrY7kpbV19iMkfX8G6+4HPNnP4s5a9YiJzML2eIoUl3XiquOmqQ0tbxxw3DQ/hVoXFmL6lVvYerAUkwfPwzzV6xCuzjTbXJ70CHmyg6/HLgs++icYsbMla0gITFf8wDnHDnpJMuRLXvoOrFx6FgMGjnKdrNiMjiaCQwZBHpCIJ5+Ek+cnspJ5efx4BNPnFTGKNG2GbwSRSy++BdcdTUu++AjjKivw8SyMnHjz0G+UxhdhgO7DS1Fx9ZWvPT8e2hs6cCAwiyUFeVgzdZmLGtwY504pjS4fegQRufl2psrW9bDs9VG7hyXE2ViBnXJ/rgtovVN/p9JuOnGm+KrVJRYdGWJStwMrDesRo1kHuzSCLB/hG8ajwSI6UuRUOkKMxhGx8buk3gxJROMtO/Ubrk7Wrq+YvLX3DcLR0+pQIYwty+3uvHM2tV4fvFa+Wkcj6yX5WDMkDKMLi/FcHEQqe/woHKd7GurbxHtTUySXmFwsr8tU7S0bOEx/B25kiwXSrOzlMNJi8wpxcWluOueu3sNZ0wGl5uTiyaxsRoyCERDgBMJ+0lPZPpSdIQMhtGxsfskXkwpeOnTf+yWtaOmi5fJ26n/ADEbHn3dLVjpzERbaQn6jZuEzfmDsKhJHEbEWeTD6lo8Oq8Kb6ypx9urG/B1bQOaxasyKKZJOgblZeWjMD8bucLYyIRbhAmuE8ehhvZOuHLycO/DD2H8uPF2qrZNmpgMrqCwQNlF+UOchgwC4QiwX3AbAftJT2T6UmSEDIaRcelNaCKYUvDS52v2pswdMW28TN5u3SdP3hu3z3oQA4aPxcTDzpIjgsZhfkc+rnrxMzzw2VLMXbcFC9Y3oMUbVM4jOa4slMrpM1niWc71tgwxaXbK1pFOj0+2IgXEVJmDiokVeEp+xWXvKduvJdupZ8x9cMzQJy6cDQ31au8K9w7Z2T9kp2ImzY6LAAcONXt20n79yqSjxlzK7W6I6UvdUCjTv8HwWzyScWe3X9bW1qp9qLG21CSjfswjmfvSYtVJM/lkHwsXrcy//vVW3Cn7WdtaWrtNvtTM+OFZJf3lcHyP8JLWDjc6hZkF5XQSV4Zob+JU0r8gDyXiiXnGuRfgsiuvjVaErfAeGZzOtaW5Be3ya6s8UiqVbda6veYaGQF2WJp1KPnGo7lFymVX70sGw0i9ondhvcFUC17s19xfyv16zK8vqK8ZnF0mn4y2LpFfWLnvvvvV6U3kETxBhD+D4xQoh8r+061yOIVPfvomQ34/QA6zRIlsGaBTybHyY8QnnnUORo4ek4xqbJNH3Axum1Tmi0HAIGAQSDEEdnbBqzdMPpmvsrW1RU6YeR0ff/KpOsN0vRyp5hJNrbmtHcMG98ewIcOw77S9sdc+0zDt4Bnya9+Jn1ASb30Ng4sXKRPPIGAQMAgYBHYqBGI6mexULTGVNQgYBAwCBgGDgAUBw+AsYJhbg4BBwCBgEEgdBAyDS513aVpiEDAIGAQMAhYEDIOzgGFuDQIGAYOAQSB1EDAMLnXepWmJQcAgYBAwCFgQMAzOAoa5NQgYBAwCBoHUQcAwuNR5l6YlBgGDgEHAIGBBwDA4Cxjm1iBgEDAIGARSB4H/BcSYac9JP+PcAAAAAElFTkSuQmCC) At the top right of the Chef UI there is a download code button. Download the code and you’ll get a zip file. Unzip the file and put the folder in your desired location. We recommend renaming the folder to the name of your app for convenience. For the rest of the setup, open up the terminal and `cd` into your app: ``` cd ~/ ``` ### Install dependencies[​](#install-dependencies "Direct link to Install dependencies") Run the following command to install all dependencies for your project ``` npm i ``` ### Run your app[​](#run-your-app "Direct link to Run your app") Run the following command run your app, and setup Convex if you haven’t already. ``` npm run dev ``` Follow any instructions to login to Convex from your machine. caution You have now taken over from Chef for development of this app. Chef doesn't have the ability to re-import a project or track any progress from outside it. Going back to this project on Chef will cause conflicts in your project. ### Set up the frontend build script[​](#set-up-the-frontend-build-script "Direct link to Set up the frontend build script") Chef projects don’t come with a build script. So make sure to add the following to your `package.json` file: ``` "scripts": { //... other scripts "build": "vite build" }, ``` ### Recommended: Setup Git[​](#recommended-setup-git "Direct link to Recommended: Setup Git") In the terminal run the following three commands setup git for your app. The downloaded code comes with a `.gitignore` file. ``` git init git add --all git commit -m "Initial commit" ``` It's also recommended you setup a remote git repository with [GitHub](https://github.com/) if you're going to use the production hosting guides below. ### Set up production frontend hosting[​](#set-up-production-frontend-hosting "Direct link to Set up production frontend hosting") Follow one of the Convex [hosting guides](/production/hosting/.md) to set up frontend hosting and continuous deployment of your frontend and backend code. ### Initialize Convex Auth for Prod[​](#initialize-convex-auth-for-prod "Direct link to Initialize Convex Auth for Prod") Once you have a production deployment. You need to [set up Convex Auth for production](https://labs.convex.dev/auth/production). ## Integrations[​](#integrations "Direct link to Integrations") ### OpenAI[​](#openai "Direct link to OpenAI") If you ask Chef to use AI, by default it will try to use the built in OpenAI proxy with a limited number of calls. This helps you prototype your AI app idea quickly. However, at some point the built in number of calls will run out and you'll need to provide your own OpenAI API Key and remove the proxy URL. So that means you'll have to find the code that looks like this: ``` const openai = new OpenAI({ baseURL: process.env.CONVEX_OPENAI_BASE_URL, apiKey: process.env.CONVEX_OPENAI_API_KEY, }); ``` And remove the baseURL parameter: ``` const openai = new OpenAI({ apiKey: process.env.CONVEX_OPENAI_API_KEY, }); ``` Chef may automatically prompt you to change the environment variable. But if it doesn't, you can change it by going to the "Database" tab. Then click on Settings > Environment Variables and change `CONVEX_OPENAI_API_KEY` to your [personal OpenAI key](https://platform.openai.com). We plan on making this transition better over time. ### Resend[​](#resend "Direct link to Resend") Chef comes with a built in way to send emails to yourself via Resend. You can only send emails to the account you used to log into Chef. To send emails to anyone, you have to setup your app for production with a domain name. This is a limitation of how email providers work to combat spam. ## FAQs[​](#faqs "Direct link to FAQs") ### What browsers does Chef support?[​](#what-browsers-does-chef-support "Direct link to What browsers does Chef support?") Chef is best used on desktop/laptop browsers. It may work on some tablet or mobile browsers. Chef does not work in Safari on any platform. ### How does the pricing for Chef work?[​](#how-does-the-pricing-for-chef-work "Direct link to How does the pricing for Chef work?") Chef pricing is primarily based on AI token usage. The free plan gives you enough tokens to build the first version of your app in a small number of prompts. After that you can upgrade to the Starter plan that where you can pay for tokens as you go. ### What’s the difference between Chef and Convex?[​](#whats-the-difference-between-chef-and-convex "Direct link to What’s the difference between Chef and Convex?") Chef is an AI app builder that builds full-stack apps. Convex is the backend and database that powers Chef. ### Can I import my existing app to Chef?[​](#can-i-import-my-existing-app-to-chef "Direct link to Can I import my existing app to Chef?") Chef currently doesn’t have import and GitHub integration. But you can get most of the value by setting up the [Convex AI Rules and MCP server](/ai.md) in your Agentic IDE like Cursor. ### Are there any best practices for Chef?[​](#are-there-any-best-practices-for-chef "Direct link to Are there any best practices for Chef?") Yes! Check out this [tips post written by one of our engineers](https://stack.convex.dev/chef-cookbook-tips-working-with-ai-app-builders). ### What Convex Components can Chef use?[​](#what-convex-components-can-chef-use "Direct link to What Convex Components can Chef use?") Chef can use the [collaborative text editor](https://www.convex.dev/components/prosemirror-sync) component and the [presence](https://www.convex.dev/components/presence) component. We will support more components soon. Chef supports all other Convex features like text search, file storage, etc. ## Limitations[​](#limitations "Direct link to Limitations") Chef works off a singular template with Convex, Convex Auth and React powered by Vite. Switching these technologies is not supported by Chef. --- # Source: https://docs.convex.dev/testing/ci.md # Continuous Integration Continuous integration allows your team to move fast by combining changes from all team members and automatically testing them on a remote machine. ## Testing in GitHub Actions[​](#testing-in-github-actions "Direct link to Testing in GitHub Actions") It's easy if you're using [GitHub](https://docs.github.com/en/actions) to set up [CI](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration) workflow for running your test suite: .github/workflows/test.yml ``` name: Run Tests on: [pull_request, push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: npm ci - run: npm run test ``` After you commit and push this file to your repository, GitHub will run `npm run test` every time you create a pull request or push a new commit. --- # Source: https://docs.convex.dev/client/tanstack/tanstack-start/clerk.md # Source: https://docs.convex.dev/auth/clerk.md # Convex & Clerk [Clerk](https://clerk.com) is an authentication platform providing login via passwords, social identity providers, one-time email or SMS access codes, and multi-factor authentication and user management. ## Get started[​](#get-started "Direct link to Get started") Convex offers a provider that is specifically for integrating with Clerk called ``. It works with any of Clerk's React-based SDKs, such as the Next.js and Expo SDKs. See the following sections for the Clerk SDK that you're using: * [React](#react) - Use this as a starting point if your SDK is not listed * [Next.js](#nextjs) * [TanStack Start](#tanstack-start) ### React[​](#react "Direct link to React") **Example:** [React with Convex and Clerk](https://github.com/get-convex/template-react-vite-clerk) This guide assumes you already have a working React app with Convex. If not follow the [Convex React Quickstart](/quickstart/react.md) first. Then: 1. Sign up for Clerk Sign up for a free Clerk account at [clerk.com/sign-up](https://dashboard.clerk.com/sign-up). ![Sign up to Clerk](/screenshots/clerk-signup.png) 2. Create an application in Clerk Choose how you want your users to sign in. ![Create a Clerk application](/screenshots/clerk-createapp.png) 3. Create a JWT Template In the Clerk Dashboard, navigate to the [JWT templates](https://dashboard.clerk.com/last-active?path=jwt-templates) page. Select *New template* and then from the list of templates, select *Convex*. You'll be redirected to the template's settings page. **Do NOT rename the JWT token. It must be called `convex`.** Copy and save the *Issuer* URL somewhere secure. This URL is the issuer domain for Clerk's JWT templates, which is your Clerk app's *Frontend API URL*. In development, it's format will be `https://verb-noun-00.clerk.accounts.dev`. In production, it's format will be `https://clerk..com`. ![Create a JWT template](/screenshots/clerk-createjwt.png) 4. Configure Convex with the Clerk issuer domain In your app's `convex` folder, create a new file `auth.config.ts` with the following code. This is the server-side configuration for validating access tokens. convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { // Replace with your own Clerk Issuer URL from your "convex" JWT template // or with `process.env.CLERK_JWT_ISSUER_DOMAIN` // and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard // See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances domain: process.env.CLERK_JWT_ISSUER_DOMAIN!, applicationID: "convex", }, ] } satisfies AuthConfig; ``` 5. Deploy your changes Run `npx convex dev` to automatically sync your configuration to your backend. ``` npx convex dev ``` 6. Install clerk In a new terminal window, install the Clerk React SDK: ``` npm install @clerk/clerk-react ``` 7. Set your Clerk API keys In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page. In the **Quick Copy** section, copy your Clerk Publishable Key and set it as the `CLERK_PUBLISHABLE_KEY` environment variable. If you're using Vite, you will need to prefix it with `VITE_`. .env ``` VITE_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY ``` 8. Configure ConvexProviderWithClerk Both Clerk and Convex have provider components that are required to provide authentication and client context. You should already have `` wrapping your app. Replace it with ``, and pass Clerk's `useAuth()` hook to it. Then, wrap it with ``. `` requires a `publishableKey` prop, which you can set to the `VITE_CLERK_PUBLISHABLE_KEY` environment variable. src/main.tsx TS ``` import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { ClerkProvider, useAuth } from "@clerk/clerk-react"; import { ConvexProviderWithClerk } from "convex/react-clerk"; import { ConvexReactClient } from "convex/react"; const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string); ReactDOM.createRoot(document.getElementById("root")!).render( , ); ``` 9. Show UI based on authentication state You can control which UI is shown when the user is signed in or signed out using Convex's ``, `` and `` helper components. These should be used instead of Clerk's ``, `` and `` components, respectively. It's important to use the [`useConvexAuth()`](/api/modules/react.md#useconvexauth) hook instead of Clerk's `useAuth()` hook when you need to check whether the user is logged in or not. The `useConvexAuth()` hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it. In the following example, the `` component is a child of ``, so its content and any of its child components are guaranteed to have an authenticated user, and Convex queries can require authentication. src/App.tsx TS ``` import { SignInButton, UserButton } from "@clerk/clerk-react"; import { Authenticated, Unauthenticated, AuthLoading, useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; function App() { return (

Still loading

); } function Content() { const messages = useQuery(api.messages.getForCurrentUser); return
Authenticated content: {messages?.length}
; } export default App; ``` 10. Use authentication state in your Convex functions If the client is authenticated, you can access the information stored in the JWT via `ctx.auth.getUserIdentity`. If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`. **Make sure that the component calling this query is a child of `` from `convex/react`**. Otherwise, it will throw on page load. convex/messages.ts TS ``` import { query } from "./_generated/server"; export const getForCurrentUser = query({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (identity === null) { throw new Error("Not authenticated"); } return await ctx.db .query("messages") .withIndex("by_author", (q) => q.eq("author", identity.email)) .collect(); }, }); ``` ### Next.js[​](#nextjs "Direct link to Next.js") **Example:** [Next.js with Convex and Clerk](https://github.com/get-convex/template-nextjs-clerk) This guide assumes you already have a working Next.js app with Convex. If not follow the [Convex Next.js Quickstart](/quickstart/nextjs.md) first. Then: 1. Sign up for Clerk Sign up for a free Clerk account at [clerk.com/sign-up](https://dashboard.clerk.com/sign-up). ![Sign up to Clerk](/screenshots/clerk-signup.png) 2. Create an application in Clerk Choose how you want your users to sign in. ![Create a Clerk application](/screenshots/clerk-createapp.png) 3. Create a JWT Template In the Clerk Dashboard, navigate to the [JWT templates](https://dashboard.clerk.com/last-active?path=jwt-templates) page. Select *New template* and then from the list of templates, select *Convex*. You'll be redirected to the template's settings page. **Do NOT rename the JWT token. It must be called `convex`.** Copy and save the *Issuer* URL somewhere secure. This URL is the issuer domain for Clerk's JWT templates, which is your Clerk app's *Frontend API URL*. In development, it's format will be `https://verb-noun-00.clerk.accounts.dev`. In production, it's format will be `https://clerk..com`. ![Create a JWT template](/screenshots/clerk-createjwt.png) 4. Configure Convex with the Clerk issuer domain In your app's `convex` folder, create a new file `auth.config.ts` with the following code. This is the server-side configuration for validating access tokens. convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { // Replace with your own Clerk Issuer URL from your "convex" JWT template // or with `process.env.CLERK_JWT_ISSUER_DOMAIN` // and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard // See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances domain: process.env.CLERK_JWT_ISSUER_DOMAIN!, applicationID: "convex", }, ] } satisfies AuthConfig; ``` 5. Deploy your changes Run `npx convex dev` to automatically sync your configuration to your backend. ``` npx convex dev ``` 6. Install clerk In a new terminal window, install the Clerk Next.js SDK: ``` npm install @clerk/nextjs ``` 7. Set your Clerk API keys In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page. In the **Quick Copy** section, copy your Clerk Publishable and Secret Keys and set them as the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables, respectively. .env ``` NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY CLERK_SECRET_KEY=YOUR_SECRET_KEY ``` 8. Add Clerk middleware Clerk's `clerkMiddleware()` helper grants you access to user authentication state throughout your app. Create a `middleware.ts` file. In your `middleware.ts` file, export the `clerkMiddleware()` helper: ``` import { clerkMiddleware } from '@clerk/nextjs/server' export default clerkMiddleware() export const config = { matcher: [ // Skip Next.js internals and all static files, unless found in search params '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', // Always run for API routes '/(api|trpc)(.*)', ], } ``` By default, `clerkMiddleware()` will not protect any routes. All routes are public and you must opt-in to protection for routes.) to learn how to require authentication for specific routes. 9. Configure ConvexProviderWithClerk Both Clerk and Convex have provider components that are required to provide authentication and client context. Typically, you'd replace `` with ``, but with Next.js App Router, things are a bit more complex. `` calls `ConvexReactClient()` to get Convex's client, so it must be used in a Client Component. Your `app/layout.tsx`, where you would use ``, is a Server Component, and a Server Component cannot contain Client Component code. To solve this, you must first create a *wrapper* Client Component around ``. ``` 'use client' import { ReactNode } from 'react' import { ConvexReactClient } from 'convex/react' import { ConvexProviderWithClerk } from 'convex/react-clerk' import { useAuth } from '@clerk/nextjs' if (!process.env.NEXT_PUBLIC_CONVEX_URL) { throw new Error('Missing NEXT_PUBLIC_CONVEX_URL in your .env file') } const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL) export default function ConvexClientProvider({ children }: { children: ReactNode }) { return ( {children} ) } ``` 10. Wrap your app in Clerk and Convex Now, your Server Component, `app/layout.tsx`, can render `` instead of rendering `` directly. It's important that `` wraps ``, and not the other way around, as Convex needs to be able to access the Clerk context. ``` import type { Metadata } from 'next' import { Geist, Geist_Mono } from 'next/font/google' import './globals.css' import { ClerkProvider } from '@clerk/nextjs' import ConvexClientProvider from '@/components/ConvexClientProvider' const geistSans = Geist({ variable: '--font-geist-sans', subsets: ['latin'], }) const geistMono = Geist_Mono({ variable: '--font-geist-mono', subsets: ['latin'], }) export const metadata: Metadata = { title: 'Clerk Next.js Quickstart', description: 'Generated by create next app', } export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( {children} ) } ``` 11. Show UI based on authentication state You can control which UI is shown when the user is signed in or signed out using Convex's ``, `` and `` helper components. These should be used instead of Clerk's ``, `` and `` components, respectively. It's important to use the [`useConvexAuth()`](/api/modules/react.md#useconvexauth) hook instead of Clerk's `useAuth()` hook when you need to check whether the user is logged in or not. The `useConvexAuth()` hook makes sure that the browser has fetched the auth token needed to make authenticated requests to your Convex backend, and that the Convex backend has validated it. In the following example, the `` component is a child of ``, so its content and any of its child components are guaranteed to have an authenticated user, and Convex queries can require authentication. app/page.tsx TS ``` "use client"; import { Authenticated, Unauthenticated } from "convex/react"; import { SignInButton, UserButton } from "@clerk/nextjs"; import { useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; export default function Home() { return ( <> ); } function Content() { const messages = useQuery(api.messages.getForCurrentUser); return
Authenticated content: {messages?.length}
; } ``` 12. Use authentication state in your Convex functions If the client is authenticated, you can access the information stored in the JWT via `ctx.auth.getUserIdentity`. If the client isn't authenticated, `ctx.auth.getUserIdentity` will return `null`. **Make sure that the component calling this query is a child of `` from `convex/react`**. Otherwise, it will throw on page load. convex/messages.ts TS ``` import { query } from "./_generated/server"; export const getForCurrentUser = query({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (identity === null) { throw new Error("Not authenticated"); } return await ctx.db .query("messages") .withIndex("by_author", (q) => q.eq("author", identity.email)) .collect(); }, }); ``` ### TanStack Start[​](#tanstack-start "Direct link to TanStack Start") **Example:** [TanStack Start with Convex and Clerk](https://github.com/get-convex/templates/tree/main/template-tanstack-start) See the [TanStack Start with Clerk guide](/client/tanstack/tanstack-start/clerk.md) for more information. ## Next steps[​](#next-steps "Direct link to Next steps") ### Accessing user information in functions[​](#accessing-user-information-in-functions "Direct link to Accessing user information in functions") See [Auth in Functions](/auth/functions-auth.md) to learn about how to access information about the authenticated user in your queries, mutations and actions. See [Storing Users in the Convex Database](/auth/database-auth.md) to learn about how to store user information in the Convex database. ### Accessing user information client-side[​](#accessing-user-information-client-side "Direct link to Accessing user information client-side") To access the authenticated user's information, use Clerk's `User` object, which can be accessed using Clerk's [`useUser()`](https://clerk.com/docs/hooks/use-user) hook. For more information on the `User` object, see the [Clerk docs](https://clerk.com/docs/references/javascript/user). components/Badge.tsx TS ``` export default function Badge() { const { user } = useUser(); return Logged in as {user.fullName}; } ``` ## Configuring dev and prod instances[​](#configuring-dev-and-prod-instances "Direct link to Configuring dev and prod instances") To configure a different Clerk instance between your Convex development and production deployments, you can use environment variables configured on the Convex dashboard. ### Configuring the backend[​](#configuring-the-backend "Direct link to Configuring the backend") In the Clerk Dashboard, navigate to the [**API keys**](https://dashboard.clerk.com/last-active?path=api-keys) page. Copy your Clerk Frontend API URL. This URL is the issuer domain for Clerk's JWT templates, and is necessary for Convex to validate access tokens. In development, it's format will be `https://verb-noun-00.clerk.accounts.dev`. In production, it's format will be `https://clerk..com`. Paste your Clerk Frontend API URL into your `.env` file, set it as the `CLERK_JWT_ISSUER_DOMAIN` environment variable. .env ``` CLERK_JWT_ISSUER_DOMAIN=https://verb-noun-00.clerk.accounts.dev ``` Then, update your `auth.config.ts` file to use the environment variable. convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { domain: process.env.CLERK_JWT_ISSUER_DOMAIN!, applicationID: "convex", }, ], } satisfies AuthConfig; ``` **Development configuration** In the left sidenav of the Convex [dashboard](https://dashboard.convex.dev), switch to your development deployment and set the values for your development Clerk instance. ![Convex dashboard dev deployment settings](/screenshots/clerk-convex-dashboard.png) Then, to switch your deployment to the new configuration, run `npx convex dev`. **Production configuration** In the left sidenav of the Convex [dashboard](https://dashboard.convex.dev), switch to your production deployment and set the values for your production Clerk instance. Then, to switch your deployment to the new configuration, run `npx convex deploy`. ### Configuring Clerk's API keys[​](#configuring-clerks-api-keys "Direct link to Configuring Clerk's API keys") Clerk's API keys differ depending on whether they are for development or production. Don't forget to update the environment variables in your `.env` file as well as your hosting platform, such as Vercel or Netlify. **Development configuration** Clerk's Publishable Key for development follows the format `pk_test_...`. .env.local ``` VITE_CLERK_PUBLISHABLE_KEY="pk_test_..." ``` **Production configuration** Clerk's Publishable Key for production follows the format `pk_live_...`. .env ``` NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_live_..." ``` ## Debugging authentication[​](#debugging-authentication "Direct link to Debugging authentication") If a user goes through the Clerk login flow successfully, and after being redirected back to your page, `useConvexAuth()` returns `isAuthenticated: false`, it's possible that your backend isn't correctly configured. The `auth.config.ts` file contains a list of configured authentication providers. You must run `npx convex dev` or `npx convex deploy` after adding a new provider to sync the configuration to your backend. For more thorough debugging steps, see [Debugging Authentication](/auth/debug.md). ## Under the hood[​](#under-the-hood "Direct link to Under the hood") The authentication flow looks like this under the hood: 1. The user clicks a login button 2. The user is redirected to a page where they log in via whatever method you configure in Clerk 3. After a successful login Clerk redirects back to your page, or a different page which you configure via the [`afterSignIn`](https://clerk.com/docs/authentication/sign-in#override-ur-ls) prop . 4. The `ClerkProvider` now knows that the user is authenticated. 5. The `ConvexProviderWithClerk` fetches an auth token from Clerk . 6. The `ConvexReactClient` passes this token down to your Convex backend to validate 7. Your Convex backend retrieves the public key from Clerk to check that the token's signature is valid. 8. The `ConvexReactClient` is notified of successful authentication, and `ConvexProviderWithClerk` now knows that the user is authenticated with Convex. `useConvexAuth` returns `isAuthenticated: true` and the `Authenticated` component renders its children. `ConvexProviderWithClerk` takes care of refetching the token when needed to make sure the user stays authenticated with your backend. --- # Source: https://docs.convex.dev/cli.md # CLI The Convex command-line interface (CLI) is your interface for managing Convex projects and Convex functions. To install the CLI, run: ``` npm install convex ``` You can view the full list of commands with: ``` npx convex ``` ## Configure[​](#configure "Direct link to Configure") ### Create a new project[​](#create-a-new-project "Direct link to Create a new project") The first time you run ``` npx convex dev ``` it will ask you to log in your device and create a new Convex project. It will then create: 1. The `convex/` directory: This is the home for your query and mutation functions. 2. `.env.local` with `CONVEX_DEPLOYMENT` variable: This is the main configuration for your Convex project. It is the name of your development deployment. ### Recreate project configuration[​](#recreate-project-configuration "Direct link to Recreate project configuration") Run ``` npx convex dev ``` in a project directory without a set `CONVEX_DEPLOYMENT` to configure a new or existing project. ### Log out[​](#log-out "Direct link to Log out") ``` npx convex logout ``` Remove the existing Convex credentials from your device, so subsequent commands like `npx convex dev` can use a different Convex account. ## Develop[​](#develop "Direct link to Develop") ### Run the Convex dev server[​](#run-the-convex-dev-server "Direct link to Run the Convex dev server") ``` npx convex dev ``` Watches the local filesystem. When you change a [function](/functions.md) or the [schema](/database/schemas.md), the new versions are pushed to your dev deployment and the [generated types](/generated-api/.md) in `convex/_generated` are updated. By default, logs from your dev deployment are displayed in the terminal. It's also possible to [run a Convex deployment locally](/cli/local-deployments.md) for development. ### Open the dashboard[​](#open-the-dashboard "Direct link to Open the dashboard") ``` npx convex dashboard ``` Open the [Convex dashboard](/dashboard.md). ### Open the docs[​](#open-the-docs "Direct link to Open the docs") ``` npx convex docs ``` Get back to these docs! ### Run Convex functions[​](#run-convex-functions "Direct link to Run Convex functions") ``` npx convex run [args] ``` Run a public or internal Convex query, mutation, or action on your development deployment. Arguments are specified as a JSON object. ``` npx convex run messages:send '{"body": "hello", "author": "me"}' ``` Add `--watch` to live update the results of a query. Add `--push` to push local code to the deployment before running the function. Use `--prod` to run functions in the production deployment for a project. ### Tail deployment logs[​](#tail-deployment-logs "Direct link to Tail deployment logs") You can choose how to pipe logs from your dev deployment to your console: ``` # Show all logs continuously npx convex dev --tail-logs always # Pause logs during deploys to see sync issues (default) npx convex dev # Don't display logs while developing npx convex dev --tail-logs disable # Tail logs without deploying npx convex logs ``` Use `--prod` with `npx convex logs` to tail the prod deployment logs instead. ### Import data from a file[​](#import-data-from-a-file "Direct link to Import data from a file") ``` npx convex import --table npx convex import .zip ``` See description and use-cases: [data import](/database/import-export/import.md). ### Export data to a file[​](#export-data-to-a-file "Direct link to Export data to a file") ``` npx convex export --path npx convex export --path .zip npx convex export --include-file-storage --path ``` See description and use-cases: [data export](/database/import-export/export.md). ### Display data from tables[​](#display-data-from-tables "Direct link to Display data from tables") ``` npx convex data # lists tables npx convex data ``` Display a simple view of the [dashboard data page](/dashboard/deployments/data.md) in the command line. The command supports `--limit` and `--order` flags to change data displayed. For more complex filters, use the dashboard data page or write a [query](/database/reading-data/.md). The `npx convex data
` command works with [system tables](/database/advanced/system-tables.md), such as `_storage`, in addition to your own tables. ### Read and write environment variables[​](#read-and-write-environment-variables "Direct link to Read and write environment variables") ``` npx convex env list npx convex env get npx convex env set npx convex env remove ``` See and update the deployment environment variables which you can otherwise manage on the dashboard [environment variables settings page](/dashboard/deployments/deployment-settings.md#environment-variables). ## Deploy[​](#deploy "Direct link to Deploy") ### Deploy Convex functions to production[​](#deploy-convex-functions-to-production "Direct link to Deploy Convex functions to production") ``` npx convex deploy ``` The target deployment to push to is determined like this: 1. If the `CONVEX_DEPLOY_KEY` environment variable is set (typical in CI), then it is the deployment associated with that key. 2. If the `CONVEX_DEPLOYMENT` environment variable is set (typical during local development), then the target deployment is the production deployment of the project that the deployment specified by `CONVEX_DEPLOYMENT` belongs to. This allows you to deploy to your prod deployment while developing against your dev deployment. This command will: 1. Run a command if specified with `--cmd`. The command will have CONVEX\_URL (or similar) environment variable available: ``` npx convex deploy --cmd "npm run build" ``` You can customize the URL environment variable name with `--cmd-url-env-var-name`: ``` npx convex deploy --cmd 'npm run build' --cmd-url-env-var-name CUSTOM_CONVEX_URL ``` 2. Typecheck your Convex functions. 3. Regenerate the [generated code](/generated-api/.md) in the `convex/_generated` directory. 4. Bundle your Convex functions and their dependencies. 5. Push your functions, [indexes](/database/reading-data/indexes/.md), and [schema](/database/schemas.md) to production. Once this command succeeds the new functions will be available immediately. ### Deploy Convex functions to a [preview deployment](/production/hosting/preview-deployments.md)[​](#deploy-convex-functions-to-a-preview-deployment "Direct link to deploy-convex-functions-to-a-preview-deployment") ``` npx convex deploy ``` When run with the `CONVEX_DEPLOY_KEY` environment variable containing a [Preview Deploy Key](/cli/deploy-key-types.md#deploying-to-preview-deployments), this command will: 1. Create a new Convex deployment. `npx convex deploy` will infer the Git branch name for Vercel, Netlify, GitHub, and GitLab environments, or the `--preview-create` option can be used to customize the name associated with the newly created deployment. ``` npx convex deploy --preview-create my-branch-name ``` 2. Run a command if specified with `--cmd`. The command will have CONVEX\_URL (or similar) environment variable available: ``` npx convex deploy --cmd "npm run build" ``` You can customize the URL environment variable name with `--cmd-url-env-var-name`: ``` npx convex deploy --cmd 'npm run build' --cmd-url-env-var-name CUSTOM_CONVEX_URL ``` 3. Typecheck your Convex functions. 4. Regenerate the [generated code](/generated-api/.md) in the `convex/_generated` directory. 5. Bundle your Convex functions and their dependencies. 6. Push your functions, [indexes](/database/reading-data/indexes/.md), and [schema](/database/schemas.md) to the deployment. 7. Run a function specified by `--preview-run` (similar to the `--run` option for `npx convex dev`). ``` npx convex deploy --preview-run myFunction ``` See the [Vercel](/production/hosting/vercel.md#preview-deployments) or [Netlify](/production/hosting/netlify.md#deploy-previews) hosting guide for setting up frontend and backend previews together. ### Update generated code[​](#update-generated-code "Direct link to Update generated code") ``` npx convex codegen ``` The [generated code](/generated-api/.md) in the `convex/_generated` directory includes types required for a TypeScript typecheck. This code is generated whenever necessary while running `npx convex dev` and this code should be committed to the repo (your code won't typecheck without it!). In the rare cases it's useful to regenerate code (e.g. in CI to ensure that the correct code was checked it) you can use this command. Generating code can require communicating with a convex deployment in order to evaluate configuration files in the Convex JavaScript runtime. This doesn't modify the code running on the deployment. --- # Source: https://docs.convex.dev/components.md # Components Convex Components package up code and data in a sandbox that allows you to confidently and quickly add new features to your backend. Convex Components are like mini self-contained Convex backends, and installing them is always safe. They can't read your app's tables or call your app's functions unless you pass them in explicitly. You can read about the full vision in [Convex: The Software-Defined Database](https://stack.convex.dev/the-software-defined-database#introducing-convex-components). Components can be installed from NPM or from a local folder. Once installed, they have their own database tables and isolated function execution environment. Check out the full directory of components on the [Convex website](https://convex.dev/components). ## [Understanding components](/components/understanding.md) [Explore the concepts behind and build a mental model for how components work.](/components/understanding.md) ## [Using components](/components/using.md) [Learn about useful components and how to use them in your application.](/components/using.md) ## [Authoring components](/components/authoring.md) [Learn how to write and publish a component.](/components/authoring.md) ## [Components Directory](https://convex.dev/components) [List of all components.](https://convex.dev/components) --- # Source: https://docs.convex.dev/production/contact.md # Contact Us Convex is a rapidly developing platform and we're always eager to hear your feedback. ## Feedback and Support[​](#feedback-and-support "Direct link to Feedback and Support") Please share any general questions, feature requests, or product feedback in our [Convex Discord Community](https://convex.dev/community). We're particularly excited to see what you build on Convex! Any specific support questions that aren't able to be adequately addressed on our Discord channel can be directed to . ## Following Convex[​](#following-convex "Direct link to Following Convex") Release notes are shared on [Convex News](https://news.convex.dev/tag/releases) and the [Convex Discord Community](https://convex.dev/community). Product announcements, articles and demos are posted on [Stack](https://stack.convex.dev/), [News](https://news.convex.dev/), [our YouTube channel](https://www.youtube.com/channel/UCoC_9mdiPwIu1sDxDtGQggQ), and [X (fka Twitter)](https://x.com/convex). ## Vulnerability Disclosure[​](#vulnerability-disclosure "Direct link to Vulnerability Disclosure") If you believe you've discovered a bug in Convex's security, please get in touch at and we'll get back to you within 24 hours. We request that you not publicly disclose the issue until we have had a chance to address it. --- # Source: https://docs.convex.dev/agents/context.md # LLM Context By default, the Agent will provide context based on the message history of the thread. This context is used to generate the next message. The context can include recent messages, as well as messages found via text and /or vector search. If a `promptMessageId` is provided, the context will include that message, as well as any other messages on that same `order`. More details on order are in [messages.mdx](/agents/messages.md#message-ordering), but in practice this means that if you pass the ID of the user-submitted message as the `promptMessageId` and there had already been some assistant and/or tool responses, those will be included in the context, allowing the LLM to continue the conversation. You can also use [RAG](/agents/rag.md) to add extra context to your prompt. ## Customizing the context[​](#customizing-the-context "Direct link to Customizing the context") You can customize the context provided to the agent when generating messages with custom `contextOptions`. These can be set as defaults on the `Agent`, or provided at the call-site for `generateText` or others. ``` const result = await agent.generateText( ctx, { threadId }, { prompt }, { // Values shown are the defaults. contextOptions: { // Whether to exclude tool messages in the context. excludeToolMessages: true, // How many recent messages to include. These are added after the search // messages, and do not count against the search limit. recentMessages: 100, // Options for searching messages via text and/or vector search. searchOptions: { limit: 10, // The maximum number of messages to fetch. textSearch: false, // Whether to use text search to find messages. vectorSearch: false, // Whether to use vector search to find messages. // Note, this is after the limit is applied. // E.g. this will quadruple the number of messages fetched. // (two before, and one after each message found in the search) messageRange: { before: 2, after: 1 }, }, // Whether to search across other threads for relevant messages. // By default, only the current thread is searched. searchOtherThreads: false, }, }, ); ``` ## Full context control[​](#full-context-control "Direct link to Full context control") To have full control over which messages are passed to the LLM, you can either: 1. Provide a `contextHandler` to filter, modify, or enrich the context messages. 2. Provide all messages manually via the `messages` argument and specify `contextOptions` to use no recent or search messages. See below for how to fetch context messages manually. ### Providing a contextHandler[​](#providing-a-contexthandler "Direct link to Providing a contextHandler") The Agent will combine messages from search, recent, input messages, and all messages on the same `order` as the `promptMessageId` if that is provided. You can customize how they are combined, as well as add or remove messages by providing a `contextHandler` which returns the `ModelMessage[]` which will be passed to the LLM. You can specify a `contextHandler` in the Agent constructor, or at the call-site for a single generation, which overrides any Agent default. ``` const myAgent = new Agent(components.agent, { ///... contextHandler: async (ctx, args) => { // This is the default behavior. return [ ...args.search, ...args.recent, ...args.inputMessages, ...args.inputPrompt, ...args.existingResponses, ]; // Equivalent to: return args.allMessages; }, ); ``` With this callback, you can: 1. Filter out messages you don't want to include. 2. Add memories or other context. 3. Add sample messages to guide the LLM on how it should respond. 4. Inject extra context based on the user or thread. 5. Copy in messages from other threads. 6. Summarize messages. For example: ``` // Note: when you specify it at the call-site, you can also leverage variables // available in the scope, e.g. if the user is in a specific step in a workflow. const result = await agent.generateText( ctx, { threadId }, { prompt }, { contextHandler: async (ctx, args) => { // Filter out messages that are not relevant. const relevantSearch = args.search.filter((m) => messageIsRelevant(m)); // Fetch user memories to include in every prompt. const userMemories = await getUserMemories(ctx, args.userId); // Fetch sample messages to instruct the LLM on how to respond. const sampleMessages = [ { role: "user", content: "Generate a function that adds two numbers" }, { role: "assistant", content: "function add(a, b) { return a + b; }" }, ]; // Fetch user context to include in every prompt. const userContext = await getUserContext(ctx, args.userId, args.threadId); // Fetch messages from a related / parent thread. const related = await getRelatedThreadMessages(ctx, args.threadId); return [ // Summarize or truncate context messages if they are too long. ...(await summarizeOrTruncateIfTooLong(related)), ...relevantSearch, ...userMemories, ...sampleMessages, ...userContext, ...args.recent, ...args.inputMessages, ...args.inputPrompt, ...args.existingResponses, ]; }, }, ); ``` ### Fetch context manually[​](#fetch-context-manually "Direct link to Fetch context manually") If you want to get context messages for a given prompt, without calling the LLM, you can use `fetchContextWithPrompt`. This is used internally to get the context messages passed to the AI SDK `generateText`, `streamText`, etc. As with normal generation, you can provide a `prompt` or `messages`, and/or a `promptMessageId` to fetch the context messages using a given pre-saved message as the prompt. This will return recent and search messages combined with the input messages. ``` import { fetchContextWithPrompt } from "@convex-dev/agent"; const { messages } = await fetchContextWithPrompt(ctx, components.agent, { prompt, messages, promptMessageId, userId, threadId, contextOptions, }); ``` ## Search for messages[​](#search-for-messages "Direct link to Search for messages") This is what the agent does automatically, but it can be useful to do manually, e.g. to find custom context to include. For text and vector search, you can provide a `targetMessageId` and/or `searchText`. It will embed the text for vector search. If `searchText` is not provided, it will use the target message's text. If `targetMessageId` is provided, it will only fetch search messages previous to that message, and recent messages up to and including that message's "order". This enables re-generating a response for an earlier message. ``` import type { MessageDoc } from "@convex-dev/agent"; const messages: MessageDoc[] = await agent.fetchContextMessages(ctx, { threadId, searchText: prompt, // Optional unless you want text/vector search. targetMessageId: promptMessageId, // Optionally target the search. userId, // Optional, unless `searchOtherThreads` is true. contextOptions, // Optional, defaults are used if not provided. }); ``` Note: you can also search for messages without an agent. The main difference is that in order to do vector search, you need to create the embeddings yourself, and it will not run your usage handler. ``` import { fetchRecentAndSearchMessages } from "@convex-dev/agent"; const { recentMessages, searchMessages } = await fetchRecentAndSearchMessages( ctx, components.agent, { threadId, searchText: prompt, // Optional unless you want text/vector search. targetMessageId: promptMessageId, // Optionally target the search. contextOptions, // Optional, defaults are used if not provided. getEmbedding: async (text) => { const embedding = await textEmbeddingModel.embed(text); return { embedding, textEmbeddingModel }; }, }, ); ``` ## Searching other threads[​](#searching-other-threads "Direct link to Searching other threads") If you set `searchOtherThreads` to `true`, the agent will search across all threads belonging to the provided `userId`. This can be useful to have multiple conversations that the Agent can reference. The search will use a hybrid of text and vector search. ## Passing in messages as context[​](#passing-in-messages-as-context "Direct link to Passing in messages as context") You can pass in messages as context to the Agent's LLM, for instance to implement [Retrieval-Augmented Generation](/agents/rag.md). The final messages sent to the LLM will be: 1. The system prompt, if one is provided or the agent has `instructions` 2. The messages found via contextOptions 3. The `messages` argument passed into `generateText` or other function calls. 4. If a `prompt` argument was provided, a final `{ role: "user", content: prompt }` message. This allows you to pass in messages that are not part of the thread history and will not be saved automatically, but that the LLM will receive as context. ## Manage embeddings manually[​](#manage-embeddings-manually "Direct link to Manage embeddings manually") The `textEmbeddingModel` argument to the Agent constructor allows you to specify a text embedding model to use for vector search. If you set this, the agent will automatically generate embeddings for messages and use them for vector search. When you change models or decide to start or stop using embeddings for vector search, you can manage the embeddings manually. Generate embeddings for a set of messages. Optionally pass `config` with a usage handler, which can be a globally shared `Config`. ``` import { embedMessages } from "@convex-dev/agent"; const embeddings = await embedMessages( ctx, { userId, threadId, textEmbeddingModel, ...config }, [{ role: "user", content: "What is love?" }], ); ``` Generate and save embeddings for existing messages. ``` const embeddings = await supportAgent.generateAndSaveEmbeddings(ctx, { messageIds, }); ``` Get and update embeddings, e.g. for a migration to a new model. ``` const messages = await ctx.runQuery(components.agent.vector.index.paginate, { vectorDimension: 1536, targetModel: "gpt-4o-mini", cursor: null, limit: 10, }); ``` Updating the embedding by ID. ``` const messages = await ctx.runQuery(components.agent.vector.index.updateBatch, { vectors: [{ model: "gpt-4o-mini", vector: embedding, id: msg.embeddingId }], }); ``` Note: If the dimension changes, you need to delete the old and insert the new. Delete embeddings ``` await ctx.runMutation(components.agent.vector.index.deleteBatch, { ids: [embeddingId1, embeddingId2], }); ``` Insert embeddings ``` const ids = await ctx.runMutation(components.agent.vector.index.insertBatch, { vectorDimension: 1536, vectors: [ { model: "gpt-4o-mini", table: "messages", userId: "123", threadId: "123", vector: embedding, // Optional, if you want to update the message with the embeddingId messageId: messageId, }, ], }); ``` --- # Source: https://docs.convex.dev/auth/convex-auth.md # Convex Auth [Convex Auth](https://labs.convex.dev/auth) is a library for implementing authentication directly within your Convex backend. This allows you to authenticate users without needing an authentication service or even a hosting server. Convex Auth currently supports client-side React web apps served from a CDN and React Native mobile apps. **Example:** [Live Demo](https://labs.convex.dev/auth-example) ([Source](https://github.com/get-convex/convex-auth-example)) Convex Auth is in beta Convex Auth is currently a [beta feature](/production/state/.md#beta-features). If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community)! Support for [authentication in Next.js](https://labs.convex.dev/auth/authz/nextjs) server components, API routes, middleware, SSR etc. is under active development. If you'd like to help test this experimental support please [let us know how it goes in Discord](https://convex.dev/community). ## Get Started[​](#get-started "Direct link to Get Started") To start a new project from scratch with Convex and Convex Auth, run: ``` npm create convex@latest ``` and choose `React (Vite)` and `Convex Auth`. *** To add Convex Auth to an existing project, follow the full [setup guide](https://labs.convex.dev/auth/setup). ## Overview[​](#overview "Direct link to Overview") Convex Auth enables you to implement the following authentication methods: 1. Magic Links & OTPs - send a link or code via email 2. OAuth - sign in with GitHub / Google / Apple etc. 3. Passwords - including password reset flow and optional email verification The library doesn't come with UI components, but you can copy code from the docs and example repo to quickly build a UI in React. Learn more in the [Convex Auth docs](https://labs.convex.dev/auth). --- # Source: https://docs.convex.dev/testing/convex-backend.md # Testing Local Backend Alternatively to [`convex-test`](/testing/convex-test.md) you can test your functions using the [open-source version of the Convex backend](https://github.com/get-convex/convex-backend). ## Getting Started[​](#getting-started "Direct link to Getting Started") Follow [this guide](https://stack.convex.dev/testing-with-local-oss-backend) for the instructions. Compared to `convex-test`, which uses a JS mock of the backend, running your tests against the real backend has these advantages: * Your tests will run against the same code as your Convex production (as long you keep the local backend up-to-date). * Limits on argument, data, query sizes are enforced. * You can bootstrap a large test dataset from a data import. * You can test your client code in combination with your backend logic. ## Limitations[​](#limitations "Direct link to Limitations") Note that testing against the local backend also has some drawbacks: * It requires setting up the local backend, which is more involved. * No control over time and any scheduled functions will run as scheduled. * Crons will also run unless disabled via [`IS_TEST`](https://stack.convex.dev/testing-with-local-oss-backend#setting-up-a-local-backend). * No way to mock `fetch` calls. * No way to mock dependencies or parts of the codebase. * No way to control randomness (tests may not be deterministic). * No way to set environment variable values from within tests. To test your functions in JS with a mocked Convex backend, check out [convex-test](/testing/convex-test.md). ## CI[​](#ci "Direct link to CI") See [Continuous Integration](/testing/ci.md) to run your tests on a shared remote machine. --- # Source: https://docs.convex.dev/deployment-api/convex-deployment-api.md Version: 1.0.0 # Convex Deployment API Admin API for interacting with deployments. ## Authentication[​](#authentication "Direct link to Authentication") * API Key: Deploy Key * API Key: OAuth Project Token * API Key: OAuth Team Token * API Key: Team Token Deploy keys are used for deployment operations. See [deploy key types](https://docs.convex.dev/cli/deploy-key-types) for more information. Use the `Convex `prefix (e.g., `Convex `). | Security Scheme Type: | apiKey | | ---------------------- | ------------- | | Header parameter name: | Authorization | Obtained through a [Convex OAuth application](https://docs.convex.dev/management-api) with project scope. Use the `Convex `prefix (e.g., `Convex `). | Security Scheme Type: | apiKey | | ---------------------- | ------------- | | Header parameter name: | Authorization | Obtained through a [Convex OAuth application](https://docs.convex.dev/management-api). Use the `Convex `prefix (e.g., `Convex `). | Security Scheme Type: | apiKey | | ---------------------- | ------------- | | Header parameter name: | Authorization | Created in the dashboard under team settings for any team you can manage. Use the `Convex `prefix (e.g., `Convex `). | Security Scheme Type: | apiKey | | ---------------------- | ------------- | | Header parameter name: | Authorization | ### License --- # Source: https://docs.convex.dev/management-api/convex-management-api.md Version: 1.0.0 # Convex Management API Management API for provisioning and managing Convex projects and deployments. ## Authentication[​](#authentication "Direct link to Authentication") * HTTP: Bearer Auth * HTTP: Bearer Auth * HTTP: Bearer Auth Obtained through a [Convex OAuth application](https://docs.convex.dev/management-api). | Security Scheme Type: | http | | -------------------------- | ------ | | HTTP Authorization Scheme: | bearer | Obtained through a [Convex OAuth application](https://docs.convex.dev/management-api). | Security Scheme Type: | http | | -------------------------- | ------ | | HTTP Authorization Scheme: | bearer | Created in the dashboard under team settings for any team you can manage. | Security Scheme Type: | http | | -------------------------- | ------ | | HTTP Authorization Scheme: | bearer | ### License --- # Source: https://docs.convex.dev/ai/convex-mcp-server.md # Convex MCP Server The Convex [Model Context Protocol](https://docs.cursor.com/context/model-context-protocol) (MCP) server provides several tools that allow AI agents to interact with your Convex deployment. ## Setup[​](#setup "Direct link to Setup") Add the following command to your MCP servers configuration: `npx -y convex@latest mcp start` For Cursor you can use this quick link to install: [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=convex\&config=eyJjb21tYW5kIjoibnB4IC15IGNvbnZleEBsYXRlc3QgbWNwIHN0YXJ0In0%3D) or see editor specific instructions: * [Cursor](/ai/using-cursor.md#setup-the-convex-mcp-server) * [Windsurf](/ai/using-windsurf.md#setup-the-convex-mcp-server) * [VS Code](/ai/using-github-copilot.md#setup-the-convex-mcp-server) * Claude Code: add the MCP server and test with ``` claude mcp add-json convex '{"type":"stdio","command":"npx","args":["convex","mcp","start"]}' claude mcp get convex ``` ## Configuration Options[​](#configuration-options "Direct link to Configuration Options") The MCP server supports several command-line options to customize its behavior: ### Project Directory[​](#project-directory "Direct link to Project Directory") By default, the MCP server can run for multiple projects, and each tool call specifies its project directory. To run the server for a single project instead, use: ``` npx -y convex@latest mcp start --project-dir /path/to/project ``` ### Deployment Selection[​](#deployment-selection "Direct link to Deployment Selection") By default, the MCP server connects to your development deployment. You can specify a different deployment using these options: * `--prod`: Run the MCP server on your project's production deployment (requires `--dangerously-enable-production-deployments`) * `--preview-name `: Run on a preview deployment with the given name * `--deployment-name `: Run on a specific deployment by name * `--env-file `: Path to a custom environment file for choosing the deployment (e.g., containing `CONVEX_DEPLOYMENT` or `CONVEX_SELF_HOSTED_URL`). Uses the same format as `.env.local` or `.env` files. ### Production Deployments[​](#production-deployments "Direct link to Production Deployments") By default, the MCP server cannot access production deployments. This is a safety measure to prevent accidental modifications to production data. If you need to access production deployments, you must explicitly enable this: ``` npx -y convex@latest mcp start --dangerously-enable-production-deployments ``` Use with care Enabling production access allows the MCP server to read and modify data in your production deployment. Only enable this when you specifically need to interact with production, and be careful with any operations that modify data. ### Disabling Tools[​](#disabling-tools "Direct link to Disabling Tools") You can disable specific tools if you want to restrict what the MCP server can do: ``` npx -y convex@latest mcp start --disable-tools data,run,envSet ``` Available tools that can be disabled: `data`, `envGet`, `envList`, `envRemove`, `envSet`, `functionSpec`, `logs`, `run`, `runOneoffQuery`, `status`, `tables` ## Available Tools[​](#available-tools "Direct link to Available Tools") ### Deployment Tools[​](#deployment-tools "Direct link to Deployment Tools") * **`status`**: Queries available deployments and returns a deployment selector that can be used with other tools. This is typically the first tool you'll use to find your Convex deployment. ### Table Tools[​](#table-tools "Direct link to Table Tools") * **`tables`**: Lists all tables in a deployment along with their: * Declared schemas (if present) * Inferred schemas (automatically tracked by Convex) * Table names and metadata * **`data`**: Allows pagination through documents in a specified table. * **`runOneoffQuery`**: Enables writing and executing sandboxed JavaScript queries against your deployment's data. These queries are read-only and cannot modify the database. ### Function Tools[​](#function-tools "Direct link to Function Tools") * **`functionSpec`**: Provides metadata about all deployed functions, including: * Function types * Visibility settings * Interface specifications * **`run`**: Executes deployed Convex functions with provided arguments. * **`logs`**: Fetches a chunk of recent function execution log entries, similar to `npx convex logs` but as structured objects. ### Environment Variable Tools[​](#environment-variable-tools "Direct link to Environment Variable Tools") * **`envList`**: Lists all environment variables for a deployment * **`envGet`**: Retrieves the value of a specific environment variable * **`envSet`**: Sets a new environment variable or updates an existing one * **`envRemove`**: Removes an environment variable from the deployment [Read more about how to use the Convex MCP Server](https://stack.convex.dev/convex-mcp-server) --- # Source: https://docs.convex.dev/public-deployment-api/convex-public-http-routes.md Version: 1.0.0 # Convex Public HTTP routes Endpoints that require no authentication ### License --- # Source: https://docs.convex.dev/testing/convex-test.md # convex-test The [`convex-test`](https://www.npmjs.com/package/convex-test) library provides a mock implementation of the Convex backend in JavaScript. It enables fast automated testing of the logic in your [functions](/functions.md). ## Example[​](#example "Direct link to Example") convex/posts.test.ts TS ``` import { convexTest } from "convex-test"; import { describe, it, expect } from "vitest"; import { api, internal } from "./_generated/api"; import schema from "./schema"; describe("posts.list", () => { it("returns empty array when no posts exist", async () => { const t = convexTest(schema, modules); // Initially, there are no posts, so `list` returns an empty array const posts = await t.query(api.posts.list); expect(posts).toEqual([]); }); it("returns all posts ordered by creation time when there are posts", async () => { const t = convexTest(schema, modules); // Create some posts await t.mutation(internal.posts.add, { title: "First Post", content: "This is the first post", author: "Alice", }); await t.mutation(internal.posts.add, { title: "Second Post", content: "This is the second post", author: "Bob", }); // `list` returns all posts ordered by creation time const posts = await t.query(api.posts.list); expect(posts).toHaveLength(2); expect(posts[0].title).toBe("Second Post"); expect(posts[1].title).toBe("First Post"); }); }); const modules = import.meta.glob("./**/*.ts"); ``` You can see more examples in the [test suite](https://github.com/get-convex/convex-test/tree/main/convex) of the convex-test library. ## Get started[​](#get-started "Direct link to Get started") 1. Install test dependencies Install [Vitest](https://vitest.dev/) and the [`convex-test`](https://www.npmjs.com/package/convex-test) library. ``` npm install --save-dev convex-test vitest @edge-runtime/vm ``` 2. Setup NPM scripts Add these scripts to your `package.json` package.json ``` "scripts": { "test": "vitest", "test:once": "vitest run", "test:debug": "vitest --inspect-brk --no-file-parallelism", "test:coverage": "vitest run --coverage --coverage.reporter=text", } ``` 3. Configure Vitest Add `vitest.config.ts` file to configure the test environment to better match the Convex runtime, and to inline the test library for better dependency tracking. If your Convex functions are in a directory other than `convex` If your project has a [different name or location configured](/production/project-configuration.md#changing-the-convex-folder-name-or-location) for the `convex/` folder in `convex.json`, you need to call [`import.meta.glob`](https://vitejs.dev/guide/features#glob-import) and pass the result as the second argument to `convexTest`. The argument to `import.meta.glob` must be a glob pattern matching all the files containing your Convex functions. The paths are relative to the test file in which `import.meta.glob` is called. It's best to do this in one place in your custom functions folder: src/convex/test.setup.ts TS ``` /// export const modules = import.meta.glob( "./**/!(*.*.*)*.*s" ); ``` This example glob pattern includes all files with a single extension ending in `s` (like `js` or `ts`) in the `src/convex` folder and any of its children. Use the result in your tests: src/convex/messages.test.ts TS ``` import { convexTest } from "convex-test"; import { test } from "vitest"; import schema from "./schema"; import { modules } from "./test.setup"; test("some behavior", async () => { const t = convexTest(schema, modules); // use `t`... }); ``` Set up multiple test environments (e.g. Convex + frontend) If you want to use Vitest to test both your Convex functions and your React frontend, you might want to use multiple Vitest environments depending on the test file location via [environmentMatchGlobs](https://vitest.dev/config/#environmentmatchglobs): vitest.config.ts TS ``` import { defineConfig } from "vitest/config"; export default defineConfig({ test: { environmentMatchGlobs: [ // all tests in convex/ will run in edge-runtime ["convex/**", "edge-runtime"], // all other tests use jsdom ["**", "jsdom"], ], server: { deps: { inline: ["convex-test"] } }, }, }); ``` vitest.config.ts TS ``` import { defineConfig } from "vitest/config"; export default defineConfig({ test: { environment: "edge-runtime", server: { deps: { inline: ["convex-test"] } }, }, }); ``` 4. Add a test file In your `convex` folder add a file ending in `.test.ts` The example test calls the `api.messages.send` mutation twice and then asserts that the `api.messages.list` query returns the expected results. convex/messages.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; test("sending messages", async () => { const t = convexTest(schema); await t.mutation(api.messages.send, { body: "Hi!", author: "Sarah" }); await t.mutation(api.messages.send, { body: "Hey!", author: "Tom" }); const messages = await t.query(api.messages.list); expect(messages).toMatchObject([ { body: "Hi!", author: "Sarah" }, { body: "Hey!", author: "Tom" } ]); }); ``` 5. Run tests Start the tests with `npm run test`. When you change the test file or your functions the tests will rerun automatically. ``` npm run test ``` If you're not familiar with Vitest, read the [Vitest Getting Started docs](https://vitest.dev/guide) first. ## Using convex-test[​](#using-convex-test "Direct link to Using convex-test") ### Initialize `convexTest`[​](#initialize-convextest "Direct link to initialize-convextest") The library exports a `convexTest` function which should be called at the start of each of your tests. The function returns an object which is by convention stored in the `t` variable and which provides methods for exercising your Convex functions. If your project uses a [schema](/database/schemas.md) you should pass it to the `convexTest` function: convex/myFunctions.test.ts TS ``` import { convexTest } from "convex-test"; import { test } from "vitest"; import schema from "./schema"; test("some behavior", async () => { const t = convexTest(schema); // use `t`... }); ``` Passing in the schema is required for the tests to correctly implement schema validation and for correct typing of [`t.run`](#setting-up-and-inspecting-data-and-storage-with-trun). If you don't have a schema, call `convexTest()` with no argument. ### Call functions[​](#call-functions "Direct link to Call functions") Your test can call public and internal Convex [functions](/functions.md) in your project: convex/myFunctions.test.ts TS ``` import { convexTest } from "convex-test"; import { test } from "vitest"; import { api, internal } from "./_generated/api"; test("functions", async () => { const t = convexTest(); const x = await t.query(api.myFunctions.myQuery, { a: 1, b: 2 }); const y = await t.query(internal.myFunctions.internalQuery, { a: 1, b: 2 }); const z = await t.mutation(api.myFunctions.mutateSomething, { a: 1, b: 2 }); const w = await t.mutation(internal.myFunctions.mutateSomething, { a: 1 }); const u = await t.action(api.myFunctions.doSomething, { a: 1, b: 2 }); const v = await t.action(internal.myFunctions.internalAction, { a: 1, b: 2 }); }); ``` ### Modify data outside of functions[​](#modify-data-outside-of-functions "Direct link to Modify data outside of functions") Sometimes you might want to directly [write](/database/writing-data.md) to the mock database or [file storage](/file-storage.md) from your test, without needing a declared function in your project. You can use the `t.run` method which takes a handler that is given a `ctx` that allows reading from and writing to the mock backend: convex/tasks.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import schema from "./schema"; test("functions", async () => { const t = convexTest(schema, modules); const firstTask = await t.run(async (ctx) => { await ctx.db.insert("tasks", { text: "Eat breakfast" }); return await ctx.db.query("tasks").first(); }); expect(firstTask).toMatchObject({ text: "Eat breakfast" }); }); const modules = import.meta.glob("./**/*.ts"); ``` ### HTTP actions[​](#http-actions "Direct link to HTTP actions") Your test can call [HTTP actions](/functions/http-actions.md) registered by your router: convex/http.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import schema from "./schema"; test("functions", async () => { const t = convexTest(schema, modules); const response = await t.fetch("/some/path", { method: "POST" }); expect(response.status).toBe(200); }); const modules = import.meta.glob("./**/*.ts"); ``` Mocking the global `fetch` function doesn't affect `t.fetch`, but you can use `t.fetch` in a `fetch` mock to route to your HTTP actions. ### Scheduled functions[​](#scheduled-functions "Direct link to Scheduled functions") One advantage of using a mock implementation running purely in JavaScript is that you can control time in the Vitest test environment. To test implementations relying on [scheduled functions](/scheduling/scheduled-functions.md) use [Vitest's fake timers](https://vitest.dev/guide/mocking.html#timers) in combination with `t.finishInProgressScheduledFunctions`: convex/scheduling.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test, vi } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; test("mutation scheduling action", async () => { // Enable fake timers vi.useFakeTimers(); const t = convexTest(schema, modules); // Call a function that schedules a mutation or action const scheduledFunctionId = await t.mutation( api.scheduler.mutationSchedulingAction, { delayMs: 10000 }, ); // Advance the mocked time vi.advanceTimersByTime(5000); // Advance the mocked time past the scheduled time of the function vi.advanceTimersByTime(6000); // Or run all currently pending timers vi.runAllTimers(); // At this point the scheduled function will be `inProgress`, // now wait for it to finish await t.finishInProgressScheduledFunctions(); // Assert that the scheduled function succeeded or failed const scheduledFunctionStatus = await t.run(async (ctx) => { return await ctx.db.system.get("_scheduled_functions", scheduledFunctionId); }); expect(scheduledFunctionStatus).toMatchObject({ state: { kind: "success" } }); // Reset to normal `setTimeout` etc. implementation vi.useRealTimers(); }); const modules = import.meta.glob("./**/*.ts"); ``` If you have a chain of several scheduled functions, for example a mutation that schedules an action that schedules another action, you can use `t.finishAllScheduledFunctions` to wait for all scheduled functions, including recursively scheduled functions, to finish: convex/chainedScheduling.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test, vi } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; test("mutation scheduling action scheduling action", async () => { // Enable fake timers vi.useFakeTimers(); const t = convexTest(schema, modules); // Call a function that schedules a mutation or action await t.mutation(api.scheduler.mutationSchedulingActionSchedulingAction); // Wait for all scheduled functions, repeatedly // advancing time and waiting for currently in-progress // functions to finish await t.finishAllScheduledFunctions(vi.runAllTimers); // Assert the resulting state after all scheduled functions finished const createdTask = await t.run(async (ctx) => { return await ctx.db.query("tasks").first(); }); expect(createdTask).toMatchObject({ author: "AI" }); // Reset to normal `setTimeout` etc. implementation vi.useRealTimers(); }); const modules = import.meta.glob("./**/*.ts"); ``` Check out more examples in [this file](https://github.com/get-convex/convex-test/blob/main/convex/scheduler.test.ts). ### Authentication[​](#authentication "Direct link to Authentication") To test functions which depend on the current [authenticated](/auth.md) user identity you can create a version of the `t` accessor with given [user identity attributes](/api/interfaces/server.UserIdentity.md). If you don't provide them, `issuer`, `subject` and `tokenIdentifier` will be generated automatically: convex/tasks.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; test("authenticated functions", async () => { const t = convexTest(schema, modules); const asSarah = t.withIdentity({ name: "Sarah" }); await asSarah.mutation(api.tasks.create, { text: "Add tests" }); const sarahsTasks = await asSarah.query(api.tasks.list); expect(sarahsTasks).toMatchObject([{ text: "Add tests" }]); const asLee = t.withIdentity({ name: "Lee" }); const leesTasks = await asLee.query(api.tasks.list); expect(leesTasks).toEqual([]); }); const modules = import.meta.glob("./**/*.ts"); ``` ## Vitest tips[​](#vitest-tips "Direct link to Vitest tips") ### Asserting results[​](#asserting-results "Direct link to Asserting results") See Vitest's [Expect](https://vitest.dev/api/expect.html) reference. [`toMatchObject()`](https://vitest.dev/api/expect.html#tomatchobject) is particularly helpful when asserting the shape of results without needing to list every object field. ### Asserting errors[​](#asserting-errors "Direct link to Asserting errors") To assert that a function throws, use [`.rejects.toThrowError()`](https://vitest.dev/api/expect.html#tothrowerror): convex/messages.test.ts TS ``` import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; test("messages validation", async () => { const t = convexTest(schema, modules); await expect(async () => { await t.mutation(api.messages.send, { body: "", author: "James" }); }).rejects.toThrowError("Empty message body is not allowed"); }); const modules = import.meta.glob("./**/*.ts"); ``` ### Mocking `fetch` calls[​](#mocking-fetch-calls "Direct link to mocking-fetch-calls") You can use Vitest's [vi.stubGlobal](https://vitest.dev/guide/mocking.html#globals) method: convex/ai.test.ts TS ``` import { expect, test, vi } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; import { convexTest } from "convex-test"; test("ai", async () => { const t = convexTest(schema, modules); vi.stubGlobal( "fetch", vi.fn(async () => ({ text: async () => "I am the overlord" }) as Response), ); const reply = await t.action(api.messages.sendAIMessage, { prompt: "hello" }); expect(reply).toEqual("I am the overlord"); vi.unstubAllGlobals(); }); const modules = import.meta.glob("./**/*.ts"); ``` ### Measuring test coverage[​](#measuring-test-coverage "Direct link to Measuring test coverage") You can get a printout of the code coverage provided by your tests. Besides answering the question "how much of my code is covered by tests" it is also helpful to check that your test is actually exercising the code that you want it to exercise. Run `npm run test:coverage` `` . It will ask you to install a required dependency the first time you run it. ![example coverage printout](/screenshots/testing_coverage.png) ### Debugging tests[​](#debugging-tests "Direct link to Debugging tests") You can attach a debugger to the running tests. Read the Vitest [Debugging docs](https://vitest.dev/guide/debugging.html) and then use `npm run test:debug` `` . ## Limitations[​](#limitations "Direct link to Limitations") Since `convex-test` is only a mock implementation, it doesn't have many of the behaviors of the real Convex backend. Still, it should be helpful for testing the logic in your functions, and catching regressions caused by changes to your code. Some of the ways the mock differs: * Error messages content. You should not write product logic that relies on the content of error messages thrown by the real backend, as they are always subject to change. * Limits. The mock doesn't enforce size and time [limits](/production/state/limits.md). * ID format. Your code should not depend on the document or storage ID format. * Runtime built-ins. Most of your functions are written for the [Convex default runtime](/functions/runtimes.md), while Vitest uses a mock of Vercel's Edge Runtime, which is similar but might differ from the Convex runtime. You should always test new code manually to make sure it doesn't use built-ins not available in the Convex runtime. * Some features have only simplified semantics, namely: * [Text search](/search.md) returns all documents that include a word for which at least one word in the searched string is a prefix. It does not sort the results by relevance. * [Vector search](/search/vector-search.md) returns results sorted by cosine similarity, but doesn't use an efficient vector index in its implementation. * There is no support for [cron jobs](/scheduling/cron-jobs.md), you should trigger your functions manually from the test. To test your functions running on a real Convex backend, check out [Testing Local Backend](/testing/convex-backend.md). ## CI[​](#ci "Direct link to CI") See [Continuous Integration](/testing/ci.md) to run your tests on a shared remote machine. --- # Source: https://docs.convex.dev/management-api/create-custom-domain.md # Create custom domain ``` POST /deployments/:deployment_name/create_custom_domain ``` Create custom domain ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/management-api/create-deploy-key.md # Create deploy key ``` POST /deployments/:deployment_name/create_deploy_key ``` Create a deploy key like "dev:happy-animal-123|ey..." which can be used with the Convex CLI to develop against or deploy code. When access to the deployment is granted through an OAuth token this deploy key will use the same OAuth-granted token. When access to the deployment is granted any other way a new token will be created which grants access only to this deployment. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/management-api/create-deployment.md # Create deployment ``` POST /projects/:project_id/create_deployment ``` Create a new deployment for a project. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/deployment-api/create-log-stream.md # Create log stream ``` POST /create_log_stream ``` Create a new log stream for the deployment. Errors if a log stream of the given type already exists. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/management-api/create-project.md # Create project ``` POST /teams/:team_id/create_project ``` Create a new project on a team and provision a dev or prod deployment. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/scheduling/cron-jobs.md # Cron Jobs Convex allows you to schedule functions to run on a recurring basis. For example, cron jobs can be used to clean up data at a regular interval, send a reminder email at the same time every month, or schedule a backup every Saturday. **Example:** [Cron Jobs](https://github.com/get-convex/convex-demos/tree/main/cron-jobs) ## Defining your cron jobs[​](#defining-your-cron-jobs "Direct link to Defining your cron jobs") Cron jobs are defined in a `crons.ts` file in your `convex/` directory and look like: convex/crons.ts TS ``` import { cronJobs } from "convex/server"; import { internal } from "./_generated/api"; const crons = cronJobs(); crons.interval( "clear messages table", { minutes: 1 }, // every minute internal.messages.clearAll, ); crons.monthly( "payment reminder", { day: 1, hourUTC: 16, minuteUTC: 0 }, // Every month on the first day at 8:00am PST internal.payments.sendPaymentEmail, { email: "my_email@gmail.com" }, // argument to sendPaymentEmail ); // An alternative way to create the same schedule as above with cron syntax crons.cron( "payment reminder duplicate", "0 16 1 * *", internal.payments.sendPaymentEmail, { email: "my_email@gmail.com" }, // argument to sendPaymentEmail ); export default crons; ``` The first argument is a unique identifier for the cron job. The second argument is the schedule at which the function should run, see [Supported schedules](/scheduling/cron-jobs.md#supported-schedules) below. The third argument is the name of the public function or [internal function](/functions/internal-functions.md), either a [mutation](/functions/mutation-functions.md) or an [action](/functions/actions.md). ## Supported schedules[​](#supported-schedules "Direct link to Supported schedules") * [`crons.interval()`](/api/classes/server.Crons.md#interval) runs a function every specified number of `seconds`, `minutes`, or `hours`. The first run occurs when the cron job is first deployed to Convex. Unlike traditional crons, this option allows you to have seconds-level granularity. * [`crons.cron()`](/api/classes/server.Crons.md#cron) the traditional way of specifying cron jobs by a string with five fields separated by spaces (e.g. `"* * * * *"`). Times in cron syntax are in the UTC timezone. [Crontab Guru](https://crontab.guru/) is a helpful resource for understanding and creating schedules in this format. * [`crons.hourly()`](/api/classes/server.Crons.md#cron), [`crons.daily()`](/api/classes/server.Crons.md#daily), [`crons.weekly()`](/api/classes/server.Crons.md#weekly), [`crons.monthly()`](/api/classes/server.Crons.md#monthly) provide an alternative syntax for common cron schedules with explicitly named arguments. ## Viewing your cron jobs[​](#viewing-your-cron-jobs "Direct link to Viewing your cron jobs") You can view all your cron jobs in the [Convex dashboard cron jobs view](/dashboard/deployments/schedules.md#cron-jobs-ui). You can view added, updated, and deleted cron jobs in the logs and history view. Results of previously executed runs of the cron jobs are also available in the logs view. ## Error handling[​](#error-handling "Direct link to Error handling") Mutations and actions have the same guarantees that are described in [Error handling](/scheduling/scheduled-functions.md#error-handling) for scheduled functions. At most one run of each cron job can be executing at any moment. If the function scheduled by the cron job takes too long to run, following runs of the cron job may be skipped to avoid execution from falling behind. Skipping a scheduled run of a cron job due to the previous run still executing logs a message visible in the logs view of the dashboard. --- # Source: https://docs.convex.dev/auth/advanced/custom-auth.md # Custom OIDC Provider **Note: This is an advanced feature!** We recommend sticking with the [supported third-party authentication providers](/auth.md). Convex can be integrated with any identity provider supporting the [OpenID Connect](https://openid.net/connect/) protocol. At minimum this means that the provider can issue [ID tokens](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) and exposes the corresponding [JWKS](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets). The ID token is passed from the client to your Convex backend which ensures that the token is valid and enables you to query the user information embedded in the token, as described in [Auth in Functions](/auth/functions-auth.md). ## Server-side integration[​](#server-side-integration "Direct link to Server-side integration") Just like with [Clerk](/auth/clerk.md) and [Auth0](/auth/auth0.md), the backend needs to be aware of the domain of the Issuer and your application's specific applicationID for a given identity provider. Add these to your `convex/auth.config.ts` file: convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { domain: "https://your.issuer.url.com", applicationID: "your-application-id", }, ], } satisfies AuthConfig; ``` The `applicationID` property must exactly match the `aud` field of your JWT and the `domain` property must exactly match the `iss` field of the JWT. Use a tool like [jwt.io](https://jwt.io/) to view an JWT and confirm these fields match exactly. If multiple providers are provided, the first one fulfilling the above criteria will be used. If you're not able to obtain tokens with an `aud` field, you'll need to instead configure a [Custom JWT](/auth/advanced/custom-jwt.md). If you're not sure if your token is an OIDC ID token, check [the spec](https://openid.net/specs/openid-connect-core-1_0-final.html#rfc.section.2) for a list of all required fields. OIDC requires the routes `${domain}/.well-known/jwks.json` and `${domain}/.well-known/openid-configuration`. `domain` may include a path like `https://your.issuer.url.com/api/auth`. This isn't common for third party auth providers but may be useful if you're implementing OIDC on your own server. ## Client-side integration[​](#client-side-integration "Direct link to Client-side integration") ### Integrating a new identity provider[​](#integrating-a-new-identity-provider "Direct link to Integrating a new identity provider") The [`ConvexProviderWithAuth`](/api/modules/react.md#convexproviderwithauth) component provides a convenient abstraction for building an auth integration similar to the ones Convex provides for [Clerk](/auth/clerk.md) and [Auth0](/auth/auth0.md). In the following example we build an integration with an imaginary "ProviderX", whose React integration includes `AuthProviderXReactProvider` and `useProviderXAuth` hook. First we replace `ConvexProvider` with `AuthProviderXReactProvider` wrapping `ConvexProviderWithAuth` at the root of our app: src/index.js ``` import { AuthProviderXReactProvider } from "providerX"; import { ConvexProviderWithAuth } from "convex/react"; root.render( , ); ``` All we really need is to implement the `useAuthFromProviderX` hook which gets passed to the `ConvexProviderWithAuth` component. This `useAuthFromProviderX` hook provides a translation between the auth provider API and the [`ConvexReactClient`](/api/classes/react.ConvexReactClient.md) API, which is ultimately responsible for making sure that the ID token is passed down to your Convex backend. src/ConvexProviderWithProviderX.js ``` function useAuthFromProviderX() { const { isLoading, isAuthenticated, getToken } = useProviderXAuth(); const fetchAccessToken = useCallback( async ({ forceRefreshToken }) => { // Here you can do whatever transformation to get the ID Token // or null // Make sure to fetch a new token when `forceRefreshToken` is true return await getToken({ ignoreCache: forceRefreshToken }); }, // If `getToken` isn't correctly memoized // remove it from this dependency array [getToken], ); return useMemo( () => ({ // Whether the auth provider is in a loading state isLoading: isLoading, // Whether the auth provider has the user signed in isAuthenticated: isAuthenticated ?? false, // The async function to fetch the ID token fetchAccessToken, }), [isLoading, isAuthenticated, fetchAccessToken], ); } ``` ### Using the new provider[​](#using-the-new-provider "Direct link to Using the new provider") If you successfully follow the steps above you can now use the standard Convex utilities for checking the authentication state: the [`useConvexAuth()`](/api/modules/react.md#useconvexauth) hook and the [`Authenticated`](/api/modules/react.md#authenticated), [`Unauthenticated`](/api/modules/react.md#authenticated) and [`AuthLoading`](/api/modules/react.md#authloading) helper components. ### Debugging[​](#debugging "Direct link to Debugging") See [Debugging Authentication](/auth/debug.md). Related posts from [![Stack](/img/stack-logo-dark.svg)![Stack](/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # Source: https://docs.convex.dev/auth/advanced/custom-jwt.md # Custom JWT Provider **Note: This is an advanced feature!** We recommend sticking with the [supported third-party authentication providers](/auth.md). A [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) is a string combining three base64-encoded JSON objects containing claims about who a user is valid for a limited period of time like an hour. You can create them with a library like [jose](https://github.com/panva/jose) after receiving some evidence (typically a cookie) of a user's identity or get them from a third party authentication service like [Clerk](https://clerk.com). The information in a JWT is signed (the Convex deployment can tell the information is really from the issuer and hasn't been modified) but generally not encrypted (you can read it by base64-decoding the token or pasting it into [jwt.io](https://jwt.io/). If the JWTs issued to your users by an authentication service contain the right fields to implement the OpenID Connect (OIDC) protocol, the easiest way to configure accepting these JWTs is adding an [OIDC Provider](/auth/advanced/custom-auth.md) entry in `convex/auth.config.ts`. If the authentication service or library you're using to issue JWTs doesn't support these fields (for example [OpenAuth](https://openauth.js.org/) JWTs missing an `aud` field because they implement the OAuth 2.0 spec but not OIDC) you'll need to configure a Custom JWT provider in the `convex/auth.config.ts` file. Custom JWTs are required only to have header fields `kid`, `alg` and `typ`, and payload fields `sub`, `iss`, and `exp`. An `iat` field is also expected by Convex clients to implement token refreshing. ## Server-side integration[​](#server-side-integration "Direct link to Server-side integration") Use `type: "customJwt"` to configure a Custom JWT auth provider: convex/auth.config.ts TS ``` import { AuthConfig } from "convex/server"; export default { providers: [ { type: "customJwt", applicationID: "your-application-id", issuer: "https://your.issuer.url.com", jwks: "https://your.issuer.url.com/.well-known/jwks.json", algorithm: "RS256", }, ], }; ``` * `applicationID`: Convex will verify that JWTs have this value in the `aud` claim. See below for important information regarding leaving this field out. The applicationID field is not required, but necessary to use with many authentication providers for security. Read more below before omitting it. * `issuer`: The issuer URL of the JWT. * `jwks`: The URL for fetching the JWKS (JSON Web Key Set) from the auth provider. If you'd like to avoid hitting an external service you may use a data URI, e.g. `"data:text/plain;charset=utf-8;base64,ey..."` * `algorithm`: The algorithm used to sign the JWT. Only RS256 and ES256 are currently supported. See [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518#section-3.1) for more details. The `issuer` property must exactly match the `iss` field of the JWT used and if specified the `applicationID` property must exactly match the `aud` field. If your JWT doesn't match, use a tool like [jwt.io](https://jwt.io/) to view an JWT and confirm these fields match exactly. ### Warning: omitting `applicationID` is often insecure[​](#warning-omitting-applicationid-is-often-insecure "Direct link to warning-omitting-applicationid-is-often-insecure") Leaving out `applicationID` from an auth configuration means the `aud` (audience) field of your users' JWTs will not be verified. In many cases this is insecure because a JWT intended for another service can be used to impersonate them in your service. Say a user has accounts with `https://todos.com` and `https://banking.com`, two services which use the same third-party authentication service, `accounts.google.com`. A JWT accepted by todos.com could be reused to authenticate with banking.com by either todos.com or an attacker that obtained access to that JWT. The `aud` (audience) field of the JWT prevents this: if the JWT was generated for a specific audience of `https://todos.com` then banking.com can enforce the `aud` field and know not to accept it. If the JWTs issued to your users have an `iss` (issuer) URL like `https://accounts.google.com` that is not specific to your application, it is not secure to trust these tokens without an ApplicationID because that JWT could have been collected by a malicious application. If the JWTs issued to your users have a more specific `iss` field like `https://api.3rd-party-auth.com/client_0123...` then it may be secure to use no `aud` field if you control all the services the issuer url grants then access to and intend for access to any one of these services to grants access to all of them. ### Custom claims[​](#custom-claims "Direct link to Custom claims") In addition to top-level fields like `subject`, `issuer`, and `tokenIdentifier`, subfields of the nested fields of the JWT will be accessible in the auth data returned from `const authInfo = await ctx.auth.getUserIdentity()` like `authInfo["properties.id"]` and `authInfo["properties.favoriteColor"]` for a JWT structured like this: ``` { "properties": { "id": "123", "favoriteColor": "red" }, "iss": "http://localhost:3000", "sub": "user:8fa2be73c2229e85", "exp": 1750968478 } ``` ## Client-side integration[​](#client-side-integration "Direct link to Client-side integration") Your users' browsers need a way to obtain an initial JWT and to request updated JWTs, ideally before the previous one expires. See the instructions for [Custom OIDC Providers](/auth/advanced/custom-auth.md#client-side-integration) for how to do this. --- # Source: https://docs.convex.dev/production/hosting/custom.md # Custom Domains & Hosting ## Custom Domains[​](#custom-domains "Direct link to Custom Domains") You can configure a custom domain, like `api.example.com`, to serve HTTP actions or Convex functions from your production Convex deployments. The settings for this feature are accessed through the Project Settings page on any of your projects. ![Add Custom Domain](/assets/images/add_custom_domain-2886f40eb3fd3ab420535fe0e9311908.png) After you enter a domain, you will be shown which records to set on your DNS provider. Some popular DNS providers that you can use to buy a domain are Cloudflare and GoDaddy. We will verify your domain in the background, and once these records are set, you will see a green checkmark. When you see that checkmark, your backend will now serve traffic from that domain. The first request may take up to a minute because Convex will have to mint a new SSL certificate. Reach out to if you have any questions about getting set up! Custom domains require a Convex Pro plan. Custom domains require a Convex Pro plan. [Learn more](https://convex.dev/pricing) about our plans or [upgrade](https://dashboard.convex.dev/team/settings/billing). ### Hosting with a Custom Domain[​](#hosting-with-a-custom-domain "Direct link to Hosting with a Custom Domain") To use a custom domain to serve your Convex functions, there's an additional step: override the `CONVEX_CLOUD_URL` environment variable. ![Override system environment variables](/assets/images/override_system_env_vars-23e3d90ae07a8a7c7937e78fef9e6869.png) Then re-deploy your project. This may entail clicking "Redeploy" in Vercel or Netlify, or directly running `npx convex deploy --cmd 'npm run build'`. The newly deployed code will access your Convex functions through your custom domain. The `CONVEX_CLOUD_URL` environment variable is used in several places: * `npx convex deploy --cmd '...'` sets `CONVEX_URL` (or similarly named) for your frontend to connect websockets and HTTP clients * In your Convex functions, it is available as `process.env.CONVEX_CLOUD_URL` * File storage URLs: `ctx.storage.getUrl(id)` and `ctx.storage.generateUploadUrl()` * Generate an OpenAPI spec with `npx convex function-spec --prod` You may also override the `CONVEX_SITE_URL` environment variable to be a custom HTTP Action domain. * In your Convex functions, it is available as `process.env.CONVEX_SITE_URL` * It may be used for webhooks * It may be used in `auth.config.ts` as the `issuer` for Convex Auth ## Custom Hosting[​](#custom-hosting "Direct link to Custom Hosting") If you're using only Convex for backend functionality you can host your web app on any static hosting provider. This guide will use [GitHub Pages](https://pages.github.com/) as an example. If you're using Next.js or other framework with server functionality you'll need to use a provider that supports it, such as [Netlify](/production/hosting/netlify.md) or [Vercel](/production/hosting/vercel.md). You can still host Next.js statically via a [static export](https://nextjs.org/docs/pages/building-your-application/deploying/static-exports). ### Configure your build[​](#configure-your-build "Direct link to Configure your build") First make sure that you have a working build process. In this guide we'll set up a local build, but your hosting provider might support a remote build. For example see [Vite's Deploying to GitHub Pages guide](https://vitejs.dev/guide/static-deploy.html#github-pages) which uses GitHub actions. We'll use Vite and GitHub Pages as an example. 1. Configure `vite.config.mts`: vite.config.mts TS ``` import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], build: { outDir: "docs", }, base: "/some-repo-name/", }); ``` The `build.outDir` field specifies where Vite will place the production build, and we use `docs` because that's the directory GitHub Pages allow hosting from. The `base` field specifies the URL path under which you'll serve your app, in this case we will serve on `https://.github.io/`. ### Configure your hosting provider[​](#configure-your-hosting-provider "Direct link to Configure your hosting provider") With GitHub Pages, you can choose whether you want to include your build output in your main working branch or publish from a separate branch. Open your repository's GitHub page > *Settings* > *Pages*. Under *Build and deployment* > *Source* choose `Deploy from a branch`. Under *branch* choose a branch (if you want to use a separate branch, push at least one commit to it first), and the `/docs` folder name. Hit *Save*. ### Build and deploy to Convex and GitHub Pages[​](#build-and-deploy-to-convex-and-github-pages "Direct link to Build and deploy to Convex and GitHub Pages") To manually deploy to GitHub pages follow these steps: 1. Checkout the branch you chose to publish from 2. Run `npx convex deploy --cmd 'npm run build'` and confirm that you want to push your current backend code to your **production** deployment 3. Commit the build output changes and push to GitHub. ### How it works[​](#how-it-works "Direct link to How it works") First, `npx convex deploy` runs through these steps: 1. It sets the `VITE_CONVEX_URL` (or similarly named) environment variable to your **production** Convex deployment. 2. It invokes the frontend framework build process, via `npm run build`. The build process reads the environment variable and uses it to point the built site at your **production** deployment. 3. It deploys your backend code, from the `convex` directory, to your **production** deployment. Afterwards you deploy the built frontend code to your hosting provider. In this case you used Git, but for other providers you might use a different method, such as an old-school FTP request. You can use `--cmd-url-env-var-name` to customize the variable name used by your frontend code if the `deploy` command cannot infer it, like ``` npx convex deploy --cmd-url-env-var-name CUSTOM_CONVEX_URL --cmd 'npm run build' ``` ### Authentication[​](#authentication "Direct link to Authentication") You will want to configure your [authentication](/auth.md) provider (Clerk, Auth0 or other) to accept your production URL, where your frontend is served. --- # Source: https://docs.convex.dev/dashboard.md # Dashboard ![Dashboard Projects View](/assets/images/projects-ea1be7a1deec4ee628278d2badc15e2f.png) [The dashboard](https://dashboard.convex.dev/) is the central hub for managing your Convex projects. Here you can create and manage your Convex teams, projects, and deployments. --- # Source: https://docs.convex.dev/generated-api/data-model.md # dataModel.d.ts This code is generated These exports are not directly available in the `convex` package! Instead you must run `npx convex dev` to create `convex/_generated/dataModel.d.ts`. Generated data model types. ## Types[​](#types "Direct link to Types") ### TableNames[​](#tablenames "Direct link to TableNames") Ƭ **TableNames**: `string` The names of all of your Convex tables. *** ### Doc[​](#doc "Direct link to Doc") Ƭ **Doc**``: `Object` The type of a document stored in Convex. #### Type parameters[​](#type-parameters "Direct link to Type parameters") | Name | Type | Description | | ----------- | ----------------------------------- | ------------------------------------------------------- | | `TableName` | extends [`TableNames`](#tablenames) | A string literal type of the table name (like "users"). | *** ### Id[​](#id "Direct link to Id") An identifier for a document in Convex. Convex documents are uniquely identified by their `Id`, which is accessible on the `_id` field. To learn more, see [Document IDs](/database/document-ids.md). Documents can be loaded using `db.get(tableName, id)` in query and mutation functions. IDs are just strings at runtime, but this type can be used to distinguish them from other strings when type checking. This is an alias of [`GenericId`](/api/modules/values.md#genericid) that is typed for your data model. #### Type parameters[​](#type-parameters-1 "Direct link to Type parameters") | Name | Type | Description | | ----------- | ----------------------------------- | ------------------------------------------------------- | | `TableName` | extends [`TableNames`](#tablenames) | A string literal type of the table name (like "users"). | *** ### DataModel[​](#datamodel "Direct link to DataModel") Ƭ **DataModel**: `Object` A type describing your Convex data model. This type includes information about what tables you have, the type of documents stored in those tables, and the indexes defined on them. This type is used to parameterize methods like [`queryGeneric`](/api/modules/server.md#querygeneric) and [`mutationGeneric`](/api/modules/server.md#mutationgeneric) to make them type-safe. --- # Source: https://docs.convex.dev/client/swift/data-types.md # Source: https://docs.convex.dev/client/android/data-types.md # Kotlin and Convex type conversion ## Custom data types[​](#custom-data-types "Direct link to Custom data types") When receiving values from Convex, you aren't limited to primitive values. You can create custom `@Serializable` classes that will be automatically decoded from response data. Consider a Convex query function that returns results like this JavaScript object: ``` { name: "Guardians", uniformColors: ["blue", "white", "red"], wins: 80n, losses: 60n } ``` That can be represented in Kotlin using: ``` @Serializable data class BaseballTeam( val name: String, val uniformColors: List, val wins: @ConvexNum Int, val losses: @ConvexNum Int) ``` Then you can pass it as the type argument in your `subscribe` call: ``` convex.subscribe("mlb:first_place_team", args = mapOf("division" to "AL Central")) ``` The data from the remote function will be deserialized to your custom class. ## Numerical types[​](#numerical-types "Direct link to Numerical types") Your Convex backend code is written in JavaScript, which has two relatively common types for numerical data: `number` and `BigInt`. `number` is used whenever a value is assigned a literal numeric value, whether `42` or `3.14`. `BigInt` can be used by adding a trailing `n`, like `42n`. Despite the two types, is very common to use `number` for holding either integer or floating point values in JavaScript. Because of this, Convex takes extra care to encode values so they won't lose precision. Since technically the `number` type is an IEEE 754 floating point value, anytime you get a plain `number` from Convex it will be represented as floating point in Kotlin. You can choose to use `Double` or `Float`, depending on your needs but be aware that `Float` might lose precision from the original. It also means that Kotlin's `Long` type (64 bit) can't be safely stored in a `number` (only 53 bits are available to encode integers) and requires a `BigInt`. That's a long lead up to explain that in order to represent numerical values in responses from Convex, you need to hint to Kotlin that they should use custom decoding. You can do this in three ways. Use whichever seems most useful to your project. 1. Annotate the plain Kotlin type (`Int`, `Long`, `Float`, `Double`) with `@ConvexNum` 2. Use a provided type alias for those types (`Int32`, `Int64`, `Float32`, `Float64`) 3. Include a special annotation at the top of any file that defines `@Serializable` classes and just use the plain types with no annotation ``` @file:UseSerializers( Int64ToIntDecoder::class, Int64ToLongDecoder::class, Float64ToFloatDecoder::class, Float64ToDoubleDecoder::class ) package com.example.convexapp import kotlinx.serialization.UseSerializers // @Serializable classes and things. ``` In the example, JavaScript's `BigInt` type is used by adding a trailing `n` to the `wins` and `losses` values which lets the Kotlin code use `Int`. If instead the code used regular JavaScript `number` types, on the Kotlin side those would be received as floating point values and deserialization would fail. If you have a situation like that where `number` is used but by convention only contains integer values, you can handle that in your `@Serializable` class. ``` @Serializable data class BaseballTeam( val name: String, val uniformColors: List, @SerialName("wins") private val internalWins: Double, @SerialName("losses") private val internalLosses: Double) { // Expose the JavaScript number values as Ints. val wins get() = internalWins.toInt() val losses get() = internalLosses.toInt() } ``` The pattern is to store the `Double` values privately and with different names that the value from the backend. Then add accessors to provide the `Int` values. ## Field name conversion[​](#field-name-conversion "Direct link to Field name conversion") This pattern was used above, but it bears describing on its own. Sometimes a value will be produced on the backend with a key that matches a Kotlin keyword (`{fun: true}`) or doesn't conform to Kotlin naming conventions (e.g. starts with an underscore). You can use `@SerialName` to handle those cases. For example, here's how you can ingest the Convex [document ID](https://docs.convex.dev/database/document-ids) from a backend response and convert it to a field name that won't trigger Kotlin lint warnings: ``` @Serializable data class ConvexDocument(@SerialName("_id") val id: String) ``` --- # Source: https://docs.convex.dev/dashboard/deployments/data.md # Data ![Data Dashboard Page](/assets/images/data-8f3dc6a4eb6c62497429e58ad703367b.png) The [data page](https://dashboard.convex.dev/deployment/data) allows you to view and manage all of your tables and documents. On the left side of the page is a list of your tables. Clicking on a table will allows you to create, view, update, and delete documents in that table. You may drag-and-drop the column headers in each table to visually re-order the data. A readonly view of the data page is available in the [command line](/cli.md#display-data-from-tables). ``` npx convex data [table] ``` ## Filtering documents[​](#filtering-documents "Direct link to Filtering documents") You may filters documents on the data page by clicking the "Filter" button on the top of the page. ![Data filters](/assets/images/data_filters-1d3e550f86642506f0507d143a070ab8.png) All fields in a document are filterable by the operations supported in Convex query syntax. [Equality](/database/reading-data/filters.md#equality-conditions) and [comparisons](/database/reading-data/filters.md#comparisons) share the same rules when filtering in the dashboard as a query using the Convex client. You may also filter based on the type of the field. To add a filter, click the `+` next to an existing filter. If you add more than one condition, they will be evaluated using the `and` operation. For each filter, you must select a field to filter by, operation, and comparison value. In the third input box (selecting a value), you may enter a valid Convex value, such as `"a string"`, `123`, or even a complex object, such as `{ a: { b: 2 } }` note When filtering by `_creationTime`, a date picker will be displayed instead of the normal JavaScript syntax input box. Comparisons for `_creationTime` are made at the nanosecond granularity, so if you'd like to filter to an exact time, try adding two filter conditions for `creationTime >= $time` and `creationTime <= $time + 1 minute`. ## Writing custom queries[​](#writing-custom-queries "Direct link to Writing custom queries") You can write a [query](/database/reading-data/.md) directly in the dashboard. This allows you to perform arbitrary filtering and transformation of the data, including sorting, joins, grouping and aggregations. In the `⋮` overflow menu at the top of the data page click on the “Custom query” option. ![Custom query button](/screenshots/data_custom_query.png) This opens the same UI used for [running your deployed functions](/dashboard/deployments/functions.md#running-functions), but with the “Custom test query” option selected, which lets you edit the source code for the query. This source code will be sent to your deployment and executed when you click on the “Run Custom Query“ button. ![Running a custom test query](/assets/images/data_custom_query_runner-3ed1f45c917689a4d0109d8759690e24.png) If you're not on the data page, you can still open this UI via the persistent *fn* button shown on the bottom right of all deployment pages. The keyboard shortcut to open the function runner is Ctrl + \` (backtick). ## Creating tables[​](#creating-tables "Direct link to Creating tables") You may create a table from the dashboard by clicking the "Create Table" button and entering a new name for the table. ## Creating documents[​](#creating-documents "Direct link to Creating documents") You may add individual documents to the table using the “Add Documents” button located in the data table's toolbar. Once you click “Add Documents” a side panel will open, allowing you to add new documents to your table using JavaScript syntax. To add more than one document add a time, add new objects to the array in the editor. ![Add document](/assets/images/data_add_document-f81e3b7952d2685f6d797773bb4c5b6d.png) ## Quick actions (context menu)[​](#quick-actions-context-menu "Direct link to Quick actions (context menu)") You can right-click on a document or value to open a context menu with quick actions, like copying values, quickly filtering by the selected value, and deleting documents. ![Quick actions context menu](/assets/images/data_context_menu-dc17e11f1df2733cdcf9170af0baf0b1.png) ## Editing a cell[​](#editing-a-cell "Direct link to Editing a cell") To edit a cell's value, double-click on the cell in the data table, or press the Enter key while it’s selected. You can change the selected cell by using the arrow keys. You can change the value by editing inline, and pressing enter to save. note You can even edit the type of your value here, as long as it satisfies your [schema](/database/schemas.md) — try replacing a string with an object! ![Inline value editor](/assets/images/data_edit_inline-eb2c461a2f3395ec975e6829eb70d66d.png) ## Editing a document[​](#editing-a-document "Direct link to Editing a document") To edit multiple fields in a document at the same time, hover over the document and right-click to open the context menu. From there you can click on "Edit Document". ![Edit entire document](/assets/images/data_edit_document-23e314be7901f2892a2136350798afc6.png) ## Adding references to other documents[​](#adding-references-to-other-documents "Direct link to Adding references to other documents") To reference another document, use the string ID of the document you want to reference. You can copy the ID by clicking on its cell and pressing CTRL/CMD+C. ## Bulk editing documents[​](#bulk-editing-documents "Direct link to Bulk editing documents") You can edit multiple or all documents at once. To select all documents click on the checkbox in the table header row. To select individual documents hover over the left-most cell and click the checkbox that appears. To select multiple adjacent documents at once, press the Shift key when clicking on the checkbox. When at least one document is selected, the “(Bulk) Edit Document(s)” button will be visible in the table toolbar. Click the button and an editor will appear on the right hand side. ![Bulk edit documents](/assets/images/data_bulk_edit-77eab4f33e91c212d74c2b3664adea8d.png) ## Deleting documents[​](#deleting-documents "Direct link to Deleting documents") When at least one document is selected (see above), the “Delete Document(s)” button will be visible in the table toolbar. Click the button to delete documents. If you're editing data in a production deployment a confirmation dialog will appear before the documents are deleted. ## Clear a table[​](#clear-a-table "Direct link to Clear a table") You can also delete all documents by clicking on the `⋮` overflow menu at the top of the data page and clicking "Clear Table". This action will delete all documents in the table, without deleting the table itself. In production environments, the Convex dashboard will have you type in the name of the table before deletion. ## Delete a table[​](#delete-a-table "Direct link to Delete a table") This is a permanent action Deleting a table is irreversible. In production environments, the Convex dashboard will have you type in the name of the table before deletion. The "Delete table" button can be found by clicking on the `⋮` overflow menu at the top of the data page. This action will delete all documents this table, and remove the table from your list of tables. If this table had indexes, you will need to redeploy your convex functions (by running `npx convex deploy` or `npx convex dev` for production or development, respectively) to recreate the indexes. ## Generating a schema[​](#generating-a-schema "Direct link to Generating a schema") At the bottom-left of the page is a "Generate Schema" button which you can click to have Convex generate a [schema](/database/schemas.md) of all your documents within this table. ![Generate Schema button](/assets/images/data_generate_schema-83edb92d597e55d5fc1f7c7fed701d47.png) ## View the schema of a table[​](#view-the-schema-of-a-table "Direct link to View the schema of a table") The "Schema" button can be found by clicking on the `⋮` overflow menu at the top of the data page. This button will open a panel showing the saved and generated [schemas](/database/schemas.md) associated with the selected table. ## View the indexes of a table[​](#view-the-indexes-of-a-table "Direct link to View the indexes of a table") The "Indexes" button can be found by clicking on the `⋮` overflow menu at the top of the data page. This button will open a panel showing the [indexes](/database/reading-data/indexes/.md) associated with the selected table. Indexes that have not completed backfilling will be accompanied by a loading spinner next to their name. --- # Source: https://docs.convex.dev/auth/database-auth.md # Storing Users in the Convex Database *If you're using [Convex Auth](/auth/convex-auth.md) the user information is already stored in your database. There's nothing else you need to implement.* You might want to store user information directly in your Convex database, for the following reasons: * Your functions need information about other users, not just about the currently logged-in user * Your functions need access to information other than the fields available in the [Open ID Connect JWT](/auth/functions-auth.md) There are two ways you can choose from for storing user information in your database (but only the second one allows storing information not contained in the JWT): 1. Have your app's [client call a mutation](#call-a-mutation-from-the-client) that stores the information from the JWT available on [`ctx.auth`](/api/interfaces/server.Auth.md) 2. [Implement a webhook](#set-up-webhooks) and have your identity provider call it whenever user information changes ## Call a mutation from the client[​](#call-a-mutation-from-the-client "Direct link to Call a mutation from the client") **Example:** [Convex Authentication with Clerk](https://github.com/get-convex/convex-demos/tree/main/users-and-clerk) ### (optional) Users table schema[​](#optional-users-table-schema "Direct link to (optional) Users table schema") You can define a `"users"` table, optionally with an [index](/database/reading-data/indexes/.md) for efficient looking up the users in the database. In the examples below we will use the `tokenIdentifier` from the `ctx.auth.getUserIdentity()` to identify the user, but you could use the `subject` field (which is usually set to the unique user ID from your auth provider) or even `email`, if your authentication provider provides email verification and you have it enabled. Which field you use will determine how multiple providers interact, and how hard it will be to migrate to a different provider. convex/schema.ts ``` users: defineTable({ name: v.string(), tokenIdentifier: v.string(), }).index("by_token", ["tokenIdentifier"]), ``` ### Mutation for storing current user[​](#mutation-for-storing-current-user "Direct link to Mutation for storing current user") This is an example of a mutation that stores the user's `name` and `tokenIdentifier`: convex/users.ts TS ``` import { mutation } from "./_generated/server"; export const store = mutation({ args: {}, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) { throw new Error("Called storeUser without authentication present"); } // Check if we've already stored this identity before. // Note: If you don't want to define an index right away, you can use // ctx.db.query("users") // .filter(q => q.eq(q.field("tokenIdentifier"), identity.tokenIdentifier)) // .unique(); const user = await ctx.db .query("users") .withIndex("by_token", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier), ) .unique(); if (user !== null) { // If we've seen this identity before but the name has changed, patch the value. if (user.name !== identity.name) { await ctx.db.patch(user._id, { name: identity.name }); } return user._id; } // If it's a new identity, create a new `User`. return await ctx.db.insert("users", { name: identity.name ?? "Anonymous", tokenIdentifier: identity.tokenIdentifier, }); }, }); ``` ### Calling the store user mutation from React[​](#calling-the-store-user-mutation-from-react "Direct link to Calling the store user mutation from React") You can call this mutation when the user logs in from a `useEffect` hook. After the mutation succeeds you can update local state to reflect that the user has been stored. This helper hook that does the job: src/useStoreUserEffect.ts TS ``` import { useUser } from "@clerk/clerk-react"; import { useConvexAuth } from "convex/react"; import { useEffect, useState } from "react"; import { useMutation } from "convex/react"; import { api } from "../convex/_generated/api"; import { Id } from "../convex/_generated/dataModel"; export function useStoreUserEffect() { const { isLoading, isAuthenticated } = useConvexAuth(); const { user } = useUser(); // When this state is set we know the server // has stored the user. const [userId, setUserId] = useState | null>(null); const storeUser = useMutation(api.users.store); // Call the `storeUser` mutation function to store // the current user in the `users` table and return the `Id` value. useEffect(() => { // If the user is not logged in don't do anything if (!isAuthenticated) { return; } // Store the user in the database. // Recall that `storeUser` gets the user information via the `auth` // object on the server. You don't need to pass anything manually here. async function createUser() { const id = await storeUser(); setUserId(id); } createUser(); return () => setUserId(null); // Make sure the effect reruns if the user logs in with // a different identity }, [isAuthenticated, storeUser, user?.id]); // Combine the local state with the state from context return { isLoading: isLoading || (isAuthenticated && userId === null), isAuthenticated: isAuthenticated && userId !== null, }; } ``` You can use this hook in your top-level component. If your queries need the user document to be present, make sure that you only render the components that call them after the user has been stored: src/App.tsx TS ``` import { SignInButton, UserButton } from "@clerk/clerk-react"; import { useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; import { useStoreUserEffect } from "./useStoreUserEffect.js"; function App() { const { isLoading, isAuthenticated } = useStoreUserEffect(); return (
{isLoading ? ( <>Loading... ) : !isAuthenticated ? ( ) : ( <> )}
); } function Content() { const messages = useQuery(api.messages.getForCurrentUser); return
Authenticated content: {messages?.length}
; } export default App; ``` In this way the `useStoreUserEffect` hook replaces the `useConvexAuth` hook. ### Using the current user's document ID[​](#using-the-current-users-document-id "Direct link to Using the current user's document ID") Similarly to the store user mutation, you can retrieve the current user's ID, or throw an error if the user hasn't been stored. Now that you have users stored as documents in your Convex database, you can use their IDs as foreign keys in other documents: convex/messages.ts TS ``` import { v } from "convex/values"; import { mutation } from "./_generated/server"; export const send = mutation({ args: { body: v.string() }, handler: async (ctx, args) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) { throw new Error("Unauthenticated call to mutation"); } const user = await ctx.db .query("users") .withIndex("by_token", (q) => q.eq("tokenIdentifier", identity.tokenIdentifier), ) .unique(); if (!user) { throw new Error("Unauthenticated call to mutation"); } await ctx.db.insert("messages", { body: args.body, user: user._id }); }, }); // do something with `user`... } }); ``` ### Loading users by their ID[​](#loading-users-by-their-id "Direct link to Loading users by their ID") The information about other users can be retrieved via their IDs: convex/messages.ts TS ``` import { query } from "./_generated/server"; export const list = query({ args: {}, handler: async (ctx) => { const messages = await ctx.db.query("messages").collect(); return Promise.all( messages.map(async (message) => { // For each message in this channel, fetch the `User` who wrote it and // insert their name into the `author` field. const user = await ctx.db.get(message.user); return { author: user?.name ?? "Anonymous", ...message, }; }), ); }, }); ``` ## Set up webhooks[​](#set-up-webhooks "Direct link to Set up webhooks") This guide will use Clerk, but Auth0 can be set up similarly via [Auth0 Actions](https://auth0.com/docs/customize/actions/actions-overview). With this implementation Clerk will call your Convex backend via an HTTP endpoint any time a user signs up, updates or deletes their account. **Example:** [Convex Authentication with Clerk and Webhooks](https://github.com/get-convex/convex-demos/tree/main/users-and-clerk-webhooks) ### Configure the webhook endpoint in Clerk[​](#configure-the-webhook-endpoint-in-clerk "Direct link to Configure the webhook endpoint in Clerk") On your Clerk dashboard, go to *Webhooks*, click on *+ Add Endpoint*. Set *Endpoint URL* to `https://.convex.site/clerk-users-webhook` (note the domain ends in **`.site`**, not `.cloud`). You can see your deployment name in the `.env.local` file in your project directory, or on your Convex dashboard as part of the [Deployment URL](/dashboard/deployments/deployment-settings.md). For example, the endpoint URL could be: `https://happy-horse-123.convex.site/clerk-users-webhook`. In *Message Filtering*, select **user** for all user events (scroll down or use the search input). Click on *Create*. After the endpoint is saved, copy the *Signing Secret* (on the right side of the UI), it should start with `whsec_`. Set it as the value of the `CLERK_WEBHOOK_SECRET` environment variable in your Convex [dashboard](https://dashboard.convex.dev). ### (optional) Users table schema[​](#optional-users-table-schema-1 "Direct link to (optional) Users table schema") You can define a `"users"` table, optionally with an [index](/database/reading-data/indexes/.md) for efficient looking up the users in the database. In the examples below we will use the `subject` from the `ctx.auth.getUserIdentity()` to identify the user, which should be set to the Clerk user ID. convex/schema.ts ``` users: defineTable({ name: v.string(), // this the Clerk ID, stored in the subject JWT field externalId: v.string(), }).index("byExternalId", ["externalId"]), ``` ### Mutations for upserting and deleting users[​](#mutations-for-upserting-and-deleting-users "Direct link to Mutations for upserting and deleting users") This is an example of mutations that handle the updates received via the webhook: convex/users.ts TS ``` import { internalMutation, query, QueryCtx } from "./_generated/server"; import { UserJSON } from "@clerk/backend"; import { v, Validator } from "convex/values"; export const current = query({ args: {}, handler: async (ctx) => { return await getCurrentUser(ctx); }, }); export const upsertFromClerk = internalMutation({ args: { data: v.any() as Validator }, // no runtime validation, trust Clerk async handler(ctx, { data }) { const userAttributes = { name: `${data.first_name} ${data.last_name}`, externalId: data.id, }; const user = await userByExternalId(ctx, data.id); if (user === null) { await ctx.db.insert("users", userAttributes); } else { await ctx.db.patch(user._id, userAttributes); } }, }); export const deleteFromClerk = internalMutation({ args: { clerkUserId: v.string() }, async handler(ctx, { clerkUserId }) { const user = await userByExternalId(ctx, clerkUserId); if (user !== null) { await ctx.db.delete(user._id); } else { console.warn( `Can't delete user, there is none for Clerk user ID: ${clerkUserId}`, ); } }, }); export async function getCurrentUserOrThrow(ctx: QueryCtx) { const userRecord = await getCurrentUser(ctx); if (!userRecord) throw new Error("Can't get current user"); return userRecord; } export async function getCurrentUser(ctx: QueryCtx) { const identity = await ctx.auth.getUserIdentity(); if (identity === null) { return null; } return await userByExternalId(ctx, identity.subject); } async function userByExternalId(ctx: QueryCtx, externalId: string) { return await ctx.db .query("users") .withIndex("byExternalId", (q) => q.eq("externalId", externalId)) .unique(); } ``` There are also a few helpers in this file: * `current` exposes the user information to the client, which will helps the client determine whether the webhook already succeeded * `upsertFromClerk` will be called when a user signs up or when they update their account * `deleteFromClerk` will be called when a user deletes their account via Clerk UI from your app * `getCurrentUserOrThrow` retrieves the currently logged-in user or throws an error * `getCurrentUser` retrieves the currently logged-in user or returns null * `userByExternalId` retrieves a user given the Clerk ID, and is used only for retrieving the current user or when updating an existing user via the webhook ### Webhook endpoint implementation[​](#webhook-endpoint-implementation "Direct link to Webhook endpoint implementation") This how the actual HTTP endpoint can be implemented: convex/http.ts TS ``` import { httpRouter } from "convex/server"; import { httpAction } from "./_generated/server"; import { internal } from "./_generated/api"; import type { WebhookEvent } from "@clerk/backend"; import { Webhook } from "svix"; const http = httpRouter(); http.route({ path: "/clerk-users-webhook", method: "POST", handler: httpAction(async (ctx, request) => { const event = await validateRequest(request); if (!event) { return new Response("Error occured", { status: 400 }); } switch (event.type) { case "user.created": // intentional fallthrough case "user.updated": await ctx.runMutation(internal.users.upsertFromClerk, { data: event.data, }); break; case "user.deleted": { const clerkUserId = event.data.id!; await ctx.runMutation(internal.users.deleteFromClerk, { clerkUserId }); break; } default: console.log("Ignored Clerk webhook event", event.type); } return new Response(null, { status: 200 }); }), }); async function validateRequest(req: Request): Promise { const payloadString = await req.text(); const svixHeaders = { "svix-id": req.headers.get("svix-id")!, "svix-timestamp": req.headers.get("svix-timestamp")!, "svix-signature": req.headers.get("svix-signature")!, }; const wh = new Webhook(process.env.CLERK_WEBHOOK_SECRET!); try { return wh.verify(payloadString, svixHeaders) as unknown as WebhookEvent; } catch (error) { console.error("Error verifying webhook event", error); return null; } } export default http; ``` If you deploy your code now and sign in, you should see the user being created in your Convex database. ### Using the current user's document[​](#using-the-current-users-document "Direct link to Using the current user's document") You can use the helpers defined before to retrieve the current user's document. Now that you have users stored as documents in your Convex database, you can use their IDs as foreign keys in other documents: convex/messages.ts TS ``` import { v } from "convex/values"; import { mutation } from "./_generated/server"; import { getCurrentUserOrThrow } from "./users"; export const send = mutation({ args: { body: v.string() }, handler: async (ctx, args) => { const user = await getCurrentUserOrThrow(ctx); await ctx.db.insert("messages", { body: args.body, userId: user._id }); }, }); ``` ### Loading users by their ID[​](#loading-users-by-their-id-1 "Direct link to Loading users by their ID") The information about other users can be retrieved via their IDs: convex/messages.ts TS ``` export const list = query({ args: {}, handler: async (ctx) => { const messages = await ctx.db.query("messages").collect(); return Promise.all( messages.map(async (message) => { // For each message in this channel, fetch the `User` who wrote it and // insert their name into the `author` field. const user = await ctx.db.get(message.user); return { author: user?.name ?? "Anonymous", ...message, }; }), ); }, }); ``` ### Waiting for current user to be stored[​](#waiting-for-current-user-to-be-stored "Direct link to Waiting for current user to be stored") If you want to use the current user's document in a query, make sure that the user has already been stored. You can do this by explicitly checking for this condition before rendering the components that call the query, or before redirecting to the authenticated portion of your app. For example you can define a hook that determines the current authentication state of the client, taking into account whether the current user has been stored: src/useCurrentUser.ts TS ``` import { useConvexAuth, useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; export function useCurrentUser() { const { isLoading, isAuthenticated } = useConvexAuth(); const user = useQuery(api.users.current); // Combine the authentication state with the user existence check return { isLoading: isLoading || (isAuthenticated && user === null), isAuthenticated: isAuthenticated && user !== null, }; } ``` And then you can use it to render the appropriate components: src/App.tsx TS ``` import { useCurrentUser } from "./useCurrentUser"; export default function App() { const { isLoading, isAuthenticated } = useCurrentUser(); return (
{isLoading ? ( <>Loading... ) : isAuthenticated ? ( ) : ( )}
); } ``` --- # Source: https://docs.convex.dev/database.md # Database The Convex database provides a relational data model, stores JSON-like documents, and can be used with or without a schema. It "just works," giving you predictable query performance in an easy-to-use interface. Query and mutation [functions](/functions.md) read and write data through a lightweight JavaScript API. There is nothing to set up and no need to write any SQL. Just use JavaScript to express your app's needs. Start by learning about the basics of [tables](#tables), [documents](#documents) and [schemas](#schemas) below, then move on to [Reading Data](/database/reading-data/.md) and [Writing Data](/database/writing-data.md). As your app grows more complex you'll need more from your database: * Relational data modeling with [Document IDs](/database/document-ids.md) * Fast querying with [Indexes](/database/reading-data/indexes/.md) * Exposing large datasets with [Paginated Queries](/database/pagination.md) * Type safety by [Defining a Schema](/database/schemas.md) * Interoperability with data [Import & Export](/database/import-export/.md) ## Tables[​](#tables "Direct link to Tables") Your Convex deployment contains tables that hold your app's data. Initially, your deployment contains no tables or documents. Each table springs into existence as soon as you add the first document to it. ``` // `friends` table doesn't exist. await ctx.db.insert("friends", { name: "Jamie" }); // Now it does, and it has one document. ``` You do not have to specify a schema upfront or create tables explicitly. ## Documents[​](#documents "Direct link to Documents") Tables contain documents. Documents are very similar to JavaScript objects. They have fields and values, and you can nest arrays or objects within them. These are all valid Convex documents: ``` {} {"name": "Jamie"} {"name": {"first": "Ari", "second": "Cole"}, "age": 60} ``` They can also contain references to other documents in other tables. See [Data Types](/database/types.md) to learn more about the types supported in Convex and [Document IDs](/database/document-ids.md) to learn about how to use those types to model your data. ## Schemas[​](#schemas "Direct link to Schemas") Though optional, schemas ensure that your data looks exactly how you want. For a simple chat app, the schema will look like this: ``` import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; // @snippet start schema export default defineSchema({ messages: defineTable({ author: v.id("users"), body: v.string(), }), }); ``` You can choose to be as flexible as you want by using types such as `v.any()` or as specific as you want by precisely describing a `v.object()`. See [the schema documentation](/database/schemas.md) to learn more about schemas. ## [Next: Reading Data](/database/reading-data/.md) [Query and read data from Convex database tables](/database/reading-data/.md) Related posts from [![Stack](/img/stack-logo-dark.svg)![Stack](/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # Source: https://docs.convex.dev/auth/debug.md # Debugging Authentication You have followed one of our authentication guides but something is not working. You have double checked that you followed all the steps, and that you used the correct secrets, but you are still stuck. ## Frequently encountered issues[​](#frequently-encountered-issues "Direct link to Frequently encountered issues") ### `ctx.auth.getUserIdentity()` returns `null` in a query[​](#ctxauthgetuseridentity-returns-null-in-a-query "Direct link to ctxauthgetuseridentity-returns-null-in-a-query") This often happens when subscribing to queries via `useQuery` in React, without waiting for the client to be authenticated. Even if the user has been logged-in previously, it takes some time for the client to authenticate with the Convex backend. Therefore on page load, `ctx.auth.getUserIdentity()` called within a query returns `null`. To handle this, you can either: 1. Use the `Authenticated` component from `convex/react` to wrap the component that includes the `useQuery` call (see the last two steps in the [Clerk guide](/auth/clerk.md#get-started)) 2. Or return `null` or some other "sentinel" value from the query and handle it on the client If you are using `fetchQuery` for [Next.js Server Rendering](/client/nextjs/app-router/server-rendering.md), make sure you are explicitly passing in a JWT token as documented [here](/client/nextjs/app-router/server-rendering.md#server-side-authentication). If this hasn't helped, follow the steps below to resolve your issue. ## Step 1: Check whether authentication works on the backend[​](#step-1-check-whether-authentication-works-on-the-backend "Direct link to Step 1: Check whether authentication works on the backend") 1. Add the following code to the *beginning* of your function (query, mutation, action or http action): ``` console.log("server identity", await ctx.auth.getUserIdentity()); ``` 2. Then call this function from whichever client you're using to talk to Convex. 3. Open the [logs page on your dashboard](https://dashboard.convex.dev/deployment/logs). 4. What do you see on the logs page? **Answer: I don't see anything**: * Potential cause: You don't have the right dashboard open. Confirm that the Deployment URL on *Settings* > *URL and Deploy Key* page matches how your client is configured. * Potential cause: Your client is not connected to Convex. Check your client logs (browser logs) for errors. Reload the page / restart the client. * Potential cause: The code has not been pushed. For dev deployments make sure you have `npx convex dev` running. For prod deployments make sure you successfully pushed via `npx convex deploy`. Go to the *Functions* page on the dashboard and check that the code shown there includes the `console.log` line you added. When you resolved the cause you should see the log appear. **Answer: I see a log with `'server identity' null`**: * Potential cause: The client is not supplying an auth token. * Potential cause: Your deployment is misconfigured. * Potential cause: Your client is misconfigured. Proceed to [step 2](#step-2-check-whether-authentication-works-on-the-frontend). **Answer: I see a log with `'server identity' { tokenIdentifier: '... }`** Great, you are all set! ## Step 2: Check whether authentication works on the frontend[​](#step-2-check-whether-authentication-works-on-the-frontend "Direct link to Step 2: Check whether authentication works on the frontend") No matter which client you use, it must pass a JWT token to your backend for authentication to work. The most bullet-proof way of ensuring your client is passing the token to the backend, is to inspect the traffic between them. 1. If you're using a client from the web browser, open the *Network* tab in your browser's developer tools. 2. Check the token * For Websocket-based clients (`ConvexReactClient` and `ConvexClient`), filter for the `sync` name and select `WS` as the type of traffic. Check the `sync` items. After the client is initialized (commonly after loading the page), it will send a message (check the *Messages* tab) with `type: "Authenticate"`, and `value` will be the authentication token. ![Network tab inspecting Websocket messages](/screenshots/auth-ws.png) * For HTTP based clients (`ConvexHTTPClient` and the [HTTP API](/http-api/.md)), select `Fetch/XHR` as the type of traffic. You should see an individual network request for each function call, with an `Authorization` header with value `Bearer `followed by the authentication token. ![Network tab inspecting HTTP headers](/screenshots/auth-http.png) 3. Do you see the authentication token in the traffic? **Answer: No**: * Potential cause: The Convex client is not configured to get/fetch a JWT token. You're not using `ConvexProviderWithClerk`/`ConvexProviderWithAuth0`/`ConvexProviderWithAuth` with the `ConvexReactClient` or you forgot to call `setAuth` on `ConvexHTTPClient` or `ConvexClient`. * Potential cause: You are not signed in, so the token is `null` or `undefined` and the `ConvexReactClient` skipped authentication altogether. Verify that you are signed in via `console.log`ing the token from whichever auth provider you are using: * Clerk: ``` // import { useAuth } from "@clerk/nextjs"; // for Next.js import { useAuth } from "@clerk/clerk-react"; const { getToken } = useAuth(); console.log(getToken({ template: "convex" })); ``` * Auth0: ``` import { useAuth0 } from "@auth0/auth0-react"; const { getAccessTokenSilently } = useAuth0(); const response = await getAccessTokenSilently({ detailedResponse: true, }); const token = response.id_token; console.log(token); ``` * Custom: However you implemented `useAuthFromProviderX` If you don't see a long string that looks like a token, check the browser logs for errors from your auth provider. If there are none, check the Network tab to see whether requests to your provider are failing. Perhaps the auth provider is misconfigured. Double check the auth provider configuration (in the corresponding React provider or however your auth provider is configured for the client). Try clearing your cookies in the browser (in dev tools *Application* > *Cookies* > *Clear all cookies* button). **Answer: Yes, I see a long string that looks like a JWT**: Great, copy the whole token (there can be `.`s in it, so make sure you're not copying just a portion of it). 4. Open , scroll down and paste the token in the Encoded textarea on the left of the page. On the right you should see: * In *HEADER*, `"typ": "JWT"` * in *PAYLOAD*, a valid JSON with at least `"aud"`, `"iss"` and `"sub"` fields. If you see gibberish in the payload you probably didn't copy the token correctly or it's not a valid JWT token. If you see a valid JWT token, repeat [step 1](#step-1-check-whether-authentication-works-on-the-backend). If you still don't see correct identity, proceed to step 3. ## Step 3: Check that backend configuration matches frontend configuration[​](#step-3-check-that-backend-configuration-matches-frontend-configuration "Direct link to Step 3: Check that backend configuration matches frontend configuration") You have a valid JWT token on the frontend, and you know that it is being passed to the backend, but the backend is not validating it. 1. Open the *Settings* > *Authentication* on your dashboard. What do you see? **Answer: I see `This deployment has no configured authentication providers`**: * Cause: You do not have an `auth.config.ts` (or `auth.config.js`) file in your `convex` directory, or you haven't pushed your code. Follow the authentication guide to create a valid auth config file. For dev deployments make sure you have `npx convex dev` running. For prod deployments make sure you successfully pushed via `npx convex deploy`. \*\*Answer: I see one or more *Domain* and *Application ID* pairs. Great, let's check they match the JWT token. 2. Look at the `iss` field in the JWT token payload at . Does it match a *Domain* on the *Authentication* page? **Answer: No, I don't see the `iss` URL on the Convex dashboard**: * Potential cause: You copied the wrong value into your `auth.config.ts` 's `domain`, or into the environment variable that is used there. Go back to the authentication guide and make sure you have the right URL from your auth provider. * Potential cause: Your client is misconfigured: * Clerk: You have the wrong `publishableKey` configured. The key must belong to the Clerk instance that you used to configure your `auth.config.ts`. * Also make sure that the JWT token in Clerk is called `convex`, as that's the name `ConvexProviderWithClerk` uses to fetch the token! * Auth0: You have the wrong `domain` configured (on the client!). The domain must belong to the Auth0 instance that you used to configure your `auth.config.ts`. * Custom: Make sure that your client is correctly configured to match your `auth.config.ts`. **Answer: Yes, I do see the `iss` URL**: Great, let's move one. 3. Look at the `aud` field in the JWT token payload at . Does it match the *Application ID* under the correct *Domain* on the *Authentication* page? **Answer: No, I don't see the `aud` value in the *Application ID* field**: * Potential cause: You copied the wrong value into your `auth.config.ts` 's `applicationID`, or into the environment variable that is used there. Go back to the authentication guide and make sure you have the right value from your auth provider. * Potential cause: Your client is misconfigured: * Clerk: You have the wrong `publishableKey` configured.The key must belong to the Clerk instance that you used to configure your `auth.config.ts`. * Auth0: You have the wrong `clientId` configured. Make sure you're using the right `clientId` for the Auth0 instance that you used to configure your `auth.config.ts`. * Custom: Make sure that your client is correctly configured to match your `auth.config.ts`. **Answer: Yes, I do see the `aud` value in the *Application ID* field**: Great, repeat [step 1](#step-1-check-whether-authentication-works-on-the-backend) and you should be all set! --- # Source: https://docs.convex.dev/agents/debugging.md # Source: https://docs.convex.dev/functions/debugging.md # Debugging Debugging is the process of figuring out why your code isn't behaving as you expect. ## Debugging during development[​](#debugging-during-development "Direct link to Debugging during development") During development the built-in `console` API allows you to understand what's going on inside your functions: convex/myFunctions.ts TS ``` import { mutation } from "./_generated/server"; import { v } from "convex/values"; export const mutateSomething = mutation({ args: { a: v.number(), b: v.number() }, handler: (_, args) => { console.log("Received args", args); // ... }, }); ``` The following methods are available in the [default Convex runtime](/functions/runtimes.md#default-convex-runtime): * Logging values, with a specified severity level: * `console.log` * `console.info` * `console.warn` * `console.error` * `console.debug` * Logging with a stack trace: * [`console.trace`](https://developer.mozilla.org/en-US/docs/Web/API/console/trace_static) * Measuring execution time: * [`console.time`](https://developer.mozilla.org/en-US/docs/Web/API/console/time_static) * [`console.timeLog`](https://developer.mozilla.org/en-US/docs/Web/API/console/timelog_static) * [`console.timeEnd`](https://developer.mozilla.org/en-US/docs/Web/API/console/timeend_static) The Convex backend also automatically logs all successful function executions and all errors thrown by your functions. You can view these logs: 1. When using the [`ConvexReactClient`](/client/react.md), in your browser developer tools console pane. The logs are sent from your dev deployment to your client, and the client logs them to the browser. Production deployments [**do not** send logs to the client](/functions/error-handling/.md#differences-in-error-reporting-between-dev-and-prod). 2. In your Convex dashboard on the [Logs page](/dashboard/deployments/logs.md). 3. In your terminal with [`npx convex dev`](/cli.md#tail-deployment-logs) during development or [`npx convex logs`](/cli.md#tail-deployment-logs), which only prints logs. ### Using a debugger[​](#using-a-debugger "Direct link to Using a debugger") You can exercise your functions from tests, in which case you can add `debugger;` statements and step through your code. See [Testing](/testing/convex-test.md#debugging-tests). ## Debugging in production[​](#debugging-in-production "Direct link to Debugging in production") When debugging an issue in production your options are: 1. Leverage existing logging 2. Add more logging and deploy a new version of your backend to production Convex backend currently only preserves a limited number of logs, and logs can be erased at any time when the Convex team performs internal maintenance and upgrades. You should therefore set up [log streaming and error reporting](/production/integrations/.md) integrations to enable your team easy access to historical logs and additional information logged by your client. ## Finding relevant logs by Request ID[​](#finding-relevant-logs-by-request-id "Direct link to Finding relevant logs by Request ID") To find the appropriate logs for an error you or your users experience, Convex includes a Request ID in all exception messages in both dev and prod in this format: `[Request ID: ]`. You can copy and paste a Request ID into your Convex dashboard to view the logs for functions started by that request. See the [Dashboard logs page](/dashboard/deployments/logs.md#filter-logs) for details. --- # Source: https://docs.convex.dev/management-api/delete-custom-domain.md # Delete custom domain ``` POST /deployments/:deployment_name/delete_custom_domain ``` Remove a custom domain from a deployment. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/management-api/delete-deploy-key.md # Delete deploy key ``` POST /deployments/:deployment_name/delete_deploy_key ``` Deletes a deploy key for the specified deployment. The `id` in the request body can be the full deploy key (with prefix), encoded token, or the name of the deploy key. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/management-api/delete-deployment.md # Delete deployment ``` POST /deployments/:deployment_name/delete ``` Delete a deployment. This will delete all data and files in the deployment, so we recommend creating and downloading a backup before calling this endpoint. This does not delete the project itself. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/file-storage/delete-files.md # Deleting Files Files stored in Convex can be deleted from [mutations](/functions/mutation-functions.md), [actions](/functions/actions.md), and [HTTP actions](/functions/http-actions.md) via the [`storage.delete()`](/api/interfaces/server.StorageWriter.md#delete) function, which accepts a storage ID. Storage IDs correspond to documents in the `"_storage"` system table (see [Metadata](/file-storage/file-metadata.md)), so they can be validated using the `v.id("_storage")`. convex/images.ts TS ``` import { v } from "convex/values"; import { Id } from "./_generated/dataModel"; import { mutation } from "./_generated/server"; export const deleteById = mutation({ args: { storageId: v.id("_storage"), }, handler: async (ctx, args) => { return await ctx.storage.delete(args.storageId); }, }); ``` --- # Source: https://docs.convex.dev/deployment-api/delete-log-stream.md # Delete log stream ``` POST /delete_log_stream/:id ``` Delete the deployment's log stream with the given id. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/management-api/delete-project.md # Delete project ``` POST /projects/:project_id/delete ``` Delete a project. Deletes all deployments in the project as well. ## Request[​](#request "Direct link to Request") ## Responses[​](#responses "Direct link to Responses") * 200 --- # Source: https://docs.convex.dev/cli/deploy-key-types.md # Deploy keys When you can't log in or use the CLI interactively to specify a project or deployment, for example in a production build environment, the environment variable `CONVEX_DEPLOY_KEY` can be set to a deploy key to make convex CLI commands run non-interactively. Deploy keys identify a deployment, project, or team; confer permission to take certain actions with those resources; and can change the behavior of the convex CLI. ### Developing locally does not require a deploy key[​](#developing-locally-does-not-require-a-deploy-key "Direct link to Developing locally does not require a deploy key") Running `npx convex dev` on a new machine offers the choice to log in or run Convex locally without an account. Logging in stores a *user token* at `~/.convex/config.json` which is used automatically for all CLI use going forward on that machine. This token grants permission to push code to and read/write data from any deployment this user has access to. Using Convex locally without logging in ([anonymous development](/cli/local-deployments.md#anonymous-development)) creates a deployment locally and records this preference for this project in the `.env.local` file in the project directory. The *admin key* for this anonymous backend is stored in `~/.convex/anonymous-convex-backend-state/` along with its serialized data. In either of these cases, there's no reason to set `CONVEX_DEPLOY_KEY`. ### How to set a deploy key[​](#how-to-set-a-deploy-key "Direct link to How to set a deploy key") Generally deploys keys are set in a dashboard of the service that needs the key but in most shells you can set it right before the command, like ``` CONVEX_DEPLOY_KEY='key goes here' npx convex dev ``` or export it before you run the command ``` export CONVEX_DEPLOY_KEY='key goes here' npx convex dev ``` or add it to your `.env.local` file where it will be found by `npx convex` when run in that directory. # Common uses of deploy keys ### Deploying from build pipelines[​](#deploying-from-build-pipelines "Direct link to Deploying from build pipelines") A *production deploy key* specifies the production deployment of a project and grants permissions to deploy code to it. > `prod:qualified-jaguar-123|eyJ2...0=` You can deploying code from a build pipeline where you can't log in (e.g. Vercel, Netlify, Cloudflare build pipelines) Read more about [deploying to production](https://docs.convex.dev/production/hosting/). ### Deploying to preview deployments[​](#deploying-to-preview-deployments "Direct link to Deploying to preview deployments") A *preview deploy key* looks like this: > `preview:team-slug:project-slug|eyJ2...0=` Use a preview deploy key to change the behavior of a normal `npx convex deploy` command to deploy to a preview branch. Read more about [preview deployments](/production/hosting/preview-deployments.md). ### Admin keys[​](#admin-keys "Direct link to Admin keys") An admin key provides complete control over a deployment. An admin key might look like > `bold-hyena-681|01c2...c09c` Unlike other types of deploy key, an admin key does not require a network connection to to be used since it's a irrevocable secret baked into the deployment when created. These keys are used to control [anonymous](/cli/local-deployments.md#anonymous-development) Convex deployments locally without logging in, but rarely need to be set explicitly. Setting `CONVEX_DEPLOY_KEY` to one will cause the Convex CLI to run against that deployment instead of offering a choice. ## Rarer types of deploy keys[​](#rarer-types-of-deploy-keys "Direct link to Rarer types of deploy keys") ### Project tokens[​](#project-tokens "Direct link to Project tokens") A *project token* grants total control over a project to a convex CLI and carries with it the permission to create and use development and production deployments in that project. > `project:team-slug:project-slug|eyJ2...0=` Project tokens are obtained when a user grants an permission to use a project to an organization via an Convex OAuth application. Actions made with the token are on behalf of the user so if a user loses access to a project the token no longer grant access to it. ### Development deploy keys[​](#development-deploy-keys "Direct link to Development deploy keys") A *dev deploy key* might be used to provide an agent full access to a single deployment for development. > `dev:joyful-jaguar-123|eyJ2...0=` This can help limit the blast radius when developing with an agent. To give an agent exclusive access to its own dev deployment, see [Agent Mode](/cli/agent-mode.md). --- # Source: https://docs.convex.dev/deployment-api.md # Deployment API The public interface of a Convex deployment is defined by the functions defined in files in a convex folder. The public HTTP endpoints of every Convex deployment consist of custom HTTP endpoints defined by [HTTP Actions](/functions/http-actions.md) and a static [public HTTP API](/http-api/.md). Deployments also provide private endpoints only for the administrators of that deployment: * [Streaming export API](/streaming-export-api.md) * [Streaming import API](/streaming-import-api.md) * [Platform APIs](/deployment-platform-api.md) --- # Source: https://docs.convex.dev/deployment-platform-api.md # Deployment Platform API info The Convex Deployment API is openly available in Beta. Please contact if your use case requires additional capabilities. Unlike HTTP endpoints which expose application-specific functionality to clients, management API endpoints configure deployments (e.g. modifying environment variables). ## Authorization[​](#authorization "Direct link to Authorization") The Deployment Management API requires a Authorization header with a key that grants admin access to that deployment. [Deployment keys](https://docs.convex.dev/cli/deploy-key-types#development-deploy-keys) created in the [dashboard](https://docs.convex.dev/dashboard/deployments/deployment-settings#url-and-deploy-key) or by API calls can be used for these APIs. [Team Access Tokens](/platform-apis.md#managing-your-own-projects) and [OAuth Application Tokens](/platform-apis/oauth-applications.md) can also be used in depending on whether you are using the Management API on behalf of your own team or on behalf of the team of a user of a Convex integration you've built. Whatever type of key, add the string `"Convex "` to the front. ``` const token = "ey...0="; const response = await fetch( "https://happy-otter-123.convex.cloud/api/v1/list_environment_variables", { headers: { Authorization: `Convex ${token}`, }, }, ); console.log(await response.json()); ``` --- # Source: https://docs.convex.dev/dashboard/deployments/deployment-settings.md # Settings The [deployment settings page](https://dashboard.convex.dev/deployment/settings) gives you access to information and configuration options related to a specific deployment (**production**, your personal **development** deployment, or a **preview** deployment). ## URL and Deploy Key[​](#url-and-deploy-key "Direct link to URL and Deploy Key") The [URL and deploy key page](https://dashboard.convex.dev/deployment/settings) shows: * The URL this deployment is hosted at. Some Convex integrations may require the deployment URL for configuration. * The URL that HTTP Actions for this deployment should be sent to. * The deployment's deploy key, used to [integrate with build tools such as Netlify and Vercel](/production/hosting/.md) and [syncing data with Fivetran and Airbyte](/production/integrations/streaming-import-export.md). ![Deployment Settings Dashboard Page](/assets/images/deployment_settings-58661797d5cadbc484d3d36dde845c04.png) ## Environment Variables[​](#environment-variables "Direct link to Environment Variables") The [environment variables page](https://dashboard.convex.dev/deployment/settings/environment-variables) lets you add, change, remove and copy the deployment's [environment variables](/production/environment-variables.md). ![deployment settings environment variables page](/assets/images/deployment_settings_env_vars-e148766325f85db3409292b10f202bba.png) ## Authentication[​](#authentication "Direct link to Authentication") The [authentication page](https://dashboard.convex.dev/deployment/settings/authentication) shows the values configured in your `auth.config.js` for user [authentication](/auth.md) implementation. ## Backup & Restore[​](#backup--restore "Direct link to Backup & Restore") The [backup & restore page](https://dashboard.convex.dev/deployment/settings/backups) lets you [backup](/database/backup-restore.md) the data stored in your deployment's database and file storage. On this page, you can schedule periodic backups. ![deployment settings export page](/assets/images/backups-7e17da1541fc3eb26194a96ab33414ea.png) ## Integrations[​](#integrations "Direct link to Integrations") The integrations page allows you to configure [log streaming](/production/integrations/.md), [exception reporting](/production/integrations/.md), and [streaming export](/production/integrations/streaming-import-export.md) integrations. ## Pause Deployment[​](#pause-deployment "Direct link to Pause Deployment") On the [pause deployment page](https://dashboard.convex.dev/deployment/settings/pause-deployment) you can [pause your deployment](/production/pause-deployment.md) with the pause button. ![deployment settings pause deployment page](/assets/images/deployment_settings_pause-4e036413269bccced2d58a99d2bb6f98.png) --- # Source: https://docs.convex.dev/client/react/deployment-urls.md # Configuring Deployment URL When [connecting to your backend](/client/react.md#connecting-to-a-backend) it's important to correctly configure the deployment URL. ### Create a Convex project[​](#create-a-convex-project "Direct link to Create a Convex project") The first time you run ``` npx convex dev ``` in your project directory you will create a new Convex project. Your new project includes two deployments: *production* and *development*. The *development* deployment's URL will be saved in `.env.local` or `.env` file, depending on the frontend framework or bundler you're using. You can find the URLs of all deployments in a project by visiting the [deployment settings](/dashboard/deployments/deployment-settings.md) on your Convex [dashboard](https://dashboard.convex.dev). ### Configure the client[​](#configure-the-client "Direct link to Configure the client") Construct a Convex React client by passing in the URL of the Convex deployment. There should generally be a single Convex client in a frontend application. src/index.js ``` import { ConvexProvider, ConvexReactClient } from "convex/react"; const deploymentURL = import.meta.env.VITE_CONVEX_URL; const convex = new ConvexReactClient(deploymentURL); ``` While this URL can be hardcoded, it's convenient to use an environment variable to determine which deployment the client should connect to. Use an environment variable name accessible from your client code according to the frontend framework or bundler you're using. ### Choosing environment variable names[​](#choosing-environment-variable-names "Direct link to Choosing environment variable names") To avoid unintentionally exposing secret environment variables in frontend code, many bundlers require environment variables referenced in frontend code to use a specific prefix. [Vite](https://vitejs.dev/guide/env-and-mode.html) requires environment variables used in frontend code start with `VITE_`, so `VITE_CONVEX_URL` is a good name. [Create React App](https://create-react-app.dev/docs/adding-custom-environment-variables/) requires environment variables used in frontend code to begin with `REACT_APP_`, so the code above uses `REACT_APP_CONVEX_URL`. [Next.js](https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser) requires them to begin with `NEXT_PUBLIC_`, so `NEXT_PUBLIC_CONVEX_URL` is a good name. Bundlers provide different ways to access these variables too: while [Vite uses `import.meta.env.VARIABLE_NAME`](https://vitejs.dev/guide/env-and-mode.html), many other tools like Next.js use the Node.js-like [`process.env.VARIABLE_NAME`](https://nextjs.org/docs/basic-features/environment-variables) ``` import { ConvexProvider, ConvexReactClient } from "convex/react"; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL); ``` [`.env` files](https://www.npmjs.com/package/dotenv) are a common way to wire up different environment variable values in development and production environments. `npx convex dev` will save the deployment URL to the corresponding `.env` file, while trying to infer which bundler your project uses. .env.local ``` NEXT_PUBLIC_CONVEX_URL=https://guiltless-dog-960.convex.cloud # examples of other environment variables that might be passed to the frontend NEXT_PUBLIC_SENTRY_DSN=https://123abc@o123.ingest.sentry.io/1234 NEXT_PUBLIC_LAUNCHDARKLY_SDK_CLIENT_SIDE_ID=01234567890abcdef ``` Your backend functions can use [environment variables](/production/environment-variables.md) configured on your dashboard. They do not source values from `.env` files. --- # Source: https://docs.convex.dev/dashboard/deployments.md # Deployments Each project in Convex has a main production deployment, and each developer on your team can also set up their own personal development deployment. Additionally, there are [preview deployments](/production/hosting/preview-deployments.md) used to test backend changes before they're deployed to production. While on a [deployment page](https://dashboard.convex.dev/deployment), you may switch between production, your development deployment, and any preview deployments by using the dropdown menu on the top-left of the page. ![Deployment switcher](/assets/images/deployment_menu-b2e23e5c7d44f8defdad7685df75ef29.png) --- # Source: https://docs.convex.dev/database/document-ids.md # Document IDs **Example:** [Relational Data Modeling](https://github.com/get-convex/convex-demos/tree/main/relational-data-modeling) Every document in convex has a globally unique string *document ID* that is automatically generated by the system. ``` const userId = await ctx.db.insert("users", { name: "Michael Jordan" }); ``` You can use this ID to efficiently read a single document using the `get` method: ``` const retrievedUser = await ctx.db.get("users", userId); ``` You can access the ID of a document in the [`_id` field](/database/types.md#system-fields): ``` const userId = retrievedUser._id; ``` Also, this same ID can be used to update that document in place: ``` await ctx.db.patch("users", userId, { name: "Steph Curry" }); ``` Convex generates an [`Id`](/generated-api/data-model.md#id) TypeScript type based on your [schema](/database/schemas.md) that is parameterized over your table names: ``` import { Id } from "./_generated/dataModel"; const userId: Id<"users"> = user._id; ``` IDs are strings at runtime, but the [`Id`](/generated-api/data-model.md#id) type can be used to distinguish IDs from other strings at compile time. ## References and relationships[​](#references-and-relationships "Direct link to References and relationships") In Convex, you can reference a document simply by embedding its `Id` in another document: ``` await ctx.db.insert("books", { title, ownerId: user._id, }); ``` You can follow references with `ctx.db.get`: ``` const user = await ctx.db.get("users", book.ownerId); ``` And [query for documents](/database/reading-data/.md) with a reference: ``` const myBooks = await ctx.db .query("books") .filter((q) => q.eq(q.field("ownerId"), user._id)) .collect(); ``` Using `Id`s as references can allow you to build a complex data model. ## Trading off deeply nested documents vs. relationships[​](#trading-off-deeply-nested-documents-vs-relationships "Direct link to Trading off deeply nested documents vs. relationships") While it's useful that Convex supports nested objects and arrays, you should keep documents relatively small in size. In practice, we recommend limiting Arrays to no more than 5-10 elements and avoiding deeply nested Objects. Instead, leverage separate tables, documents, and references to structure your data. This will lead to better maintainability and performance as your project grows. ## Serializing IDs[​](#serializing-ids "Direct link to Serializing IDs") IDs are strings, which can be easily inserted into URLs or stored outside of Convex. You can pass an ID string from an external source (like a URL) into a Convex function and get the corresponding object. If you're using TypeScript on the client you can cast a string to the `Id` type: src/App.tsx ``` import { useQuery } from "convex/react"; import { Id } from "../convex/_generated/dataModel"; import { api } from "../convex/_generated/api"; export function App() { const id = localStorage.getItem("myIDStorage"); const task = useQuery(api.tasks.getTask, { taskId: id as Id<"tasks"> }); // ... } ``` Since this ID is coming from an external source, use an argument validator or [`ctx.db.normalizeId`](/api/interfaces/server.GenericDatabaseReader.md#normalizeid) to confirm that the ID belongs to the expected table before returning the object. convex/tasks.ts TS ``` import { query } from "./_generated/server"; import { v } from "convex/values"; export const getTask = query({ args: { taskId: v.id("tasks"), }, handler: async (ctx, args) => { const task = await ctx.db.get("tasks", args.taskId); // ... }, }); ``` Related posts from [![Stack](/img/stack-logo-dark.svg)![Stack](/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # Source: https://docs.convex.dev/platform-apis/embedded-dashboard.md # Embedding the dashboard Convex provides a hosted dashboard that is embeddable via iframe. Embedding the dashboard is useful for developers building AI app generators, like [Convex Chef](https://chef.convex.dev). You can embed the Convex dashboard by adding an `