# Table of Contents - [Convex Developer Hub](#convex-developer-hub) - [Convex Docs | Convex Developer Hub](#convex-docs-convex-developer-hub) - [Quickstarts | Convex Developer Hub](#quickstarts-convex-developer-hub) - [Functions | Convex Developer Hub](#functions-convex-developer-hub) - [Database | Convex Developer Hub](#database-convex-developer-hub) - [Realtime | Convex Developer Hub](#realtime-convex-developer-hub) - [Convex Tutorial: A Chat App | Convex Developer Hub](#convex-tutorial-a-chat-app-convex-developer-hub) - [Python | Convex Developer Hub](#python-convex-developer-hub) - [Chef | Convex Developer Hub](#chef-convex-developer-hub) - [Self Hosting | Convex Developer Hub](#self-hosting-convex-developer-hub) - [Scheduling | Convex Developer Hub](#scheduling-convex-developer-hub) - [Rust | Convex Developer Hub](#rust-convex-developer-hub) - [OpenAPI & Other Languages | Convex Developer Hub](#openapi-other-languages-convex-developer-hub) - [Convex React Native | Convex Developer Hub](#convex-react-native-convex-developer-hub) - [ESLint rules | Convex Developer Hub](#eslint-rules-convex-developer-hub) - [Svelte | Convex Developer Hub](#svelte-convex-developer-hub) - [Vue | Convex Developer Hub](#vue-convex-developer-hub) - [AI & Search | Convex Developer Hub](#ai-search-convex-developer-hub) - [Convex Overview | Convex Developer Hub](#convex-overview-convex-developer-hub) - [Components | Convex Developer Hub](#components-convex-developer-hub) - [Platform APIs | Convex Developer Hub](#platform-apis-convex-developer-hub) - [Convex with TanStack Query | Convex Developer Hub](#convex-with-tanstack-query-convex-developer-hub) - [Errors and Warnings | Convex Developer Hub](#errors-and-warnings-convex-developer-hub) - [Dashboard | Convex Developer Hub](#dashboard-convex-developer-hub) - [iOS & macOS Swift | Convex Developer Hub](#ios-macos-swift-convex-developer-hub) - [Next.js | Convex Developer Hub](#next-js-convex-developer-hub) - [React Quickstart | Convex Developer Hub](#react-quickstart-convex-developer-hub) - [Testing | Convex Developer Hub](#testing-convex-developer-hub) - [Remix Quickstart | Convex Developer Hub](#remix-quickstart-convex-developer-hub) - [React Native Quickstart | Convex Developer Hub](#react-native-quickstart-convex-developer-hub) - [AI Code Generation | Convex Developer Hub](#ai-code-generation-convex-developer-hub) - [Android Kotlin | Convex Developer Hub](#android-kotlin-convex-developer-hub) - [iOS Swift Quickstart | Convex Developer Hub](#ios-swift-quickstart-convex-developer-hub) - [Svelte Quickstart | Convex Developer Hub](#svelte-quickstart-convex-developer-hub) - [Android Kotlin Quickstart | Convex Developer Hub](#android-kotlin-quickstart-convex-developer-hub) - [Python Quickstart | Convex Developer Hub](#python-quickstart-convex-developer-hub) - [Node.js Quickstart | Convex Developer Hub](#node-js-quickstart-convex-developer-hub) - [Nuxt Quickstart | Convex Developer Hub](#nuxt-quickstart-convex-developer-hub) - [Script Tag Quickstart | Convex Developer Hub](#script-tag-quickstart-convex-developer-hub) - [Bun Quickstart | Convex Developer Hub](#bun-quickstart-convex-developer-hub) - [TanStack Start Quickstart | Convex Developer Hub](#tanstack-start-quickstart-convex-developer-hub) - [Rust Quickstart | Convex Developer Hub](#rust-quickstart-convex-developer-hub) - [Convex React | Convex Developer Hub](#convex-react-convex-developer-hub) - [Generated Code | Convex Developer Hub](#generated-code-convex-developer-hub) - [Vue Quickstart | Convex Developer Hub](#vue-quickstart-convex-developer-hub) - [JavaScript | Convex Developer Hub](#javascript-convex-developer-hub) - [Deployment API | Convex Developer Hub](#deployment-api-convex-developer-hub) - [File Storage | Convex Developer Hub](#file-storage-convex-developer-hub) - [Next.js Quickstart | Convex Developer Hub](#next-js-quickstart-convex-developer-hub) - [Authentication | Convex Developer Hub](#authentication-convex-developer-hub) - [convex | Convex Developer Hub](#convex-convex-developer-hub) - [Deploying Your App to Production | Convex Developer Hub](#deploying-your-app-to-production-convex-developer-hub) - [CLI | Convex Developer Hub](#cli-convex-developer-hub) - [Management API | Convex Developer Hub](#management-api-convex-developer-hub) - [AI Agents | Convex Developer Hub](#ai-agents-convex-developer-hub) - [Convex Tutorial: Scaling Your App | Convex Developer Hub](#convex-tutorial-scaling-your-app-convex-developer-hub) - [Mutations | Convex Developer Hub](#mutations-convex-developer-hub) - [Queries | Convex Developer Hub](#queries-convex-developer-hub) - [Internal Functions | Convex Developer Hub](#internal-functions-convex-developer-hub) - [Bundling | Convex Developer Hub](#bundling-convex-developer-hub) - [Runtimes | Convex Developer Hub](#runtimes-convex-developer-hub) - [Argument and Return Value Validation | Convex Developer Hub](#argument-and-return-value-validation-convex-developer-hub) - [Actions | Convex Developer Hub](#actions-convex-developer-hub) - [Debugging | Convex Developer Hub](#debugging-convex-developer-hub) - [Error Handling | Convex Developer Hub](#error-handling-convex-developer-hub) - [HTTP Actions | Convex Developer Hub](#http-actions-convex-developer-hub) - [Document IDs | Convex Developer Hub](#document-ids-convex-developer-hub) - [Backup & Restore | Convex Developer Hub](#backup-restore-convex-developer-hub) - [Writing Data | Convex Developer Hub](#writing-data-convex-developer-hub) - [Data Types | Convex Developer Hub](#data-types-convex-developer-hub) - [Reading Data | Convex Developer Hub](#reading-data-convex-developer-hub) - [Data Import & Export | Convex Developer Hub](#data-import-export-convex-developer-hub) - [Schemas | Convex Developer Hub](#schemas-convex-developer-hub) - [Paginated Queries | Convex Developer Hub](#paginated-queries-convex-developer-hub) - [Indexes | Convex Developer Hub](#indexes-convex-developer-hub) - [OCC and Atomicity | Convex Developer Hub](#occ-and-atomicity-convex-developer-hub) - [Convex Tutorial: Calling External Services | Convex Developer Hub](#convex-tutorial-calling-external-services-convex-developer-hub) - [Dev workflow | Convex Developer Hub](#dev-workflow-convex-developer-hub) - [The Zen of Convex | Convex Developer Hub](#the-zen-of-convex-convex-developer-hub) - [Understanding Components | Convex Developer Hub](#understanding-components-convex-developer-hub) - [Convex Auth | Convex Developer Hub](#convex-auth-convex-developer-hub) - [Cron Jobs | Convex Developer Hub](#cron-jobs-convex-developer-hub) - [Using Cursor with Convex | Convex Developer Hub](#using-cursor-with-convex-convex-developer-hub) - [Scheduled Functions | Convex Developer Hub](#scheduled-functions-convex-developer-hub) - [Best Practices | Convex Developer Hub](#best-practices-convex-developer-hub) - [Storing Generated Files | Convex Developer Hub](#storing-generated-files-convex-developer-hub) - [Streaming Import | Convex Developer Hub](#streaming-import-convex-developer-hub) - [Full Text Search | Convex Developer Hub](#full-text-search-convex-developer-hub) - [Convex & Clerk | Convex Developer Hub](#convex-clerk-convex-developer-hub) - [Module: nextjs | Convex Developer Hub](#module-nextjs-convex-developer-hub) - [Vector Search | Convex Developer Hub](#vector-search-convex-developer-hub) - [OAuth Applications | Convex Developer Hub](#oauth-applications-convex-developer-hub) - [Get token details | Convex Developer Hub](#get-token-details-convex-developer-hub) - [List deployments for team | Convex Developer Hub](#list-deployments-for-team-convex-developer-hub) - [Nuxt | Convex Developer Hub](#nuxt-convex-developer-hub) - [Uploading and Storing Files | Convex Developer Hub](#uploading-and-storing-files-convex-developer-hub) - [Swift and Convex type conversion | Convex Developer Hub](#swift-and-convex-type-conversion-convex-developer-hub) - [Testing Local Backend | Convex Developer Hub](#testing-local-backend-convex-developer-hub) - [Continuous Integration | Convex Developer Hub](#continuous-integration-convex-developer-hub) - [Kotlin and Convex type conversion | Convex Developer Hub](#kotlin-and-convex-type-conversion-convex-developer-hub) - [Multiple Repositories | Convex Developer Hub](#multiple-repositories-convex-developer-hub) - [Using GitHub Copilot with Convex | Convex Developer Hub](#using-github-copilot-with-convex-convex-developer-hub) - [List local deployments | Convex Developer Hub](#list-local-deployments-convex-developer-hub) - [TanStack Start | Convex Developer Hub](#tanstack-start-convex-developer-hub) - [Configuring Deployment URL | Convex Developer Hub](#configuring-deployment-url-convex-developer-hub) - [Data Export | Convex Developer Hub](#data-export-convex-developer-hub) - [Agent Mode | Convex Developer Hub](#agent-mode-convex-developer-hub) - [Optimistic Updates | Convex Developer Hub](#optimistic-updates-convex-developer-hub) - [api.js | Convex Developer Hub](#api-js-convex-developer-hub) - [Using Windsurf with Convex | Convex Developer Hub](#using-windsurf-with-convex-convex-developer-hub) - [Using Components | Convex Developer Hub](#using-components-convex-developer-hub) - [Embedding the dashboard | Convex Developer Hub](#embedding-the-dashboard-convex-developer-hub) - [Getting Started with Agent | Convex Developer Hub](#getting-started-with-agent-convex-developer-hub) - [Convex MCP Server | Convex Developer Hub](#convex-mcp-server-convex-developer-hub) - [Script Tag | Convex Developer Hub](#script-tag-convex-developer-hub) - [Projects | Convex Developer Hub](#projects-convex-developer-hub) - [dataModel.d.ts | Convex Developer Hub](#datamodel-d-ts-convex-developer-hub) - [Node.js | Convex Developer Hub](#node-js-convex-developer-hub) - [Next.js Server Rendering | Convex Developer Hub](#next-js-server-rendering-convex-developer-hub) - [Next.js Pages Router | Convex Developer Hub](#next-js-pages-router-convex-developer-hub) - [Local Deployments for Development | Convex Developer Hub](#local-deployments-for-development-convex-developer-hub) - [File Storage | Convex Developer Hub](#file-storage-convex-developer-hub) - [Bun | Convex Developer Hub](#bun-convex-developer-hub) - [Next.js Pages Quickstart | Convex Developer Hub](#next-js-pages-quickstart-convex-developer-hub) - [TanStack Start with Clerk | Convex Developer Hub](#tanstack-start-with-clerk-convex-developer-hub) - [System Tables | Convex Developer Hub](#system-tables-convex-developer-hub) - [Streaming Export | Convex Developer Hub](#streaming-export-convex-developer-hub) - [Module: react-clerk | Convex Developer Hub](#module-react-clerk-convex-developer-hub) - [Deploy keys | Convex Developer Hub](#deploy-keys-convex-developer-hub) - [Deleting Files | Convex Developer Hub](#deleting-files-convex-developer-hub) - [Convex HTTP API | Convex Developer Hub](#convex-http-api-convex-developer-hub) - [Authoring Components | Convex Developer Hub](#authoring-components-convex-developer-hub) - [List team members | Convex Developer Hub](#list-team-members-convex-developer-hub) - [Human Agents | Convex Developer Hub](#human-agents-convex-developer-hub) - [Application Errors | Convex Developer Hub](#application-errors-convex-developer-hub) - [Regions | Convex Developer Hub](#regions-convex-developer-hub) - [Files and Images in Agent messages | Convex Developer Hub](#files-and-images-in-agent-messages-convex-developer-hub) - [Teams | Convex Developer Hub](#teams-convex-developer-hub) - [Debugging | Convex Developer Hub](#debugging-convex-developer-hub) - [TypeScript | Convex Developer Hub](#typescript-convex-developer-hub) - [Data Import | Convex Developer Hub](#data-import-convex-developer-hub) - [Serving Files | Convex Developer Hub](#serving-files-convex-developer-hub) - [Module: react-auth0 | Convex Developer Hub](#module-react-auth0-convex-developer-hub) - [Debugging Authentication | Convex Developer Hub](#debugging-authentication-convex-developer-hub) - [Accessing File Metadata | Convex Developer Hub](#accessing-file-metadata-convex-developer-hub) - [Workflows | Convex Developer Hub](#workflows-convex-developer-hub) - [Rate Limiting | Convex Developer Hub](#rate-limiting-convex-developer-hub) - [Contact Us | Convex Developer Hub](#contact-us-convex-developer-hub) - [LLM Context | Convex Developer Hub](#llm-context-convex-developer-hub) - [Networking | Convex Developer Hub](#networking-convex-developer-hub) - [Pausing a Deployment | Convex Developer Hub](#pausing-a-deployment-convex-developer-hub) - [Status and Guarantees | Convex Developer Hub](#status-and-guarantees-convex-developer-hub) - [Convex & Auth0 | Convex Developer Hub](#convex-auth0-convex-developer-hub) - [Data | Convex Developer Hub](#data-convex-developer-hub) - [Deployments | Convex Developer Hub](#deployments-convex-developer-hub) - [RAG (Retrieval-Augmented Generation) with the Agent component | Convex Developer Hub](#rag-retrieval-augmented-generation-with-the-agent-component-convex-developer-hub) - [Environment Variables | Convex Developer Hub](#environment-variables-convex-developer-hub) - [List projects | Convex Developer Hub](#list-projects-convex-developer-hub) - [Integrations | Convex Developer Hub](#integrations-convex-developer-hub) - [Project Configuration | Convex Developer Hub](#project-configuration-convex-developer-hub) - [Auth in Functions | Convex Developer Hub](#auth-in-functions-convex-developer-hub) - [Settings | Convex Developer Hub](#settings-convex-developer-hub) - [Playground | Convex Developer Hub](#playground-convex-developer-hub) - [Threads | Convex Developer Hub](#threads-convex-developer-hub) - [Custom OIDC Provider | Convex Developer Hub](#custom-oidc-provider-convex-developer-hub) - [Usage Tracking | Convex Developer Hub](#usage-tracking-convex-developer-hub) - [Agent Definition and Usage | Convex Developer Hub](#agent-definition-and-usage-convex-developer-hub) - [Create project | Convex Developer Hub](#create-project-convex-developer-hub) - [Convex & WorkOS AuthKit | Convex Developer Hub](#convex-workos-authkit-convex-developer-hub) - [convex-test | Convex Developer Hub](#convex-test-convex-developer-hub) - [Messages | Convex Developer Hub](#messages-convex-developer-hub) - [Streaming | Convex Developer Hub](#streaming-convex-developer-hub) - [Storing Users in the Convex Database | Convex Developer Hub](#storing-users-in-the-convex-database-convex-developer-hub) - [Create custom domain | Convex Developer Hub](#create-custom-domain-convex-developer-hub) - [Hosting and Deployment | Convex Developer Hub](#hosting-and-deployment-convex-developer-hub) - [Tools | Convex Developer Hub](#tools-convex-developer-hub) - [Class: ConvexReactClient | Convex Developer Hub](#class-convexreactclient-convex-developer-hub) - [Module: browser | Convex Developer Hub](#module-browser-convex-developer-hub) - [Class: ConvexHttpClient | Convex Developer Hub](#class-convexhttpclient-convex-developer-hub) - [Preview Deployments | Convex Developer Hub](#preview-deployments-convex-developer-hub) - [Class: ConvexClient | Convex Developer Hub](#class-convexclient-convex-developer-hub) - [server.js | Convex Developer Hub](#server-js-convex-developer-hub) - [Module: react | Convex Developer Hub](#module-react-convex-developer-hub) - [Create deploy key | Convex Developer Hub](#create-deploy-key-convex-developer-hub) - [Unpause deployment | Convex Developer Hub](#unpause-deployment-convex-developer-hub) - [Deployment Platform API | Convex Developer Hub](#deployment-platform-api-convex-developer-hub) - [List deployments | Convex Developer Hub](#list-deployments-convex-developer-hub) - [Namespace: Base64 | Convex Developer Hub](#namespace-base64-convex-developer-hub) - [Module: values | Convex Developer Hub](#module-values-convex-developer-hub) - [Module: server | Convex Developer Hub](#module-server-convex-developer-hub) - [Limits | Convex Developer Hub](#limits-convex-developer-hub) - [Logs | Convex Developer Hub](#logs-convex-developer-hub) - [Exception Reporting | Convex Developer Hub](#exception-reporting-convex-developer-hub) - [Streaming Data in and out of Convex | Convex Developer Hub](#streaming-data-in-and-out-of-convex-convex-developer-hub) - [Schema Philosophy | Convex Developer Hub](#schema-philosophy-convex-developer-hub) - [Interface: GenericActionCtx | Convex Developer Hub](#interface-genericactionctx-datamodel-convex-developer-hub) - [Filters | Convex Developer Hub](#filters-convex-developer-hub) - [Functions | Convex Developer Hub](#functions-convex-developer-hub) - [Log Streams | Convex Developer Hub](#log-streams-convex-developer-hub) - [Interface: Auth | Convex Developer Hub](#interface-auth-convex-developer-hub) - [Introduction to Indexes and Query Performance | Convex Developer Hub](#introduction-to-indexes-and-query-performance-convex-developer-hub) --- # Convex Developer Hub [Skip to main content](https://docs.convex.dev/*#__docusaurus_skipToContent_fallback) Page Not Found ============== We could not find what you were looking for. Please contact the owner of the site that linked you to the original URL and let them know their link is broken. We use third-party cookies to understand how people interact with our site. See our [Privacy Policy](https://www.convex.dev/legal/privacy/) to learn more. DeclineAccept --- # Convex Docs | Convex Developer Hub [Skip to main content](https://docs.convex.dev/home#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex is the open source, reactive database where queries are TypeScript code running right in the database. Just like React components react to state changes, Convex queries react to database changes. Convex provides a database, a place to write your server functions, and client libraries. It makes it easy to build and scale dynamic live-updating apps. [Tutorial: Build a chat app\ --------------------------\ \ Follow a step-by-step tutorial to build your first Convex app - a real-time chat application.](https://docs.convex.dev/tutorial) [Understanding Convex\ --------------------\ \ Learn about the core concepts and architecture that make Convex unique and powerful.](https://docs.convex.dev/understanding) Get Started[​](https://docs.convex.dev/home#get-started "Direct link to Get Started") -------------------------------------------------------------------------------------- [Prompt to start an app with Convex Chef\ ---------------------------------------](https://chef.convex.dev/) Your favorite frameworks: [React Logo\ \ React\ -----](https://docs.convex.dev/quickstart/react) [Next.js\ -------](https://docs.convex.dev/quickstart/nextjs) [Remix\ -----](https://docs.convex.dev/quickstart/remix) [TanStack Start\ --------------](https://docs.convex.dev/quickstart/tanstack-start) [React Native\ ------------](https://docs.convex.dev/quickstart/react-native) [Vue\ ---](https://docs.convex.dev/quickstart/vue) [Nuxt\ ----](https://docs.convex.dev/quickstart/nuxt) [Svelte\ ------](https://docs.convex.dev/quickstart/svelte) [Node.js\ -------](https://docs.convex.dev/quickstart/nodejs) [Bun Logo\ \ Bun\ ---](https://docs.convex.dev/quickstart/bun) [HTML5 Logo\ \ Script tag\ ----------](https://docs.convex.dev/quickstart/script-tag) Your favorite languages: [JavaScript\ ----------](https://docs.convex.dev/client/javascript) [Python\ ------](https://docs.convex.dev/quickstart/python) [iOS Swift\ ---------](https://docs.convex.dev/quickstart/swift) [Android Kotlin\ --------------](https://docs.convex.dev/quickstart/android) [Rust\ ----](https://docs.convex.dev/quickstart/rust) Why Convex?[​](https://docs.convex.dev/home#why-convex "Direct link to Why Convex?") ------------------------------------------------------------------------------------- [Backends for Product Developers\ -------------------------------](https://www.youtube.com/watch?v=Xjud1weG4z8) [Intro to Convex\ ---------------](https://www.youtube.com/watch?v=UVvd7BF99-4) [Supercharging your app with a reactive backend\ ----------------------------------------------](https://www.youtube.com/watch?v=V6En7UO4Ui0) [Why I use Convex over Supabase as my BaaS\ -----------------------------------------](https://www.youtube.com/watch?v=O_HXVAMPEbc) Read the team's Perspectives on [Stack](https://stack.convex.dev/) : [Convex vs Relational Databases\ ------------------------------](https://stack.convex.dev/convex-vs-relational-databases) [Convex vs Firebase\ ------------------](https://stack.convex.dev/convex-vs-firebase) [How Convex Works\ ----------------](https://stack.convex.dev/how-convex-works) Learn Convex[​](https://docs.convex.dev/home#learn-convex "Direct link to Learn Convex") ----------------------------------------------------------------------------------------- [Convex with Next.js Quickstart\ ------------------------------](https://www.youtube.com/watch?v=vaQZYRSiimI) [Notion Clone: Next.js 13, React, Convex, Tailwind\ -------------------------------------------------](https://www.youtube.com/watch?v=0OaDyjB9Ib8) [Build a Saas Podcast Platform in Next.js\ ----------------------------------------](https://www.youtube.com/watch?v=zfAb95tJvZQ) [Building a Subscription Based SaaS with Stripe\ ----------------------------------------------](https://www.youtube.com/watch?v=Vjtn9pWAZDI) See more walkthroughs and patterns on [Stack](https://stack.convex.dev/) [Build AI Apps\ -------------](https://stack.convex.dev/tag/AI) [Convex Patterns\ ---------------](https://stack.convex.dev/tag/Patterns) [Convex Walkthroughs\ -------------------](https://stack.convex.dev/tag/Walkthroughs) We use third-party cookies to understand how people interact with our site. See our [Privacy Policy](https://www.convex.dev/legal/privacy/) to learn more. DeclineAccept --- # Quickstarts | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstarts#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Quickly get up and running with your favorite frontend tooling: [React Logo\ \ React\ -----](https://docs.convex.dev/quickstart/react) [Next.js\ -------](https://docs.convex.dev/quickstart/nextjs) [Remix\ -----](https://docs.convex.dev/quickstart/remix) [TanStack Start\ --------------](https://docs.convex.dev/quickstart/tanstack-start) [React Native\ ------------](https://docs.convex.dev/quickstart/react-native) [Vue\ ---](https://docs.convex.dev/quickstart/vue) [Nuxt\ ----](https://docs.convex.dev/quickstart/nuxt) [Svelte\ ------](https://docs.convex.dev/quickstart/svelte) [Node.js\ -------](https://docs.convex.dev/quickstart/nodejs) [Bun Logo\ \ Bun\ ---](https://docs.convex.dev/quickstart/bun) [HTML5 Logo\ \ Script tag\ ----------](https://docs.convex.dev/quickstart/script-tag) Quickly get up and running with your favorite languages: [JavaScript\ ----------](https://docs.convex.dev/client/javascript) [Python\ ------](https://docs.convex.dev/quickstart/python) [iOS Swift\ ---------](https://docs.convex.dev/quickstart/swift) [Android Kotlin\ --------------](https://docs.convex.dev/quickstart/android) [Rust\ ----](https://docs.convex.dev/quickstart/rust) We use third-party cookies to understand how people interact with our site. See our [Privacy Policy](https://www.convex.dev/legal/privacy/) to learn more. DeclineAccept --- # Functions | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Functions run on the backend and are written in JavaScript (or TypeScript). They are automatically available as APIs accessed through [client libraries](https://docs.convex.dev/client/react) . Everything you do in the Convex backend starts from functions. There are three types of functions: * [Queries](https://docs.convex.dev/functions/query-functions) read data from your Convex database and are automatically cached and subscribable (realtime, reactive). * [Mutations](https://docs.convex.dev/functions/mutation-functions) write data to the database and run as a transaction. * [Actions](https://docs.convex.dev/functions/actions) can call OpenAI, Stripe, Twilio, or any other service or API you need to make your app work. You can also build [HTTP actions](https://docs.convex.dev/functions/http-actions) when you want to call your functions from a webhook or a custom client. Here's an overview of the three different types of Convex functions and what they can do: | | Queries | Mutations | Actions | | --- | --- | --- | --- | | Database access | Yes | Yes | No | | Transactional | Yes | Yes | No | | Cached | Yes | No | No | | Real-time Updates | Yes | No | No | | External API Calls (fetch) | No | No | Yes | We use third-party cookies to understand how people interact with our site. See our [Privacy Policy](https://www.convex.dev/legal/privacy/) to learn more. DeclineAccept --- # Database | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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](https://docs.convex.dev/functions) 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](https://docs.convex.dev/database#tables) , [documents](https://docs.convex.dev/database#documents) and [schemas](https://docs.convex.dev/database#schemas) below, then move on to [Reading Data](https://docs.convex.dev/database/reading-data/) and [Writing Data](https://docs.convex.dev/database/writing-data) . As your app grows more complex you'll need more from your database: * Relational data modeling with [Document IDs](https://docs.convex.dev/database/document-ids) * Fast querying with [Indexes](https://docs.convex.dev/database/reading-data/indexes/) * Exposing large datasets with [Paginated Queries](https://docs.convex.dev/database/pagination) * Type safety by [Defining a Schema](https://docs.convex.dev/database/schemas) * Interoperability with data [Import & Export](https://docs.convex.dev/database/import-export/) Tables[​](https://docs.convex.dev/database#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[​](https://docs.convex.dev/database#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](https://docs.convex.dev/database/types) to learn more about the types supported in Convex and [Document IDs](https://docs.convex.dev/database/document-ids) to learn about how to use those types to model your data. Schemas[​](https://docs.convex.dev/database#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 schemaexport 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](https://docs.convex.dev/database/schemas) to learn more about schemas. [Next: Reading Data\ ------------------\ \ Query and read data from Convex database tables](https://docs.convex.dev/database/reading-data) Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) We use third-party cookies to understand how people interact with our site. See our [Privacy Policy](https://www.convex.dev/legal/privacy/) to learn more. DeclineAccept --- # Realtime | Convex Developer Hub [Skip to main content](https://docs.convex.dev/realtime#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Turns out Convex is automatically realtime! You don’t have to do anything special if you are already using [query functions](https://docs.convex.dev/functions/query-functions) , [database](https://docs.convex.dev/database) , and [client libraries](https://docs.convex.dev/client/react/) in your app. Convex tracks the dependencies to your query functions, including database changes, and triggers the subscription in the client libraries. ![Convex is automatically reactive and realtime](https://docs.convex.dev/assets/images/realtime-3197272a21b075792f6ac922af228378.gif) Aside from building a highly interactive app with ease, there are other benefits to the realtime architecture of Convex: Automatic caching[​](https://docs.convex.dev/realtime#automatic-caching "Direct link to Automatic caching") ------------------------------------------------------------------------------------------------------------ Convex automatically caches the result of your query functions so that future calls just read from the cache. The cache is updated if the data ever changes. You don't get charged for database bandwidth for cached reads. This requires no work or bookkeeping from you. Consistent data across your app[​](https://docs.convex.dev/realtime#consistent-data-across-your-app "Direct link to Consistent data across your app") ------------------------------------------------------------------------------------------------------------------------------------------------------ Every client subscription gets updated simultaneously to the same snapshot of the database. Your app always displays the most consistent view of your data. This avoids bugs like increasing the number of items in the shopping cart and not showing that an item is sold out. Learn more[​](https://docs.convex.dev/realtime#learn-more "Direct link to Learn more") --------------------------------------------------------------------------------------- Learn how to work with realtime and reactive queries in Convex on [Stack](https://stack.convex.dev/tag/Reactivity) . Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) * [Automatic caching](https://docs.convex.dev/realtime#automatic-caching) * [Consistent data across your app](https://docs.convex.dev/realtime#consistent-data-across-your-app) * [Learn more](https://docs.convex.dev/realtime#learn-more) We use third-party cookies to understand how people interact with our site. See our [Privacy Policy](https://www.convex.dev/legal/privacy/) to learn more. DeclineAccept --- # Convex Tutorial: A Chat App | Convex Developer Hub [Skip to main content](https://docs.convex.dev/tutorial/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex provides you with a fully featured backend with cloud functions, database, scheduling, and a sync engine that keeps your frontend and backend up to date in real-time. Today, in about **10 lines of code,** we'll build a backend that reads and writes to the database and automatically updates all users in a chat app. After that we'll see how to connect to external services and setup your product for success and scale. Start developing with Convex[​](https://docs.convex.dev/tutorial/#start-developing-with-convex "Direct link to Start developing with Convex") ---------------------------------------------------------------------------------------------------------------------------------------------- Before you begin: You'll need Node.js 18+ and Git Ensure you have Node.js version 18 or greater installed on your computer. You can check your version of Node.js by running `node --version` in your terminal. If you don't have the appropriate version of Node.js installed, [install it from the Node.js website.](https://nodejs.org/en) In addition, this walkthrough requires Git, so verify you have it installed by running `git -v` in your terminal. If not, head over to the [Git website](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) for installation instructions. First, clone the example project repo from GitHub and install the dependencies: git clone https://github.com/get-convex/convex-tutorial.gitcd convex-tutorialnpm install This app's `dev` npm command sets up Convex and then runs the web app: npm run dev During setup, you'll see that Convex uses your GitHub account for authentication. Sign into Convex with GitHub and then accept the default project setup prompts. This will **automatically create your backend** and a folder called `convex/` in your project, where you'll write your backend code. **Make sure you keep this command (`npm run dev`) running in the background throughout this tutorial.** It's running both the dev web server for the frontend as well as the `convex` command in the background to keep your backend in sync with your local codebase. Once your server is up and running, open [localhost:5173](http://localhost:5173/) and check it out: ![Chat UI](https://docs.convex.dev/assets/images/tut_chat_ui-9ab95f331e3132c9c61a0e2fc4eaf16c.png) If you try sending a message now, you'll see an alert telling you the mutation is not yet implemented. We'll do that in a bit, but first here's a quick summary of how Convex works. How Convex works[​](https://docs.convex.dev/tutorial/#how-convex-works "Direct link to How Convex works") ---------------------------------------------------------------------------------------------------------- ![Overview of the sync engine](https://docs.convex.dev/assets/images/ConvexSyncEngine-3271d28868180073da72479d72a5d93e.png) **Database.** The Convex database is a document-relational database, which means you have tables with JSON like documents in them. All documents have an auto-generated `_id` that can be used to create relations between documents. You interact with the database through mutation and query functions that are written entirely in TypeScript. **Mutation functions.** Mutations are TypeScript functions that update the database. All mutation functions in Convex run as a database transaction. So either all the changes are committed, or none are. **Query functions.** Queries are TypeScript functions that can only read from the database. As we'll see in a bit, you subscribe to them from your frontend to keep your app automatically up to date. Your frontend registers to listen to query updates through the **client library**. The client libraries talk to Convex via WebSockets for fast realtime updates. The **sync engine** reruns query functions when any input to the function changes, including any changes to the documents in the database that the query reads. It then updates every app listening to the query. The sync engine is the combination of queries, mutations and the database. Now, let's dive into the code! Your first `mutation`[​](https://docs.convex.dev/tutorial/#your-first-mutation "Direct link to your-first-mutation") --------------------------------------------------------------------------------------------------------------------- Create a new file in your `convex/` folder called `chat.ts`. This is where you'll write your Convex backend functions for this application. **Add the following to your `convex/chat.ts` file.** import { mutation } from "./_generated/server";import { v } from "convex/values";export const sendMessage = mutation({ args: { user: v.string(), body: v.string(), }, handler: async (ctx, args) => { console.log("This TypeScript function is running on the server."); await ctx.db.insert("messages", { user: args.user, body: args.body, }); },}); Let's break this down: 1. You've added a new backend `mutation` function called `sendMessage` and exposed it as a public api. 2. The whole function automatically runs as a transaction that will roll back if an exception is thrown. 3. Since this is just a TypeScript function you can drop `console.log` lines to do simple debugging on the server. 4. `args:` ensures the function arguments are two strings named `user` and `body`, both as types and runtime values. 5. `ctx.db.insert` tells Convex to insert a new message document into the table. Now, let's connect this mutation to your web app. **Update your `src/App.tsx` file like so:** // Import `useMutation` and `api` from Convex.import { useMutation } from "convex/react";import { api } from "../convex/_generated/api";//...export default function App() { // Replace the "TODO: Add mutation hook here." with: const sendMessage = useMutation(api.chat.sendMessage); //... return (
{/* ... */}
{ e.preventDefault(); // Replace "alert("Mutation not implemented yet");" with: await sendMessage({ user: NAME, body: newMessageText }); setNewMessageText(""); }} > {/* ... */}
);} There are two steps to call a mutation in your frontend: 1. `const sendMessage = useMutation(api.chat.sendMessage);` gives your frontend app a handle to the mutation function 2. `await sendMessage({ user: NAME, body: newMessageText });` calls the mutation with the proper parameters. This is a good time to **open up the Convex dashboard**. Open a new browser window and go to [https://dashboard.convex.dev](https://dashboard.convex.dev/) and find new `convex-tutorial` project. **Go to the "Data" screen**. So far, there is no data in your database. **Keep your chat app and dashboard windows open side by side**. Now try to send some messages from your chat app. Mutations hooked up to the Convex backend and database. You'll notice new chat messages showing up live in the `messages` table. Convex automatically created a `messages` table when you sent the first message. In Convex, [schemas](https://docs.convex.dev/database/schemas) are optional. Eventually, you'll want to enforce the structure of your tables, but for the purposes of the tutorial we'll skip this. In the dashboard you can also go to the [logs screen](https://dashboard.convex.dev/deployment/logs) and see every call to the mutation as you ran with the log line we added earlier. The logs screen is a critical part of debugging your backend in development. You've successfully created a `mutation` function, which is also a database transaction, and connected it to your UI. Now, let's make sure your app can update live the same way the dashboard is updating live. Your first `query`[​](https://docs.convex.dev/tutorial/#your-first-query "Direct link to your-first-query") ------------------------------------------------------------------------------------------------------------ **Update your `convex/chat.ts` file like this:** // Update your server import like this:import { query, mutation } from "./_generated/server";// ...// Add the following function to the file:export const getMessages = query({ args: {}, handler: async (ctx) => { // Get most recent messages first const messages = await ctx.db.query("messages").order("desc").take(50); // Reverse the list so that it's in a chronological order. return messages.reverse(); },}); Let's break this down: 1. You've added a new backend `query` function called `getMessages` and exposed it as a public api. 2. Since this is a query function, the `ctx.db` in this function only lets you read data. 3. In the first line of the `handler` you are querying the most recent 50 messages from newest to oldest. 4. In the second line you're reversing the list using plain old TypeScript. **Now update `src/App.tsx` to read from your query:** // Update your convex/react import like this:import { useQuery, useMutation } from "convex/react";//...export default function App() { // Replace the `const messages = ...` line with the following const messages = useQuery(api.chat.getMessages); //...} That one `useQuery` line is doing a lot of work automatically for you. It's telling the Convex client library to subscribe to your `getMessages` function. Anytime there are new messages to show the query function is automatically rerun. The result is put in `const messages` variable and React rerenders your UI component to show the latest messages. That's it. Now go back to your app and try sending messages. Your app should be showing live updates as new messages arrive: Queries hooked up and live updating to the app. Don't believe it? Try opening two chat windows side by side and send some messages: Live syncing chat app. What you built[​](https://docs.convex.dev/tutorial/#what-you-built "Direct link to What you built") ---------------------------------------------------------------------------------------------------- With just a few lines of code you've built a live updating chat app. 1. You created a `mutation` TypeScript function that, in a transaction, adds new chat messages to your database. 2. You created a `query` TypeScript function updates your app with the latest data. 3. You used the client library that keeps your frontend in live sync with the backend. You've learned the fundamentals of Convex and the sync engine that powers everything. Next up[​](https://docs.convex.dev/tutorial/#next-up "Direct link to Next up") ------------------------------------------------------------------------------- In this tutorial we just touched on the very basics. It's ok to just stop here and go explore the rest of the docs, including [efficient queries via indexes](https://docs.convex.dev/database/reading-data/indexes/) and traversing [relationships through joins](https://docs.convex.dev/database/reading-data/#join) . If you're deeply curious about how Convex works, you can read this [excellent deep dive](https://stack.convex.dev/how-convex-works) . But if you want to see how to call external services and build sophisticated backend workflows, jump into the [next section →](https://docs.convex.dev/tutorial/actions) . [Calling external services\ -------------------------\ \ Extend your chat app by calling external APIs using Convex actions and the scheduler to integrate Wikipedia summaries into your application.](https://docs.convex.dev/tutorial/actions) --- # Python | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/python#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! See the [Python Quickstart](https://docs.convex.dev/quickstart/python) and the [convex PyPI package docs](https://pypi.org/project/convex/) . The Python client is open source and available on [GitHub](https://github.com/get-convex/convex-py) . --- # Chef | Convex Developer Hub [Skip to main content](https://docs.convex.dev/chef#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page 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](https://docs.convex.dev/assets/images/chef_preview-dfe305b7d7ebb5910c22cf2c22a6842d.png) Deploying to production[​](https://docs.convex.dev/chef#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](https://docs.convex.dev/ai) , helping you keep coding with confidence. ### Download the code[​](https://docs.convex.dev/chef#download-the-code "Direct link to Download the code") ![Chef Screenshot]() 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[​](https://docs.convex.dev/chef#install-dependencies "Direct link to Install dependencies") Run the following command to install all dependencies for your project npm i ### Run your app[​](https://docs.convex.dev/chef#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[​](https://docs.convex.dev/chef#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[​](https://docs.convex.dev/chef#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 initgit add --allgit 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[​](https://docs.convex.dev/chef#set-up-production-frontend-hosting "Direct link to Set up production frontend hosting") Follow one of the Convex [hosting guides](https://docs.convex.dev/production/hosting/) to set up frontend hosting and continuous deployment of your frontend and backend code. ### Initialize Convex Auth for Prod[​](https://docs.convex.dev/chef#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[​](https://docs.convex.dev/chef#integrations "Direct link to Integrations") ----------------------------------------------------------------------------------------- ### OpenAI[​](https://docs.convex.dev/chef#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[​](https://docs.convex.dev/chef#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[​](https://docs.convex.dev/chef#faqs "Direct link to FAQs") ----------------------------------------------------------------- ### What browsers does Chef support?[​](https://docs.convex.dev/chef#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?[​](https://docs.convex.dev/chef#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?[​](https://docs.convex.dev/chef#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?[​](https://docs.convex.dev/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](https://docs.convex.dev/ai) in your Agentic IDE like Cursor. ### Are there any best practices for Chef?[​](https://docs.convex.dev/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?[​](https://docs.convex.dev/chef#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[​](https://docs.convex.dev/chef#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. * [Deploying to production](https://docs.convex.dev/chef#deploying-to-production) * [Download the code](https://docs.convex.dev/chef#download-the-code) * [Install dependencies](https://docs.convex.dev/chef#install-dependencies) * [Run your app](https://docs.convex.dev/chef#run-your-app) * [Set up the frontend build script](https://docs.convex.dev/chef#set-up-the-frontend-build-script) * [Recommended: Setup Git](https://docs.convex.dev/chef#recommended-setup-git) * [Set up production frontend hosting](https://docs.convex.dev/chef#set-up-production-frontend-hosting) * [Initialize Convex Auth for Prod](https://docs.convex.dev/chef#initialize-convex-auth-for-prod) * [Integrations](https://docs.convex.dev/chef#integrations) * [OpenAI](https://docs.convex.dev/chef#openai) * [Resend](https://docs.convex.dev/chef#resend) * [FAQs](https://docs.convex.dev/chef#faqs) * [What browsers does Chef support?](https://docs.convex.dev/chef#what-browsers-does-chef-support) * [How does the pricing for Chef work?](https://docs.convex.dev/chef#how-does-the-pricing-for-chef-work) * [What’s the difference between Chef and Convex?](https://docs.convex.dev/chef#whats-the-difference-between-chef-and-convex) * [Can I import my existing app to Chef?](https://docs.convex.dev/chef#can-i-import-my-existing-app-to-chef) * [Are there any best practices for Chef?](https://docs.convex.dev/chef#are-there-any-best-practices-for-chef) * [What Convex Components can Chef use?](https://docs.convex.dev/chef#what-convex-components-can-chef-use) * [Limitations](https://docs.convex.dev/chef#limitations) --- # Self Hosting | Convex Developer Hub [Skip to main content](https://docs.convex.dev/self-hosting#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page If you're excited about self-hosting, you can run the Convex backend on your own servers. Self-hosted Convex runs the [open-source backend](https://github.com/get-convex/convex-backend) , and contains the same fully up-to-date code the cloud service uses. To get started with self hosting, follow the self-hosting guide: [Self-hosting guide\ ------------------](https://github.com/get-convex/convex-backend/blob/main/self-hosted/README.md) Join the `#self-hosted` channel in the [Discord community](https://convex.dev/community) for self-hosting support. Self hosting is not for everyone. If you're looking for a more hands-off solution, we recommend using the [Convex-hosted product](https://convex.dev/pricing) . Open Source Convex Backend[​](https://docs.convex.dev/self-hosting#open-source-convex-backend "Direct link to Open Source Convex Backend") ------------------------------------------------------------------------------------------------------------------------------------------- The majority of the backend is written in Rust, with a healthy dose of TypeScript supporting the server-side function environment. You can learn more about open-sourcing at Convex in our [announcement](https://news.convex.dev/convex-goes-open-source/) and the [software engineering daily podcast](https://softwareengineeringdaily.com/2024/03/20/going-open-source-at-convex-with-james-cowling/) . Convex uses an [FSL Apache 2.0 License](https://fsl.software/) which is a [fair source](https://fair.io/) license. You can do almost anything Apache-2.0 allows, except create another product designed to compete with the hosted Convex Cloud. This ensures the sustainability of Convex's development while giving the community maximum freedom. All code is automatically converted to full Apache-2.0 two years after its creation. For legal text, see [FSL Apache 2.0 License](https://fsl.software/) . Other Convex Open Source Projects[​](https://docs.convex.dev/self-hosting#other-convex-open-source-projects "Direct link to Other Convex Open Source Projects") ---------------------------------------------------------------------------------------------------------------------------------------------------------------- The Convex backend, client libraries, dashboard, and CLI are all open-source. You can explore everything on the [Convex GitHub page](https://github.com/get-convex) . ### Convex Clients[​](https://docs.convex.dev/self-hosting#convex-clients "Direct link to Convex Clients") All Convex Clients are open-source. * [Convex JavaScript/TypeScript clients & CLI](https://github.com/get-convex/convex-js) * [Convex Python Client](https://github.com/get-convex/convex-py) * [Convex Rust Client](https://github.com/get-convex/convex-rs) ### Much Much More[​](https://docs.convex.dev/self-hosting#much-much-more "Direct link to Much Much More") Convex also open-sources many other helpful projects including [helpers](https://github.com/get-convex/convex-helpers) , [templates](https://github.com/orgs/get-convex/repositories?type=all&q=template) , [demos](https://github.com/get-convex/convex-demos) , a [testing harness](https://github.com/get-convex/convex-test) and much more. See the complete list of all our public repositories [at GitHub](https://github.com/orgs/get-convex/repositories?type=all) . Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) * [Open Source Convex Backend](https://docs.convex.dev/self-hosting#open-source-convex-backend) * [Other Convex Open Source Projects](https://docs.convex.dev/self-hosting#other-convex-open-source-projects) * [Convex Clients](https://docs.convex.dev/self-hosting#convex-clients) * [Much Much More](https://docs.convex.dev/self-hosting#much-much-more) --- # Scheduling | Convex Developer Hub [Skip to main content](https://docs.convex.dev/scheduling#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex lets you easily schedule a function to run once or repeatedly in the future. This allows you to build durable workflows like sending a welcome email a day after someone joins or regularly reconciling your accounts with Stripe. Convex provides two different features for scheduling: * [Scheduled Functions](https://docs.convex.dev/scheduling/scheduled-functions) can be scheduled durably by any other function to run at a later point in time. You can schedule functions minutes, days, and even months in the future. * [Cron Jobs](https://docs.convex.dev/scheduling/cron-jobs) schedule functions to run on a recurring basis, such as daily. Durable function components[​](https://docs.convex.dev/scheduling#durable-function-components "Direct link to Durable function components") -------------------------------------------------------------------------------------------------------------------------------------------- Built-in scheduled functions and crons work well for simpler apps and workflows. If you're operating at high scale or need more specific guarantees, use the following higher-level [components](https://docs.convex.dev/components) for durable functions. [Convex Component\ \ ### Workpool\ \ Workpool give critical tasks priority by organizing async operations into separate, customizable queues.](https://www.convex.dev/components/workpool) [Convex Component\ \ ### Workflow\ \ Simplify programming long running code flows. Workflows execute durably with configurable retries and delays.](https://www.convex.dev/components/workflow) [Convex Component\ \ ### Crons\ \ Use cronspec to run functions on a repeated schedule at runtime.](https://www.convex.dev/components/crons) Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # Rust | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/rust#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! See the [Rust Quickstart](https://docs.convex.dev/quickstart/rust) and [`convex` on docs.rs docs](https://docs.rs/convex/latest/convex/) . The Rust client is open source and available on [GitHub](https://github.com/get-convex/convex-rs) . --- # OpenAPI & Other Languages | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/open-api#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page While Convex doesn't have first-party clients for languages such as Go, Java, or C++, you can generate [OpenAPI](https://swagger.io/specification/) specifications from your Convex deployment to create type-safe clients for languages that aren't currently supported. Under the hood, this uses our [HTTP API](https://docs.convex.dev/http-api) . This means that your queries will not be reactive/real-time. OAS generation is in beta OAS generation is currently a [beta feature](https://docs.convex.dev/production/state/#beta-features) . If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community) ! Setup[​](https://docs.convex.dev/client/open-api#setup "Direct link to Setup") ------------------------------------------------------------------------------- 1. Install the Convex Helpers npm package Install the `convex-helpers` package, which contains a CLI command to generate an Open API specification. npm install convex-helpers 2. Generate an OpenAPI specification Running this command will call into your configured Convex deployment and generate an `convex-spec.yaml` file based on it. You can see additional flags by passing `--help` to the command. npx convex-helpers open-api-spec 3. Generate a type-safe client You can use a separate tools to generate a client from the `convex-spec.yaml` file. Some popular options are [OpenAPI Tools](https://github.com/OpenAPITools/openapi-generator) and [Swagger](https://swagger.io/tools/swagger-codegen/) . # convex-spec.yamlopenapi: 3.0.3info: title: Convex App - OpenAPI 3.0 version: 0.0.0 servers: - url: "{hostUrl}" description: Convex App API ... Example[​](https://docs.convex.dev/client/open-api#example "Direct link to Example") ------------------------------------------------------------------------------------- Below are code snippets of what this workflow looks like in action. npm i openapi-generator-clinpx openapi-generator-cli generate -i convex-spec.yaml -g go -o convex_client These snippets include two different files: * `convex/load.ts` - contains Convex function definitions * `convex.go` - contains `Go` code that uses a generated, type-safe `HTTP` client. convex/load.ts TS import { v } from "convex/values";import { query } from "./_generated/server";import { LinkTable } from "./schema";export const loadOne = query({ args: { normalizedId: v.string(), token: v.string() }, returns: v.union( v.object({ ...LinkTable.validator.fields, _creationTime: v.number(), _id: v.id("links"), }), v.null(), ), handler: async (ctx, { normalizedId, token }) => { if (token === "" || token !== process.env.CONVEX_AUTH_TOKEN) { throw new Error("Invalid authorization token"); } return await ctx.db .query("links") .withIndex("by_normalizedId", (q) => q.eq("normalizedId", normalizedId)) .first(); },}); convex.go type Link struct { Short string // the "foo" part of http://go/foo Long string // the target URL or text/template pattern to run Created time.Time LastEdit time.Time // when the link was last edited Owner string // user@domain}func (c *ConvexDB) Load(short string) (*Link, error) { request := *convex.NewRequestLoadLoadOne(*convex.NewRequestLoadLoadOneArgs(short, c.token)) resp, httpRes, err := c.client.QueryAPI.ApiRunLoadLoadOnePost(context.Background()).RequestLoadLoadOne(request).Execute() validationErr := validateResponse(httpRes.StatusCode, err, resp.Status) if validationErr != nil { return nil, validationErr } linkDoc := resp.Value.Get() if linkDoc == nil { err := fs.ErrNotExist return nil, err } link := Link{ Short: linkDoc.Short, Long: linkDoc.Long, Created: time.Unix(int64(linkDoc.Created), 0), LastEdit: time.Unix(int64(linkDoc.LastEdit), 0), Owner: linkDoc.Owner, } return &link, nil Limits[​](https://docs.convex.dev/client/open-api#limits "Direct link to Limits") ---------------------------------------------------------------------------------- * Argument and return value validators are not required, but they will enrich the types of your OpenAPI spec. Where validators aren't defined, we default to `v.any()` as the validator. * You cannot call internal functions from outside of your Convex deployment. * We currently do not support `bigints` or `bytes`. * [Setup](https://docs.convex.dev/client/open-api#setup) * [Example](https://docs.convex.dev/client/open-api#example) * [Limits](https://docs.convex.dev/client/open-api#limits) --- # Convex React Native | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/react-native#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! To use Convex in [React Native](https://reactnative.dev/) use the [Convex React client library](https://docs.convex.dev/client/react) . Follow the [React Native Quickstart](https://docs.convex.dev/quickstart/react-native) for the different configuration needed specifically for React Native. You can also clone a working [Convex React Native demo](https://github.com/get-convex/convex-demos/tree/main/react-native) . --- # ESLint rules | Convex Developer Hub [Skip to main content](https://docs.convex.dev/eslint#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page The Convex ESLint plugin provides linter rules that enforce best practices for Convex functions. Let us know if there's a rule you would find helpful! Setup[​](https://docs.convex.dev/eslint#setup "Direct link to Setup") ---------------------------------------------------------------------- For ESLint 9 (flat config, using `eslint.config.js`), install the rules with: npm i @convex-dev/eslint-plugin --save-dev and add this to your `eslint.config.js` file: import { defineConfig } from "eslint/config";import convexPlugin from "@convex-dev/eslint-plugin";export default defineConfig([ // Other configurations ...convexPlugin.configs.recommended,]); If you’re using the deprecated `.eslintrc.js` format Install these two libraries: npm i @typescript-eslint/eslint-plugin @convex-dev/eslint-plugin --save-dev In `.eslintrc.js`, add: module.exports = extends: [ // Other configurations "plugin:@typescript-eslint/recommended", "plugin:@convex-dev/recommended", ], ignorePatterns: ["node_modules/", "dist/", "build/"],}; If your Convex functions are in a directory other than `convex` By default, the Convex ESLint plugin will only apply rules in the `convex` directory. If you’re [customizing the Convex directory location](https://docs.convex.dev/production/project-configuration#changing-the-convex-folder-name-or-location) , here’s how to adapt your ESLint configuration: // eslint.config.jsimport { defineConfig } from "eslint/config";import convexPlugin from "@convex-dev/eslint-plugin";const recommendedConfig = convexPlugin.configs.recommended[0];const recommendedRules = recommendedConfig.rules;export default defineConfig([ // Other configurations go here... // Custom configuration with modified directory pattern { files: ["**/src/convex/**/*.ts"], plugins: { "@convex-dev": convexPlugin, }, rules: recommendedRules, },]); If you’re using the `next lint` command from Next.js For `next lint` to run ESLint on your `convex` directory you need to add that directory to the default set of directories. Add this section to your `next.config.ts`: const nextConfig: NextConfig = { /* other options here */ eslint: { dirs: ["pages", "app", "components", "lib", "src", "convex"], },}; Rules[​](https://docs.convex.dev/eslint#rules "Direct link to Rules") ---------------------------------------------------------------------- | Rule | Recommended | Auto-fixable | | --- | --- | --- | | [`@convex-dev/no-old-registered-function-syntax`](https://docs.convex.dev/eslint#no-old-registered-function-syntax)

Prefer object syntax for registered functions | ✅ | 🔧 | | [`@convex-dev/require-argument-validators`](https://docs.convex.dev/eslint#require-argument-validators)

Require argument validators for Convex functions | ✅ | 🔧 | | [`@convex-dev/explicit-table-ids`](https://docs.convex.dev/eslint#explicit-table-ids)

Require explicit table names in database operations | ✅ | 🔧 | | [`@convex-dev/import-wrong-runtime`](https://docs.convex.dev/eslint#import-wrong-runtime)

Prevent Convex runtime files from importing from Node runtime files | | | | [`@convex-dev/no-collect-in-query`](https://docs.convex.dev/eslint#no-collect-in-query)

Prefer `.take()` / `.paginate()` over `.collect()` in queries | | | ### no-old-registered-function-syntax[​](https://docs.convex.dev/eslint#no-old-registered-function-syntax "Direct link to no-old-registered-function-syntax") Prefer object syntax for registered functions. Convex queries, mutations, and actions can be defined with a single function or with an object containing a handler property. Using the objects makes it possible to add argument and return value validators, so is always preferable. // ✅ Allowed by this rule:export const list = query({ handler: async (ctx) => { const data = await ctx.db.query("messages").collect(); ... },});// ❌ Not allowed by this rule:export const list = query(async (ctx) => { const data = await ctx.db.query("messages").collect(); ...}); ### require-argument-validators[​](https://docs.convex.dev/eslint#require-argument-validators "Direct link to require-argument-validators") Require argument validators for Convex functions. Convex queries, mutations, and actions can validate their arguments before beginning to run the handler function. Besides being a concise way to validate, the types of arguments, using argument validators enables generating more descriptive function specs and therefore OpenAPI bindings. // ✅ Allowed by this rule:export const list = query({ args: {}, handler: async (ctx) => { ... },});// ✅ Allowed by this rule:export const list = query({ args: { channel: v.id('channel') }, handler: async (ctx, { channel }) => { ... },});// ❌ Not allowed with option { ignoreUnusedArguments: false } (default)// ✅ Allowed with option { ignoreUnusedArguments: true }export const list = query({ handler: async (ctx) => { ... },});// ❌ Not allowed by this rule:export const list = query({ handler: async (ctx, { channel }: { channel: Id<"channel"> }) => { ... },}); This rule can be customized to tolerate functions that don’t define an argument validator but don’t use their arguments. Here’s how you can set up the rule to work this way: // eslint.config.jsexport default defineConfig([ // Your other rules… { files: ["**/convex/**/*.ts"], rules: { "@convex-dev/require-args-validator": [ "error", { ignoreUnusedArguments: true, }, ], }, },]); ### explicit-table-ids[​](https://docs.convex.dev/eslint#explicit-table-ids "Direct link to explicit-table-ids") Require explicit table names in database operations. Starting from version 1.31.0 of the `convex` npm package, we recommend including the table name as the first argument to database operations (`db.get`, `db.replace`, `db.patch`, `db.delete`). This approach is more secure because it prevents vulnerabilities when an ID from one table is incorrectly typed as belonging to another table. The implicit syntax (where table names are inferred from the ID) will be deprecated in the future to give developers more control over ID generation. For both these reasons, we recommend developers to migrate to the new format. This rule helps migrate code from the old implicit format to the new explicit format. It uses TypeScript type information to automatically infer the table name from the `Id<"tableName">` type and provides automatic fixes. const messageId: Id<"messages"> = "123" as Id<"messages">;// ✅ Allowed by this rule:const message = await ctx.db.get("messages", messageId);await ctx.db.patch("messages", messageId, { text: "updated" });await ctx.db.replace("messages", messageId, { text: "replaced", author: "Alice",});await ctx.db.delete("messages", messageId);// ❌ Not allowed by this rule:const message = await ctx.db.get(messageId);await ctx.db.patch(messageId, { text: "updated" });await ctx.db.replace(messageId, { text: "replaced", author: "Alice" });await ctx.db.delete(messageId); typescript-eslint required In order for this rule to work, [typescript-eslint](https://typescript-eslint.io/) must be set up in your ESLint configuration. If typescript-eslint is installed and the rule doesn’t seem to work, please make sure that [type-aware linting](https://typescript-eslint.io/troubleshooting/typed-linting/) is enabled. Note that if you’re not using ESLint, you can alternatively use the `@convex-dev/codemod` CLI tool to automatically migrate to the new format: npx @convex-dev/codemod@latest explicit-ids [Learn more on news.convex.dev →](https://news.convex.dev/db-table-name/) ### import-wrong-runtime[​](https://docs.convex.dev/eslint#import-wrong-runtime "Direct link to import-wrong-runtime") Prevent Convex runtime files from importing from Node runtime files (files with a `"use node"` directive). This rule is experimental. Please let us know if you find it helpful! // In a file that doesn’t use `"use node"`:// ✅ Allowed by this rule:import { someFunction } from "./someOtherFile"; // where someOtherFile doesn't use `"use node"`// ❌ Not allowed by this rule:import { someFunction } from "./someNodeFile"; // where someNodeFile uses `"use node"` ### no-collect-in-query[​](https://docs.convex.dev/eslint#no-collect-in-query "Direct link to no-collect-in-query") Prefer `.take()` / `.paginate()` over `.collect()` in queries. typescript-eslint required In order for this rule to work, [typescript-eslint](https://typescript-eslint.io/) must be set up in your ESLint configuration. If typescript-eslint is installed and the rule doesn’t seem to work, please make sure that [type-aware linting](https://typescript-eslint.io/troubleshooting/typed-linting/) is enabled. You should avoid using `.collect()` in queries that can return a large number of documents at once. In these queries, using `.collect()` can lead to excessive bandwidth usage and mutation conflicts, and the query can also fail if it reaches the [Convex query limits](https://docs.convex.dev/production/state/limits#transactions) . Prefer `.take(N)` if you only need the first _N_ results, or `.paginate()` if you want to page through results. If you know the query will always return a small number of results, you can disable this rule for that line with: // eslint-disable-next-line @convex-dev/no-collect-in-queryconst results = await ctx.db.query("roles").collect(); * [Setup](https://docs.convex.dev/eslint#setup) * [Rules](https://docs.convex.dev/eslint#rules) * [no-old-registered-function-syntax](https://docs.convex.dev/eslint#no-old-registered-function-syntax) * [require-argument-validators](https://docs.convex.dev/eslint#require-argument-validators) * [explicit-table-ids](https://docs.convex.dev/eslint#explicit-table-ids) * [import-wrong-runtime](https://docs.convex.dev/eslint#import-wrong-runtime) * [no-collect-in-query](https://docs.convex.dev/eslint#no-collect-in-query) --- # Svelte | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/svelte#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex is a great fit for reactive UI frameworks like Svelte. The [convex-svelte npm package](https://www.npmjs.com/package/convex-svelte) enhances the [ConvexClient](https://docs.convex.dev/api/classes/browser.ConvexClient) with declarative subscriptions in Svelte 5. See the [Svelte Quickstart](https://docs.convex.dev/quickstart/svelte) to get started. The Svelte client is open source and available on [GitHub](https://github.com/get-convex/convex-svelte) . --- # Vue | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/vue#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! [Vue](https://vuejs.org/) is an "approachable, performant and versatile framework for building web user interfaces." The community-maintained [`convex-vue`](https://www.npmjs.com/package/convex-vue) npm package is a Vue.js integration library for Convex. It also powers [convex-nuxt](https://docs.convex.dev/client/vue/nuxt) . See the [Vue Quickstart](https://docs.convex.dev/quickstart/vue) to get started or the [convex-vue GitHub page](https://github.com/chris-visser/convex-vue) for more documentation. info The [`convex-vue` library](https://github.com/chris-visser/convex-vue) is community-maintained. Thank you to the maintainer [Chris Visser](https://github.com/chris-visser) for his work on this project! You're welcome to ask questions about the library on the [Convex Discord](https://convex.dev/community) but opening a [convex-vue GitHub](https://github.com/chris-visser/convex-vue) issue is a better way to request a new feature or report a bug. --- # AI & Search | Convex Developer Hub [Skip to main content](https://docs.convex.dev/search#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Whether building RAG enabled chatbots or quick search in your applications, Convex provides easy apis to create powerful AI and search enabled products. [Vector Search](https://docs.convex.dev/search/vector-search) enables searching for documents based on their semantic meaning. It uses vector embeddings to calculate similarity and retrieve documents that are similar to a given query. Vector search is a key part of common AI techniques like RAG. [Full Text Search](https://docs.convex.dev/search/text-search) enables keyword and phrase search within your documents. It supports prefix matching to enable typeahead search. Convex full text search is also reactive and always up to date like all Convex queries, making it easy to build reliable quick search boxes. [Convex Actions](https://docs.convex.dev/functions/actions) easily enable you to call AI apis, save data to your database, and drive your user interface. See examples of how you can use this to [build sophisticated AI applications](https://stack.convex.dev/tag/AI) . Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # Convex Overview | Convex Developer Hub [Skip to main content](https://docs.convex.dev/understanding/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex is the open source, reactive database where queries are TypeScript code running right in the database. Just like React components react to state changes, Convex queries react to database changes. Convex provides a database, a place to write your server functions, and client libraries. It makes it easy to build and scale dynamic live-updating apps. The following diagram shows the standard three-tier app architecture that Convex enables. We'll start at the bottom and work our way up to the top of this diagram. ![Convex in your app](https://docs.convex.dev/assets/images/basic-diagram-8ad312f058c3cf7e15c3396e46eedb48.png) Database[​](https://docs.convex.dev/understanding/#database "Direct link to Database") --------------------------------------------------------------------------------------- The [database](https://docs.convex.dev/database) is at the core of Convex. The Convex database is automatically provisioned when you create your project. There is no connection setup or cluster management. info In Convex, your database queries are just [TypeScript code](https://docs.convex.dev/database/reading-data/) written in your [server functions](https://docs.convex.dev/functions) . There is no SQL to write. There are no ORMs needed. The Convex database is reactive. Whenever any data on which a query depends changes, the query is rerun, and client subscriptions are updated. Convex is a "document-relational" database. "Document" means you put JSON-like nested objects into your database. "Relational" means you have tables with relations, like `tasks` assigned to a `user` using IDs to reference documents in other tables. The Convex cloud offering runs on top of PlanetScale using MySQL as its persistence layer. The Open Source version uses SQLite, Postgres and MySQL. The database is ACID-compliant and uses [serializable isolation and optimistic concurrency control](https://docs.convex.dev/database/advanced/occ) . All that to say, Convex provides the strictest possible transactional guarantees, and you never see inconsistent data. Server functions[​](https://docs.convex.dev/understanding/#server-functions "Direct link to Server functions") --------------------------------------------------------------------------------------------------------------- When you create a new Convex project, you automatically get a `convex/` folder where you write your [server functions](https://docs.convex.dev/functions) . This is where all your backend application logic and database query code live. Example TypeScript server functions that read (query) and write (mutation) to the database. convex/tasks.ts // A Convex query functionexport const getAllOpenTasks = query({ args: {}, handler: async (ctx, args) => { // Query the database to get all items that are not completed const tasks = await ctx.db .query("tasks") .withIndex("by_completed", (q) => q.eq("completed", false)) .collect(); return tasks; },});// A Convex mutation functionexport const setTaskCompleted = mutation({ args: { taskId: v.id("tasks"), completed: v.boolean() }, handler: async (ctx, { taskId, completed }) => { // Update the database using TypeScript await ctx.db.patch("tasks", taskId, { completed }); },}); You read and write to your database through query or mutation functions. [Query functions](https://docs.convex.dev/functions/query-functions) are pure functions that can only read from the database. [Mutation functions](https://docs.convex.dev/functions/mutation-functions) are transactions that can read or write from the database. These two database functions are [not allowed to take any non-deterministic](https://docs.convex.dev/functions/runtimes#restrictions-on-queries-and-mutations) actions like network requests to ensure transactional guarantees. info The entire Convex mutation function is a transaction. There are no `begin` or `end` transaction statements to write. Convex automatically retries the function on conflicts, and you don't have to manage anything. Convex also provides standard general-purpose serverless functions called actions. [Action functions](https://docs.convex.dev/functions/actions) can make network requests. They have to call query or mutation functions to read and write to the database. You use actions to call LLMs or send emails. You can also durably schedule Convex functions via the [scheduler](https://docs.convex.dev/scheduling/scheduled-functions) or [cron jobs](https://docs.convex.dev/scheduling/cron-jobs) . Scheduling lets you build workflows like emailing a new user a day later if they haven't performed an onboarding task. You call your Convex functions via [client libraries](https://docs.convex.dev/client/react) or directly via [HTTP](https://docs.convex.dev/http-api/#functions-api) . Client libraries[​](https://docs.convex.dev/understanding/#client-libraries "Direct link to Client libraries") --------------------------------------------------------------------------------------------------------------- Convex client libraries keep your frontend synced with the results of your server functions. // In your React componentimport { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export function TaskList() { const data = useQuery(api.tasks.getAllOpenTasks); return data ?? "Loading...";} Like the `useState` hook that updates your React component when local state changes, the Convex `useQuery` hook automatically updates your component whenever the result of your query changes. There's no manual subscription management or state synchronization needed. When calling query functions, the client library subscribes to the results of the function. Convex tracks the dependencies of your query functions, including what data was read from the database. Whenever relevant data in the database changes, the Convex automatically reruns the query and sends the result to the client. The client library also queues up mutations in memory to send to the server. As mutations execute and cause query results to update, the client library keeps your app state consistent. It updates all subscriptions to the same logical moment in time in the database. Convex provides client libraries for nearly all popular web and native app frameworks. Client libraries connect to your Convex deployment via WebSockets. You can then call your public Convex functions [through the library](https://docs.convex.dev/client/react#fetching-data) . You can also use Convex with [HTTP directly](https://docs.convex.dev/http-api/#functions-api) , you just won't get the automatic subscriptions. Putting it all together[​](https://docs.convex.dev/understanding/#putting-it-all-together "Direct link to Putting it all together") ------------------------------------------------------------------------------------------------------------------------------------ Let's return to the `getAllOpenTasks` Convex query function from earlier that gets all tasks that are not marked as `completed`: convex/tasks.ts export const getAllOpenTasks = query({ args: {}, handler: async (ctx, args) => { // Query the database to get all items that are not completed const tasks = await ctx.db .query("tasks") .withIndex("by_completed", (q) => q.eq("completed", false)) .collect(); return tasks; },}); Let's follow along what happens when you subscribe to this query: ![Convex data flow](https://docs.convex.dev/assets/images/convex-query-subscription-945e7990515e438ab4385f9b4803bbd4.png) The web app uses the `useQuery` hook to subscribe to this query, and the following happens to get an initial value: * The Convex client sends a message to the Convex server to subscribe to the query * The Convex server runs the function, which reads data from the database * The Convex server sends a message to the client with the function's result In this case the initial result looks like this (1): [ { _id: "e4g", title: "Grocery shopping", complete: false }, { _id: "u9v", title: "Plant new flowers", complete: false },]; Then you use a mutation to mark an item as completed (2). Convex then reruns the query (3) to get an updated result. And pushes the result to the web app via the WebSocket connection (4): [ { _id: "e4g", title: "Grocery shopping", complete: false },]; Beyond reactivity[​](https://docs.convex.dev/understanding/#beyond-reactivity "Direct link to Beyond reactivity") ------------------------------------------------------------------------------------------------------------------ Beyond reactivity, Convex's architecture is crucial for a deeper reason. Convex does not let your app have inconsistent state at any layer of the stack. To illustrate this, let's imagine you're building a shopping cart for an e-commerce store. ![Convex in your app](https://docs.convex.dev/assets/images/convex-swaghaus-dcc9919685db6a7f34378afc500f68cd.png) On the product listing page, you have two numbers, one showing the number of items remaining in stock and another showing the number of items in your shopping cart. Each number is a result of a different query function. Every time you press the "Add to Cart" button, a mutation is called to remove one item from the stock and add it to the shopping cart. The mutation to change the cart runs in a transaction, so your database is always in a consistent state. The reactive database knows that the queries showing the number of items in stock and the number of items in the shopping cart both need to be updated. The queries are invalidated and rerun. The results are pushed to the web app via the WebSocket connection. The client library makes sure that both queries update at the same time in the web app since they reflect a singular moment in time in your database. You never have a moment where those numbers don't add up. Your app always shows consistent data. You can see this example in action in the [Swaghaus sample app](https://swaghaus.biz/) . For human and AI generated code[​](https://docs.convex.dev/understanding/#for-human-and-ai-generated-code "Direct link to For human and AI generated code") ------------------------------------------------------------------------------------------------------------------------------------------------------------ Convex is designed around a small set of composable abstractions with strong guarantees that result in code that is not only faster to write, it’s 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. Learn more[​](https://docs.convex.dev/understanding/#learn-more "Direct link to Learn more") --------------------------------------------------------------------------------------------- If you are intrigued about the details of how Convex pulls this all off, you can read Convex co-founder Sujay's excellent [How Convex Works](https://stack.convex.dev/how-convex-works) blog post. Now that you have a good sense of how Convex fits in your app. Let's walk through the overall workflow of setting up and launching a Convex app. * [Database](https://docs.convex.dev/understanding/#database) * [Server functions](https://docs.convex.dev/understanding/#server-functions) * [Client libraries](https://docs.convex.dev/understanding/#client-libraries) * [Putting it all together](https://docs.convex.dev/understanding/#putting-it-all-together) * [Beyond reactivity](https://docs.convex.dev/understanding/#beyond-reactivity) * [For human and AI generated code](https://docs.convex.dev/understanding/#for-human-and-ai-generated-code) * [Learn more](https://docs.convex.dev/understanding/#learn-more) --- # Components | Convex Developer Hub [Skip to main content](https://docs.convex.dev/components#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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\ ------------------------\ \ Explore the concepts behind and build a mental model for how components work.](https://docs.convex.dev/components/understanding) [Using components\ ----------------\ \ Learn about useful components and how to use them in your application.](https://docs.convex.dev/components/using) [Authoring components\ --------------------\ \ Learn how to write and publish a component.](https://docs.convex.dev/components/authoring) [Components Directory\ --------------------\ \ List of all components.](https://convex.dev/components) --- # Platform APIs | Convex Developer Hub [Skip to main content](https://docs.convex.dev/platform-apis#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page info Convex Platform APIs are in openly available in Beta. Please contact [platforms@convex.dev](mailto:platforms@convex.dev) if your use case requires additional capabilities. This guide is for products that want to orchestrate multiple Convex projects in their accounts or manage projects in their users' accounts. These APIs are most often used by AI app builders, such as [Bloom](https://bloom.diy/) , [A0](https://a0.dev/) , or [Macaly](https://www.macaly.com/) . These guides assume a good understanding of Convex cloud hierarchy (teams, projects, and deployments) as well as the [development workflow](https://docs.convex.dev/understanding/workflow) . Managing your own projects[​](https://docs.convex.dev/platform-apis#managing-your-own-projects "Direct link to Managing your own projects") -------------------------------------------------------------------------------------------------------------------------------------------- This means that you are creating projects, deployments, and pushing code programmatically in the context of the team you own. To manage projects in your own team, you need to get a team-scoped token and ID from your [Team Settings](https://dashboard.convex.dev/team/settings/access-tokens) . caution These tokens are owned by the team member that's logged into the Convex dashboard when you retrieve them. This means that this user owns any dev deployments created by using these tokens. If this user leaves the team, that also deletes all of their dev deployments from the team. We recommend creating a separate service account that's added as a team member. Retrieve the token after logging in as this service account. Managing your users' projects[​](https://docs.convex.dev/platform-apis#managing-your-users-projects "Direct link to Managing your users' projects") ---------------------------------------------------------------------------------------------------------------------------------------------------- This means your users authorize your product to manage their own Convex team or projects. To do this, you need to create an OAuth 2.0 application so that the user can grant your product the necessary permissions. Follow the [OAuth Applications](https://docs.convex.dev/platform-apis/oauth-applications) guide to create an OAuth application and request a relevant token. APIs to manage projects[​](https://docs.convex.dev/platform-apis#apis-to-manage-projects "Direct link to APIs to manage projects") ----------------------------------------------------------------------------------------------------------------------------------- Once you have obtained a token from one of the methods above, you can use it to call the relevant APIs to manage Convex projects and deployments. [Management API Reference](https://docs.convex.dev/management-api) Pushing code to a deployment[​](https://docs.convex.dev/platform-apis#pushing-code-to-a-deployment "Direct link to Pushing code to a deployment") -------------------------------------------------------------------------------------------------------------------------------------------------- Working with your deployment should be scripted primarily with the existing Convex CLI. The Convex CLI manages a lot of the heavy lifting: bundling code, properly handling responses, etc. The examples here assume you are working in a container with shell and file system access from which you can drive the app building process. You likely already have this if you're generating frontend code. Set [`CONVEX_DEPLOY_KEY`](https://docs.convex.dev/cli/deploy-key-types) is the value returned by the [Create deploy key](https://docs.convex.dev/management-api/create-deploy-key) API. ### Pushing code to the dev Convex backend[​](https://docs.convex.dev/platform-apis#pushing-code-to-the-dev-convex-backend "Direct link to Pushing code to the dev Convex backend") CONVEX_DEPLOY_KEY="YOUR_DEPLOY_KEY" npx convex dev --once ### Pushing code to the prod Convex backend[​](https://docs.convex.dev/platform-apis#pushing-code-to-the-prod-convex-backend "Direct link to Pushing code to the prod Convex backend") CONVEX_DEPLOY_KEY="YOUR_DEPLOY_KEY" npx convex deploy To view the full list of commands, refer to the [CLI documentation](https://docs.convex.dev/cli) . * [Managing your own projects](https://docs.convex.dev/platform-apis#managing-your-own-projects) * [Managing your users' projects](https://docs.convex.dev/platform-apis#managing-your-users-projects) * [APIs to manage projects](https://docs.convex.dev/platform-apis#apis-to-manage-projects) * [Pushing code to a deployment](https://docs.convex.dev/platform-apis#pushing-code-to-a-deployment) * [Pushing code to the dev Convex backend](https://docs.convex.dev/platform-apis#pushing-code-to-the-dev-convex-backend) * [Pushing code to the prod Convex backend](https://docs.convex.dev/platform-apis#pushing-code-to-the-prod-convex-backend) --- # Convex with TanStack Query | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/tanstack/tanstack-query/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [TanStack Query](https://tanstack.com/query/latest) is an excellent, popular library for managing requests to a server. The [`@convex-dev/react-query`](https://www.npmjs.com/package/@convex-dev/react-query) library provides [Query Option](https://tanstack.com/query/latest/docs/framework/react/guides/query-options) functions for use with TanStack Query. Not all features of the standard [Convex React client](https://docs.convex.dev/client/react) are available through the TanStack Query APIs but you can use the two alongside each other, dropping into the standard Convex React hooks as necessary. The TanStack Query adapter is in beta The TanStack Query adapter is currently a [beta feature](https://docs.convex.dev/production/state/#beta-features) . If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community) ! This makes subscribing to a Convex query function using the TanStack Query `useQuery` hook look like this: const { data, isPending, error } = useQuery(convexQuery(api.messages.list, {})); Instead of the typical polling pattern for API endpoints used with TanStack Query, the code above receives updates for this `api.messages.list` query from the Convex server reactively. New results for all relevant subscriptions are pushed to the client where they update at the same time so data is never stale and there's no need to manually invalidate queries. Support for other frameworks Currently only [React Query](https://tanstack.com/query/latest/docs/framework/react/overview) is supported via [`@convex-dev/react-query`](https://www.npmjs.com/package/@convex-dev/react-query) . [Let us know](https://convex.dev/community) if you would find support for vue-query, svelte-query, solid-query, or angular-query helpful. Setup[​](https://docs.convex.dev/client/tanstack/tanstack-query/#setup "Direct link to Setup") ----------------------------------------------------------------------------------------------- To get live updates in TanStack Query create a `ConvexQueryClient` and connect it to the TanStack Query [QueryClient](https://tanstack.com/query/latest/docs/reference/QueryClient) . After installing the adapter library with npm i @convex-dev/react-query wire up Convex to TanStack Query like this: src/main.tsx import { ConvexQueryClient } from "@convex-dev/react-query";import { QueryClient, QueryClientProvider } from "@tanstack/react-query";import { ConvexProvider, ConvexReactClient } from "convex/react";import ReactDOM from "react-dom/client";import App from "./App";import "./index.css";const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);const convexQueryClient = new ConvexQueryClient(convex);const queryClient = new QueryClient({ defaultOptions: { queries: { queryKeyHashFn: convexQueryClient.hashFn(), queryFn: convexQueryClient.queryFn(), }, },});convexQueryClient.connect(queryClient);ReactDOM.createRoot(document.getElementById("root")!).render( ,); Note that when your create your React tree you should both: * wrap your app in the TanStack Query [`QueryClientProvider`](https://tanstack.com/query/latest/docs/framework/react/reference/QueryClientProvider) so you can use [TanStack Query hooks](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) and * wrap your app in the [`ConvexProvider`](https://docs.convex.dev/api/modules/react#convexprovider) so you can also use normal [Convex React](https://docs.convex.dev/client/react) hooks Queries[​](https://docs.convex.dev/client/tanstack/tanstack-query/#queries "Direct link to Queries") ----------------------------------------------------------------------------------------------------- A live-updating subscription to a Convex [query](https://docs.convex.dev/functions/query-functions) is as simple as calling TanStack [`useQuery`](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) with `convexQuery`: import { useQuery } from "@tanstack/react-query";import { convexQuery } from "@convex-dev/react-query";import { api } from "../convex/_generated/api";export function App() { const { data, isPending, error } = useQuery( convexQuery(api.functions.myQuery, { id: 123 }), ); return isPending ? "Loading..." : data;} You can spread the object returned by `convexQuery` into an object specifying additional [arguments of `useQuery`](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery) . const { data, isPending, error } = useQuery({ ...convexQuery(api.functions.myQuery, { id: 123 }), initialData: [], // use an empty list if no data is available yet gcTime: 10000, // stay subscribed for 10 seconds after this component unmounts}); Mutations[​](https://docs.convex.dev/client/tanstack/tanstack-query/#mutations "Direct link to Mutations") ----------------------------------------------------------------------------------------------------------- Your app can call Convex [mutations](https://docs.convex.dev/functions/mutation-functions) by using the TanStack [`useMutation`](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation) hook, and setting the `mutationFn` property to the result of calling `useConvexMutation`: import { useMutation } from "@tanstack/react-query";import { useConvexMutation } from "@convex-dev/react-query";import { api } from "../convex/_generated/api";export function App() { const { mutate, isPending } = useMutation({ mutationFn: useConvexMutation(api.functions.doSomething), }); return ;} `useConvexMutation` is just a re-export of the [`useMutation`](https://docs.convex.dev/client/react#editing-data) hook from [Convex React](https://docs.convex.dev/client/react) . Differences from using `fetch` with TanStack Query[​](https://docs.convex.dev/client/tanstack/tanstack-query/#differences-from-using-fetch-with-tanstack-query "Direct link to differences-from-using-fetch-with-tanstack-query") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Convex provides stronger guarantees than other methods of fetching data with React Query, so some options and return value properties are no longer necessary. Subscriptions to Convex queries will remain active after the last component using `useQuery` for a given function unmounts for `gcTime` milliseconds. This value is 5 minutes by default; if this results in unwanted function activity use a smaller value. Data provided by Convex is never stale, so the `isStale` property of the return value of `useQuery` will always be false. `retry`\-related options are ignored, since Convex provides its own retry mechanism over its WebSocket protocol. `refetch`\-related options are similarly ignored since Convex queries are always up to date. * [Setup](https://docs.convex.dev/client/tanstack/tanstack-query/#setup) * [Queries](https://docs.convex.dev/client/tanstack/tanstack-query/#queries) * [Mutations](https://docs.convex.dev/client/tanstack/tanstack-query/#mutations) * [Differences from using `fetch` with TanStack Query](https://docs.convex.dev/client/tanstack/tanstack-query/#differences-from-using-fetch-with-tanstack-query) --- # Errors and Warnings | Convex Developer Hub [Skip to main content](https://docs.convex.dev/error#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page This page explains specific errors thrown by Convex. See [Error Handling](https://docs.convex.dev/functions/error-handling/) to learn about handling errors in general. Write conflict: Optimistic concurrency control[​](https://docs.convex.dev/error#1 "Direct link to Write conflict: Optimistic concurrency control") --------------------------------------------------------------------------------------------------------------------------------------------------- This system error is thrown when a mutation repeatedly fails due to conflicting changes from parallel mutation executions. ### Example A[​](https://docs.convex.dev/error#example-a "Direct link to Example A") A mutation `updateCounter` always updates the same document: export const updateCounter = mutation({ args: {}, handler: async (ctx) => { const doc = await ctx.db.get("counts", process.env.COUNTER_ID); await ctx.db.patch("counts", doc._id, { value: doc.value + 1 }); },}); If this mutation is called many times per second, many of its executions will conflict with each other. Convex internally does several retries to mitigate this concern, but if the mutation is called more rapidly than Convex can execute it, some of the invocations will eventually throw this error: > failure `updateCounter` > > Documents read from or written to the table "counters" changed while this mutation was being run and on every subsequent retry. Another call to this mutation changed the document with ID "123456789101112". The error message will note the table name, which mutation caused the conflict (in this example its another call to the same mutation), and one document ID which was part of the conflicting change. ### Example B[​](https://docs.convex.dev/error#example-b "Direct link to Example B") Mutation `writeCount` depends on the entire `tasks` table: export const writeCount = mutation({ args: { target: v.id("counts"), }, handler: async (ctx, args) => { const tasks = await ctx.db.query("tasks").collect(); await ctx.db.patch("tasks", args.target, { value: tasks }); },});export const addTask = mutation({ args: { text: v.string(), }, handler: async (ctx, args) => { await ctx.db.insert("tasks", { text: args.text }); },}); If the mutation `writeCount` is called at the same time as many calls to `addTask` are made, either of the mutations can fail with this error. This is because any change to the `"tasks"` table will conflict with the `writeCount` mutation: > failure `writeCount` > > Documents read from or written to the table "tasks" changed while this mutation was being run and on every subsequent retry. A call to "addTask" changed the document with ID "123456789101112". ### Remediation[​](https://docs.convex.dev/error#remediation "Direct link to Remediation") To fix this issue: 1. Make sure that your mutations only read the data they need. Consider reducing the amount of data read by using indexed queries with [selective index range expressions](https://docs.convex.dev/database/indexes/) . 2. Make sure you are not calling a mutation an unexpected number of times, perhaps from an action inside a loop. 3. Design your data model such that it doesn't require making many writes to the same document. ### Resources[​](https://docs.convex.dev/error#resources "Direct link to Resources") * Learn more about [optimistic concurrency control](https://docs.convex.dev/database/advanced/occ) . * See this [Stack post](https://stack.convex.dev/waitlist) for an example of designing an app to avoid mutation conflicts. ### Related Components[​](https://docs.convex.dev/error#related-components "Direct link to Related Components") [Convex Component\ \ ### Workpool\ \ Workpool give critical tasks priority by organizing async operations into separate, customizable queues.](https://www.convex.dev/components/workpool) [Convex Component\ \ ### Sharded Counter\ \ High-throughput counter enables denormalized counts without write conflicts by spreading writes over multiple documents.](https://www.convex.dev/components/sharded-counter) [Convex Component\ \ ### Action Cache\ \ Cache frequently run actions. By leveraging the \`force\` parameter to keep the cache populated, you can ensure that the cache is always up to date and avoid data races.](https://www.convex.dev/components/action-cache) Undefined validator[​](https://docs.convex.dev/error#undefined-validator "Direct link to Undefined validator") --------------------------------------------------------------------------------------------------------------- This error occurs when a validator passed to a Convex function definition or schema is `undefined`. This most commonly happens due to circular imports (also known as import cycles) in TypeScript. ### Example[​](https://docs.convex.dev/error#example "Direct link to Example") You have two files that import from each other: convex/validators.ts TS import { v } from "convex/values";import { someUtility } from "./functions";export const myValidator = v.object({ name: v.string(),});// Uses someUtility somewhere... convex/functions.ts TS import { mutation } from "./_generated/server";// Both functions.ts and validators.ts import from each other.import { myValidator } from "./validators";export function someUtility() { // ...}export const myMutation = mutation({ args: { data: myValidator, // <-- May be undefined due to import cycle }, handler: async (ctx, args) => { // ... },}); When `functions.ts` is loaded, it imports from `validators.ts`, which in turn tries to import from `functions.ts`. Since `functions.ts` hasn't finished the `import` statement yet, `myValidator` is still `undefined`, causing the `mutation` builder to throw an error. Note: the value may be defined at runtime if you try to log it. This is only a quirk of TypeScript’s import time behavior. ### Cycles involving `schema.ts`[​](https://docs.convex.dev/error#cycles-involving-schemats "Direct link to cycles-involving-schemats") A common way to accidentally introduce this kind of cycle is through your `schema.ts` file. Larger apps often define validators or whole tables in other files and import them into `schema.ts`. If these files import from `schema.ts` or depend on files that do, you have a cycle. schema.ts → validators.ts → someFile.ts → schema.ts To break the cycle, define validators in "pure" files that have minimal dependencies, and import them into the places they are needed. ### Investigate circular imports[​](https://docs.convex.dev/error#investigate-circular-imports "Direct link to Investigate circular imports") If you suspect a circular import but aren't sure where it is, tools like [madge](https://github.com/pahen/madge) can help you visualize your import graph and list cycles: npx madge convex/ --extensions ts --exclude api.d.ts --circular We exclude `api.d.ts` here because type-only imports are generally safe. * [Write conflict: Optimistic concurrency control](https://docs.convex.dev/error#1) * [Example A](https://docs.convex.dev/error#example-a) * [Example B](https://docs.convex.dev/error#example-b) * [Remediation](https://docs.convex.dev/error#remediation) * [Resources](https://docs.convex.dev/error#resources) * [Related Components](https://docs.convex.dev/error#related-components) * [Undefined validator](https://docs.convex.dev/error#undefined-validator) * [Example](https://docs.convex.dev/error#example) * [Cycles involving `schema.ts`](https://docs.convex.dev/error#cycles-involving-schemats) * [Investigate circular imports](https://docs.convex.dev/error#investigate-circular-imports) --- # Dashboard | Convex Developer Hub [Skip to main content](https://docs.convex.dev/dashboard#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! ![Dashboard Projects View](https://docs.convex.dev/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. --- # iOS & macOS Swift | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/swift#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page The Convex Swift client library enables your iOS or macOS application to interact with your Convex backend. It allows your frontend code to: 1. Call your [queries](https://docs.convex.dev/functions/query-functions) , [mutations](https://docs.convex.dev/functions/mutation-functions)  and [actions](https://docs.convex.dev/functions/actions) 2. Authenticate users using [Auth0](https://docs.convex.dev/auth/auth0) The library is open source and [available on GitHub](https://github.com/get-convex/convex-swift) . Follow the [Swift Quickstart](https://docs.convex.dev/quickstart/swift)  to get started. Installation[​](https://docs.convex.dev/client/swift#installation "Direct link to Installation") ------------------------------------------------------------------------------------------------- For an iOS or macOS project in Xcode, you’ll need to perform the following steps to add a dependency on the `ConvexMobile` library. 1. Click on the top-level app container in the project navigator on the left 2. Click on the app name under the PROJECT heading 3. Click the _Package Dependencies_ tab 4. Click the + button ![Screenshot 2024-10-02 at 2.33.43 PM.png](https://docs.convex.dev/assets/images/swift_qs_step_2-4edae92b06d29aba638512edc3fcc267.png) 5. Paste [`https://github.com/get-convex/convex-swift`](https://github.com/get-convex/convex-swift) into the search box and press Enter 6. When the `convex-swift` package loads, click the Add Package button 7. In the _Package Products_ dialog, select your product name in the _Add to Target_ dropdown 8. Click _Add Package_ The latest release and [release history](https://github.com/get-convex/convex-swift/releases) is available on GitHub. Connecting to a backend[​](https://docs.convex.dev/client/swift#connecting-to-a-backend "Direct link to Connecting to a backend") ---------------------------------------------------------------------------------------------------------------------------------- The `ConvexClient` is used to establish and maintain a connection between your application and the Convex backend. First you need to create an instance of the client by giving it your backend deployment URL: import ConvexMobilelet convex = ConvexClient(deploymentUrl: "https://.convex.cloud") You should create and use one instance of the `ConvexClient` for the lifetime of your application process. You can store the client in a global constant like shown above. An actual connection to the Convex backend won’t be initiated until you call a method on the `ConvexClient`. After that it will maintain the connection and re-establish it if it gets dropped. Fetching data[​](https://docs.convex.dev/client/swift#fetching-data "Direct link to Fetching data") ---------------------------------------------------------------------------------------------------- The Swift Convex library gives you access to the Convex sync engine, which enables real-time _subscriptions_ to query results. You subscribe to queries with the `subscribe` method on `ConvexClient` which returns a [`Publisher`](https://developer.apple.com/documentation/combine) . The data available via the `Publisher` will change over time as the underlying data backing the query changes. You can call methods on the `Publisher` to transform and consume the data it provides. A simple way to consume a query that returns a list of strings in a `View` is to use a combination of a `@State` containing a list and the `.task` modifier with code that loops over the query results as an `AsyncSequence`: struct ColorList: View { @State private var colors: [String] = [] var body: some View { List { ForEach(colors, id: \.self) { color in Text(color) } }.task { let latestColors = convex.subscribe(to: "colors:get", yielding: [String].self) .replaceError(with: []) .values for await colors in latestColors { self.colors = colors } } }} Any time the data that powers the backend `"colors:get"` query changes, a new array of `String` values will appear in the `AsyncSequence` and the `View`'s `colors` list gets assigned the new data. The UI will then rebuild reactively to reflect the changed data. ### Query arguments[​](https://docs.convex.dev/client/swift#query-arguments "Direct link to Query arguments") You can pass arguments to `subscribe` and they will be supplied to the associated backend `query` function. The arguments must be a Dictionary keyed with strings and the values should generally be primitive types, Arrays and other Dictionaries. let publisher = convex.subscribe(to: "colors:get", with:["onlyFavorites": true], yielding:[String].self) Assuming the `colors:get` query accepts an `onlyFavorites` argument, the value can be received and used to perform logic in the query function. tip Use [Decodable structs](https://docs.convex.dev/client/swift/data-types#custom-data-types) to automatically convert Convex objects to Swift structs. caution * There are important gotchas when [sending and receiving numbers](https://docs.convex.dev/client/swift/data-types#numerical-types) between Swift and Convex. * Depending on your backend functions, you may need to deal with [reserved Swift keywords](https://docs.convex.dev/client/swift/data-types#field-name-conversion) . ### Subscription lifetime[​](https://docs.convex.dev/client/swift#subscription-lifetime "Direct link to Subscription lifetime") The `Publisher` returned from `subscribe` will persist as long as the associated `View` or `ObservableObject`. When either is no longer part of the UI, the underlying query subscription to Convex will be canceled. Editing Data[​](https://docs.convex.dev/client/swift#editing-data "Direct link to Editing Data") ------------------------------------------------------------------------------------------------- You can use the `mutation` method on `ConvexClient` to trigger a backend [mutation](https://docs.convex.dev/functions/mutation-functions) . `mutation` is an `async` method so you'll need to call it within a `Task`. Mutations can return a value or not. Mutations can also receive arguments, just like queries. Here's an example of calling a mutation with arguments that returns a value: let isColorAdded: Bool = try await convex.mutation("colors:put", with: ["color": newColor]) ### Handling errors[​](https://docs.convex.dev/client/swift#handling-errors "Direct link to Handling errors") If an error occurs during a call to `mutation`, it will throw. Typically you may want to catch [`ConvexError`](https://docs.convex.dev/functions/error-handling/application-errors)  and `ServerError` and handle them however is appropriate in your application. Here’s a small example of how you might handle an error from `colors:put` if it threw a `ConvexError` with an error message if a color already existed. do { try await convex.mutation("colors:put", with: ["color": newColor])} catch ClientError.ConvexError(let data) { errorMessage = try! JSONDecoder().decode(String.self, from: Data(data.utf8)) colorNotAdded = true} See documentation on [error handling](https://docs.convex.dev/functions/error-handling/)  for more details. Calling third-party APIs[​](https://docs.convex.dev/client/swift#calling-third-party-apis "Direct link to Calling third-party APIs") ------------------------------------------------------------------------------------------------------------------------------------- You can use the `action` method on `ConvexClient` to trigger a backend [action](https://docs.convex.dev/functions/actions) . Calls to `action` can accept arguments, return values and throw exceptions just like calls to `mutation`. Even though you can call actions from your client code, it's not always the right choice. See the action docs for tips on [calling actions from clients](https://docs.convex.dev/functions/actions#calling-actions-from-clients) . Authentication[​](https://docs.convex.dev/client/swift#authentication "Direct link to Authentication") ------------------------------------------------------------------------------------------------------- You can use `ConvexClientWithAuth` in place of `ConvexClient` to use an authentication provider. You'll need to choose an existing `AuthProvider` implementation or possibly create your own. See the `AuthProvider` options below and consult the overall [Convex authentication docs](https://docs.convex.dev/auth)  as needed. ### Auth0[​](https://docs.convex.dev/client/swift#authentication-with-auth0 "Direct link to Auth0") To use Auth0, you'll need to add a dependency on the `convex-swift-auth0` library as well as have an Auth0 account and application configuration. See the [README](https://github.com/get-convex/convex-swift-auth0/blob/main/README.md)  in the `convex-swift-auth0` repo for more detailed setup instructions, and the [Workout example app](https://github.com/get-convex/ios-convex-workout)  which is configured for Auth0. ### Clerk[​](https://docs.convex.dev/client/swift#authentication-with-clerk "Direct link to Clerk") To use Clerk, you'll need to add a dependency on the `clerk-convex-swift` library as well as have a Clerk account and application configured to use Convex. See the [README](https://github.com/clerk/clerk-convex-swift/blob/main/README.md) in the `clerk-convex-swift` repo for detailed setup instructions. Clerk also has [a version of the Workout example app](https://github.com/clerk/clerk-convex-swift/tree/main/Example) available so you can see a real-world integration. ### Custom auth providers[​](https://docs.convex.dev/client/swift#custom-auth-providers "Direct link to Custom auth providers") It should also be possible to integrate other similar OpenID Connect authentication providers. See the [`AuthProvider`](https://github.com/get-convex/convex-swift/blob/125b71b2f8725931a5b0e8252f0799ef2adad120/Sources/ConvexMobile/ConvexMobile.swift#L177)  protocol in the `convex-swift` repo for more info. Production and dev deployments[​](https://docs.convex.dev/client/swift#production-and-dev-deployments "Direct link to Production and dev deployments") ------------------------------------------------------------------------------------------------------------------------------------------------------- When you're ready to move toward [production](https://docs.convex.dev/production)  for your app, you can setup your Xcode build system to point different build targets to different Convex deployments. Build environment configuration is highly specialized, and it’s possible that you or your team have different conventions, but this is one way to approach the problem. 1. Create “Dev” and “Prod” folders in your project sources. 2. Add an `Env.swift` file in each one with contents like: let deploymentUrl = "https://$DEV_OR_PROD.convex.cloud" 3. Put your dev URL in `Dev/Env.swift` and your prod URL in `Prod/Env.swift`. Don’t worry if Xcode complains that `deploymentUrl` is defined multiple times. 4. Click on your top-level project in the explorer view on the left. 5. Select your build target from the **TARGETS** list. 6. Change the target’s name so it ends in “dev”. 7. Right/Ctrl-click it and duplicate it, giving it a name that ends in “prod”. 8. With the “dev” target selected, click the **Build Phases** tab. 9. Expand the **Compile Sources** section. 10. Select `Prod/Env.swift` and remove it with the - button. 11. Likewise, open the “prod” target and remove `Dev/Env.swift` from its sources. ![Screenshot 2024-10-03 at 1.34.34 PM.png](https://docs.convex.dev/assets/images/swift_env_setup-39f43c73bf9c8599957530a43582ca3d.png) Now you can refer to `deploymentUrl` wherever you create your `ConvexClient` and depending on the target that you build, it will use your dev or prod URL. Structuring your application[​](https://docs.convex.dev/client/swift#structuring-your-application "Direct link to Structuring your application") ------------------------------------------------------------------------------------------------------------------------------------------------- The examples shown in this guide are intended to be brief, and don't provide guidance on how to structure a whole application. If you want a more robust and layered approach, put your code that interacts with `ConvexClient`in a class that conforms to `ObservableObject`. Then your `View` can observe that object as a `@StateObject` and will rebuild whenever it changes. For example, if we adapt the `colors:get` example from above to a `ViewModel: ObservableObject` class, the `View` no longer plays a direct part in fetching the data - it only knows that the list of `colors` is provided by the `ViewModel`. import SwiftUIclass ViewModel: ObservableObject { @Published var colors: [String] = [] init() { convex.subscribe(to: "colors:get") .replaceError(with: []) .receive(on: DispatchQueue.main) .assign(to: &$colors) }}struct ContentView: View { @StateObject var viewModel = ViewModel() var body: some View { List { ForEach(viewModel.colors, id: \.self) { color in Text(color) } } }} Depending on your needs and the scale of your app, it might make sense to give it even more formal structure as demonstrated in something like [https://github.com/nalexn/clean-architecture-swiftui](https://github.com/nalexn/clean-architecture-swiftui) . Under the hood[​](https://docs.convex.dev/client/swift#under-the-hood "Direct link to Under the hood") ------------------------------------------------------------------------------------------------------- The Swift Convex library is built on top of the official [Convex Rust client](https://docs.convex.dev/client/rust) . It handles maintaining a WebSocket connection with the Convex backend and implements the full Convex protocol. All method calls on `ConvexClient` are handled via a Tokio async runtime on the Rust side and are safe to call from the application's main actor. ### Observing WebSocket state[​](https://docs.convex.dev/client/swift#observing-websocket-state "Direct link to Observing WebSocket state") You can call the `watchWebSocketState()` method on a client to get a `Publisher` that will keep you up to date on the status of the Convex WebSocket connection. The connection is either in `.connected` or `.connecting` state, as Convex always tries to maintain a connection to the backend. _Available since [version 0.7.0](https://github.com/get-convex/convex-swift/releases/tag/0.7.0) ._ ### Debug logging[​](https://docs.convex.dev/client/swift#debug-logging "Direct link to Debug logging") While developing your application, it can be useful to see the underlying state of the Convex client. Calling the `initConvexLogging()` function in your `App.init` method will cause Convex to output log messages to the OSLog where they can easily be viewed in XCode. caution The debug logs can contain sensitive data that your application sends to/from your Convex backend. Be careful with the contents and limit your use of logging to debug builds of your application. _Available since [version 0.6.0](https://github.com/get-convex/convex-swift/releases/tag/0.6.0) ._ * [Installation](https://docs.convex.dev/client/swift#installation) * [Connecting to a backend](https://docs.convex.dev/client/swift#connecting-to-a-backend) * [Fetching data](https://docs.convex.dev/client/swift#fetching-data) * [Query arguments](https://docs.convex.dev/client/swift#query-arguments) * [Subscription lifetime](https://docs.convex.dev/client/swift#subscription-lifetime) * [Editing Data](https://docs.convex.dev/client/swift#editing-data) * [Handling errors](https://docs.convex.dev/client/swift#handling-errors) * [Calling third-party APIs](https://docs.convex.dev/client/swift#calling-third-party-apis) * [Authentication](https://docs.convex.dev/client/swift#authentication) * [Auth0](https://docs.convex.dev/client/swift#authentication-with-auth0) * [Clerk](https://docs.convex.dev/client/swift#authentication-with-clerk) * [Custom auth providers](https://docs.convex.dev/client/swift#custom-auth-providers) * [Production and dev deployments](https://docs.convex.dev/client/swift#production-and-dev-deployments) * [Structuring your application](https://docs.convex.dev/client/swift#structuring-your-application) * [Under the hood](https://docs.convex.dev/client/swift#under-the-hood) * [Observing WebSocket state](https://docs.convex.dev/client/swift#observing-websocket-state) * [Debug logging](https://docs.convex.dev/client/swift#debug-logging) --- # Next.js | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/nextjs/app-router/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [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](https://docs.convex.dev/client/nextjs/pages-router/) version of this page. Getting started[​](https://docs.convex.dev/client/nextjs/app-router/#getting-started "Direct link to Getting started") ----------------------------------------------------------------------------------------------------------------------- Follow the [Next.js Quickstart](https://docs.convex.dev/quickstart/nextjs) to add Convex to a new or existing Next.js project. Calling Convex functions from client code[​](https://docs.convex.dev/client/nextjs/app-router/#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](https://docs.convex.dev/client/react) . [Convex React library documentation\ ----------------------------------](https://docs.convex.dev/client/react) Server rendering (SSR)[​](https://docs.convex.dev/client/nextjs/app-router/#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](https://docs.convex.dev/functions/query-functions#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](https://docs.convex.dev/client/nextjs/app-router/server-rendering) page for more details about preloading data for Client Components, fetching data and authentication in Server Components, and implementing Route Handlers. Adding authentication[​](https://docs.convex.dev/client/nextjs/app-router/#adding-authentication "Direct link to Adding authentication") ----------------------------------------------------------------------------------------------------------------------------------------- ### Client-side only[​](https://docs.convex.dev/client/nextjs/app-router/#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](https://docs.convex.dev/auth/clerk) or [Auth0](https://docs.convex.dev/auth/auth0) , 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](https://docs.convex.dev/api/classes/react.ConvexReactClient) instance between pages to avoid needing to reconnect to Convex on client-side page navigation. ### Server and client side[​](https://docs.convex.dev/client/nextjs/app-router/#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[​](https://docs.convex.dev/client/nextjs/app-router/#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[​](https://docs.convex.dev/client/nextjs/app-router/#auth0 "Direct link to Auth0") See the [Auth0 Next.js](https://auth0.com/docs/quickstart/webapp/nextjs/01-login) guide. #### Other providers[​](https://docs.convex.dev/client/nextjs/app-router/#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. * [Getting started](https://docs.convex.dev/client/nextjs/app-router/#getting-started) * [Calling Convex functions from client code](https://docs.convex.dev/client/nextjs/app-router/#calling-convex-functions-from-client-code) * [Server rendering (SSR)](https://docs.convex.dev/client/nextjs/app-router/#server-rendering-ssr) * [Adding authentication](https://docs.convex.dev/client/nextjs/app-router/#adding-authentication) * [Client-side only](https://docs.convex.dev/client/nextjs/app-router/#client-side-only) * [Server and client side](https://docs.convex.dev/client/nextjs/app-router/#server-and-client-side) --- # React Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/react#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! To get setup quickly with Convex and React run **`npm create convex@latest`** or follow the guide below. * * * Learn how to query data from Convex in a React app using Vite and TypeScript 1. Create a React app Create a React app using the `create vite` command. npm create vite@latest my-app -- --template react-ts 2. Install the Convex client and server library To get started, install the `convex` package which provides a convenient interface for working with Convex from a React app. Navigate to your app directory and install `convex`. cd my-app && npm install convex 3. Set up a Convex dev deployment Next, run `npx 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. npx 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. npx convex import --table tasks sampleData.jsonl 6. (optional) Define a schema Add a new file `schema.ts` in the `convex/` folder with a description of your data. This will declare the types of your data for optional typechecking with TypeScript, and it will be also enforced at runtime. Alternatively remove the line `'plugin:@typescript-eslint/recommended-requiring-type-checking',` from the `.eslintrc.cjs` file to lower the type checking strictness. convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ tasks: defineTable({ text: v.string(), isCompleted: v.boolean(), }),}); 7. Expose a database query Add a new file `tasks.ts` 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.ts TS import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 8. Connect the app to your backend In `src/main.tsx`, create a `ConvexReactClient` and pass it to a `ConvexProvider` wrapping your app. src/main.tsx TS import React from "react";import ReactDOM from "react-dom/client";import App from "./App";import "./index.css";import { ConvexProvider, ConvexReactClient } from "convex/react";const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);ReactDOM.createRoot(document.getElementById("root")!).render( ,); 9. Display the data in your app In `src/App.tsx`, use the `useQuery` hook to fetch from your `api.tasks.get` API function and display the data. src/App.tsx TS import "./App.css";import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";function App() { const tasks = useQuery(api.tasks.get); return (
{tasks?.map(({ _id, text }) =>
{text}
)}
);}export default App; 10. Start the app Start the app, open [http://localhost:5173/](http://localhost:5173/) in a browser, and see the list of tasks. npm run dev See the complete [React documentation](https://docs.convex.dev/client/react) . --- # Testing | Convex Developer Hub [Skip to main content](https://docs.convex.dev/testing#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex makes it easy to test your app via automated tests running in JS or against a real backend, and manually in dev, preview and staging environments. Automated tests[​](https://docs.convex.dev/testing#automated-tests "Direct link to Automated tests") ----------------------------------------------------------------------------------------------------- ### `convex-test` library[​](https://docs.convex.dev/testing#convex-test-library "Direct link to convex-test-library") [Use the `convex-test` library](https://docs.convex.dev/testing/convex-test) to test your functions in JS via the excellent Vitest testing framework. ### Testing against a real backend[​](https://docs.convex.dev/testing#testing-against-a-real-backend "Direct link to Testing against a real backend") Convex open source builds allow you to test all of your backend logic running on a real [local Convex backend](https://docs.convex.dev/testing/convex-backend) . ### Set up testing in CI[​](https://docs.convex.dev/testing#set-up-testing-in-ci "Direct link to Set up testing in CI") It's a good idea to test your app continuously in a controlled environment. No matter which way automated method you use, it's easy to run them with [GitHub Actions](https://docs.convex.dev/testing/ci) . Manual tests[​](https://docs.convex.dev/testing#manual-tests "Direct link to Manual tests") -------------------------------------------------------------------------------------------- ### Running a function in dev[​](https://docs.convex.dev/testing#running-a-function-in-dev "Direct link to Running a function in dev") Manually run a function in dev to quickly see if things are working: * [Run functions from the command line](https://docs.convex.dev/cli#run-convex-functions) * [Run functions from the dashboard](https://docs.convex.dev/dashboard/deployments/functions#running-functions) ### Preview deployments[​](https://docs.convex.dev/testing#preview-deployments "Direct link to Preview deployments") [Use preview deployments](https://docs.convex.dev/production/hosting/preview-deployments) to get early feedback from your team for your in-progress features. ### Staging environment[​](https://docs.convex.dev/testing#staging-environment "Direct link to Staging environment") You can set up a separate project as a staging environment to test against. See [Deploying Your App to Production](https://docs.convex.dev/production#staging-environment) . * [Automated tests](https://docs.convex.dev/testing#automated-tests) * [`convex-test` library](https://docs.convex.dev/testing#convex-test-library) * [Testing against a real backend](https://docs.convex.dev/testing#testing-against-a-real-backend) * [Set up testing in CI](https://docs.convex.dev/testing#set-up-testing-in-ci) * [Manual tests](https://docs.convex.dev/testing#manual-tests) * [Running a function in dev](https://docs.convex.dev/testing#running-a-function-in-dev) * [Preview deployments](https://docs.convex.dev/testing#preview-deployments) * [Staging environment](https://docs.convex.dev/testing#staging-environment) --- # Remix Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/remix#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Remix app. 1. Create a Remix site Create a Remix site using the `npx create-remix@latest` command. npx create-remix@latest my-remix-app 2. Install the Convex library To get started, install the `convex` package. cd my-remix-app && npm install convex 3. Set up a Convex dev deployment Next, run `npx 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. npx convex dev 4. Create sample data for your database Create a `sampleData.jsonl` file at the root of you app and fill it with the sample data given. 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 you just created in `sampleData.jsonl` into your Convex database with the `import` command. npx convex import --table tasks sampleData.jsonl 6. Expose a database query Add a new file `tasks.ts` 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.ts import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 7. Wire up the ConvexProvider Modify `app/root.tsx` to set up the Convex client there to make it available on every page of your app. app/root.tsx import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData,} from "@remix-run/react";import { ConvexProvider, ConvexReactClient } from "convex/react";import { useState } from "react";export async function loader() { const CONVEX_URL = process.env["CONVEX_URL"]!; return { ENV: { CONVEX_URL } };}export function Layout({ children }: { children: React.ReactNode }) { const { ENV } = useLoaderData(); const [convex] = useState(() => new ConvexReactClient(ENV.CONVEX_URL)); return ( {children} );}export default function App() { return ;} 8. Display the data in your app In `app/routes/_index.tsx` use `useQuery` to subscribe your `api.tasks.get` API function. app/routes/\_index.tsx import type { MetaFunction } from "@remix-run/node";import { api } from "convex/_generated/api";import { useQuery } from "convex/react";export const meta: MetaFunction = () => { return [ { title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }, ];};export default function Index() { const tasks = useQuery(api.tasks.get); return (

Welcome to Remix

{tasks === undefined ? "loading..." : tasks.map(({ _id, text }) =>
{text}
)}
);} 9. Start the app Start the app, open [http://localhost:5173](http://localhost:5173/) in a browser, and see the list of tasks. npm run dev Remix uses the React web library. See the complete [React documentation](https://docs.convex.dev/client/react) . --- # React Native Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/react-native#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a React Native app. 1. Create a React Native app Create a React Native app using the `npx create-expo-app` command. npx create-expo-app my-app 2. Install the Convex client and server library To get started, install the `convex` package which provides a convenient interface for working with Convex from a React app. Navigate to your app and install `convex`. cd my-app && npm install convex 3. Set up a Convex dev deployment Next, run `npx 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. npx convex dev 4. Create sample data for your database 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. npx convex import --table tasks sampleData.jsonl 6. Expose a database query Add a new file `tasks.ts` 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.ts import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 7. Reset the Expo project If you haven't done so yet, reset the Expo project to get a fresh `app` directory. npm run reset-project 8. Connect the app to your backend In `_layout.tsx`, create a `ConvexReactClient` and pass it to a `ConvexProvider` wrapping your component tree. app/\_layout.tsx import { ConvexProvider, ConvexReactClient } from "convex/react";import { Stack } from "expo-router";const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, { unsavedChangesWarning: false,});export default function RootLayout() { return ( );} 9. Display the data in your app In `index.tsx` use the `useQuery` hook to fetch from your `api.tasks.get` API. app/index.tsx import { api } from "@/convex/_generated/api";import { useQuery } from "convex/react";import { Text, View } from "react-native";export default function Index() { const tasks = useQuery(api.tasks.get); return ( {tasks?.map(({ _id, text }) => {text})} );} 10. Start the app Start the app, scan the provided QR code with your phone, and see the serialized list of tasks in the center of the screen. npm start React native uses the same library as React web. See the complete [React documentation](https://docs.convex.dev/client/react) . --- # AI Code Generation | Convex Developer Hub [Skip to main content](https://docs.convex.dev/ai#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! [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[​](https://docs.convex.dev/ai#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](https://docs.convex.dev/ai/using-cursor#add-convex-cursorrules) * [Windsurf](https://docs.convex.dev/ai/using-windsurf#add-convex-rules) * [GitHub Copilot](https://docs.convex.dev/ai/using-github-copilot#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[​](https://docs.convex.dev/ai#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](https://docs.convex.dev/cli/agent-mode) . 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 iCONVEX_AGENT_MODE=anonymous npx convex dev --once or bun iCONVEX_AGENT_MODE=anonymous bun x convex dev --once This command requires "full" internet access to download the binary. Convex MCP Server[​](https://docs.convex.dev/ai#convex-mcp-server "Direct link to Convex MCP Server") ------------------------------------------------------------------------------------------------------ [Setup the Convex MCP server](https://docs.convex.dev/ai/convex-mcp-server) to give your AI coding agent access to your Convex deployment to query and optimize your project. --- # Android Kotlin | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/android#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex Android client library enables your Android application to interact with your Convex backend. It allows your frontend code to: 1. Call your [queries](https://docs.convex.dev/functions/query-functions) , [mutations](https://docs.convex.dev/functions/mutation-functions)  and [actions](https://docs.convex.dev/functions/actions) 2. Authenticate users using [Auth0](https://docs.convex.dev/auth/auth0) The library is open source and [available on GitHub](https://github.com/get-convex/convex-mobile/tree/main/android) . Follow the [Android Quickstart](https://docs.convex.dev/quickstart/android)  to get started. Installation[​](https://docs.convex.dev/client/android#installation "Direct link to Installation") --------------------------------------------------------------------------------------------------- You'll need to make the following changes to your app's `build.gradle[.kts]` file. plugins { // ... existing plugins kotlin("plugin.serialization") version "1.9.0"}dependencies { // ... existing dependencies implementation("dev.convex:android-convexmobile:0.8.0@aar") { isTransitive = true } implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")} After that, sync Gradle to pick up those changes. Your app will now have access to the Convex for Android library as well as Kotlin's JSON serialization which is used to communicate between your code and the Convex backend. Connecting to a backend[​](https://docs.convex.dev/client/android#connecting-to-a-backend "Direct link to Connecting to a backend") ------------------------------------------------------------------------------------------------------------------------------------ The `ConvexClient` is used to establish and maintain a connect between your application and the Convex backend. First you need to create an instance of the client by giving it your backend deployment URL: package com.example.convexappimport dev.convex.android.ConvexClientval convex = ConvexClient("https://.convex.cloud") You should create and use one instance of the `ConvexClient` for the lifetime of your application process. It can be convenient to create a custom Android [`Application`](https://developer.android.com/reference/android/app/Application) subclass and initialize it there: package com.example.convexappimport android.app.Applicationimport dev.convex.android.ConvexClientclass MyApplication : Application() { lateinit var convex: ConvexClient override fun onCreate() { super.onCreate() convex = ConvexClient("https://.convex.cloud") }} Once you've done that, you can access the client from a Jetpack Compose `@Composable` function like this: val convex = (application as MyApplication).convex Fetching data[​](https://docs.convex.dev/client/android#fetching-data "Direct link to Fetching data") ------------------------------------------------------------------------------------------------------ Convex for Android gives you access to the Convex [reactor](https://docs.convex.dev/tutorial/reactor) , which enables real-time _subscriptions_ to query results. You subscribe to queries with the `subscribe` method on `ConvexClient` which returns a `Flow`. The contents of the `Flow` will change over time as the underlying data backing the query changes. All methods on `ConvexClient` suspend, and need to be called from a `CoroutineScope` or another `suspend` function. A simple way to consume a query that returns a list of strings from a `@Composable` is to use a combination of mutable state containing a list and `LaunchedEffect`: var workouts: List by remember { mutableStateOf(listOf()) }LaunchedEffect("onLaunch") { client.subscribe>("workouts:get").collect { result -> result.onSuccess { receivedWorkouts -> workouts = receivedWorkouts } }} Any time the data that powers the backend `"workouts:get"` query changes, a new `Result>` will be emitted into the `Flow` and the `workouts` list will refresh with the new data. Any UI that uses `workouts` will then rebuild, giving you a fully reactive UI. Note: you may prefer to put the subscription logic wrapped a Repository as described in the [Android architecture patterns](https://developer.android.com/topic/architecture/data-layer) . ### Query arguments[​](https://docs.convex.dev/client/android#query-arguments "Direct link to Query arguments") You can pass arguments to `subscribe` and they will be supplied to the associated backend `query` function. The arguments are typed as `Map`. The values in the map must be primitive values or other maps and lists. val favoriteColors = mapOf("favoriteColors" to listOf("blue", "red"))client.subscribe>("users:list", args = favoriteColors) Assuming a backend query that accepts a `favoriteColors` argument, the value can be received and used to perform logic in the query function. tip Use serializable [Kotlin Data classes](https://docs.convex.dev/client/android/data-types#custom-data-types) to automatically convert Convex objects to Kotlin model classes. caution * There are important gotchas when [sending and receiving numbers](https://docs.convex.dev/client/android/data-types#numerical-types) between Kotlin and Convex. * `_` is a used to signify private fields in Kotlin. If you want to use a `_creationTime` and `_id` Convex fields directly without warnings you'll have to [convert the field name in Kotlin](https://docs.convex.dev/client/android/data-types#field-name-conversion) . * Depending on your backend functions, you may need to deal with [reserved Kotlin keywords](https://docs.convex.dev/client/android/data-types#field-name-conversion) . ### Subscription lifetime[​](https://docs.convex.dev/client/android#subscription-lifetime "Direct link to Subscription lifetime") The `Flow` returned from `subscribe` will persist as long as something is waiting to consume results from it. When a `@Composable` or `ViewModel` with a subscription goes out of scope, the underlying query subscription to Convex will be canceled. Editing data[​](https://docs.convex.dev/client/android#editing-data "Direct link to Editing data") --------------------------------------------------------------------------------------------------- You can use the `mutation` method on `ConvexClient` to trigger a backend [mutation](https://docs.convex.dev/functions/mutation-functions) . You'll need to use it in another `suspend` function or a `CoroutineScope`. Mutations can return a value or not. If you expect a type in the response, indicate it in the call signature. Mutations can also receive arguments, just like queries. Here's an example of returning a type from a mutation with arguments: val recordsDeleted = convex.mutation<@ConvexNum Int>( "messages:cleanup", args = mapOf("keepLatest" to 100)) If an error occurs during a call to `mutation`, it will throw an exception. Typically you may want to catch [`ConvexError`](https://docs.convex.dev/functions/error-handling/application-errors) and `ServerError` and handle them however is appropriate in your application. See documentation on [error handling](https://docs.convex.dev/functions/error-handling/) for more details. Calling third-party APIs[​](https://docs.convex.dev/client/android#calling-third-party-apis "Direct link to Calling third-party APIs") --------------------------------------------------------------------------------------------------------------------------------------- You can use the `action` method on `ConvexClient` to trigger a backend [action](https://docs.convex.dev/functions/actions) . Calls to `action` can accept arguments, return values and throw exceptions just like calls to `mutation`. Even though you can call actions from Android, it's not always the right choice. See the action docs for tips on [calling actions from clients](https://docs.convex.dev/functions/actions#calling-actions-from-clients) . Authentication[​](https://docs.convex.dev/client/android#authentication "Direct link to Authentication") --------------------------------------------------------------------------------------------------------- You can use `ConvexClientWithAuth` in place of `ConvexClient` to use an authentication provider. You'll need to choose an existing `AuthProvider` implementation or possibly create your own. See the `AuthProvider` options below and consult the overall [Convex authentication docs](https://docs.convex.dev/auth)  as needed. ### Auth0[​](https://docs.convex.dev/client/android#authentication-with-auth0 "Direct link to Auth0") To use Auth0, you'll need the `convex-android-auth0` library as well as an Auth0 account and application configuration. See the [README](https://github.com/get-convex/convex-android-auth0/blob/main/README.md) in the `convex-android-auth0` repo for more detailed setup instructions, and the [Workout example app](https://github.com/get-convex/android-convex-workout) which is configured for Auth0. ### Clerk[​](https://docs.convex.dev/client/android#authentication-with-clerk "Direct link to Clerk") To use Clerk, you'll need to add a dependency on the `clerk-convex-kotlin` library as well as have a Clerk account and application configured to use Convex. See the [README](https://github.com/clerk/clerk-convex-kotlin/blob/main/README.md) in the `clerk-convex-kotlin` repo for detailed setup instructions. Clerk also has [a version of the Workout example app](https://github.com/clerk/clerk-convex-kotlin/tree/main/samples/workout-tracker) available so you can see a real-world integration. ### Custom auth providers[​](https://docs.convex.dev/client/android#custom-auth-providers "Direct link to Custom auth providers") It should also be possible to integrate other similar OpenID Connect authentication providers. See the [`AuthProvider`](https://github.com/get-convex/convex-mobile/blob/720a79a752e76297cc8c905d4f6e2dfbbc82bae7/android/convexmobile/src/main/java/dev/convex/android/ConvexClient.kt#L376) interface in the `convex-mobile` repo for more info. Production and dev deployments[​](https://docs.convex.dev/client/android#production-and-dev-deployments "Direct link to Production and dev deployments") --------------------------------------------------------------------------------------------------------------------------------------------------------- When you're ready to move toward [production](https://docs.convex.dev/production) for your app, you can setup your Android build system to point different builds or flavors of your application to different Convex deployments. One fairly simple way to do it is by passing different values (e.g. deployment URL) to different build targets or flavors. Here's a simple example that shows using different deployment URLs for release and debug builds: // In the android section of build.gradle.kts:buildTypes { release { // Snip various other config like ProGuard ... resValue("string", "convex_url", "YOUR_PROD.convex.cloud") } debug { resValue("string", "convex_url", "YOUR_DEV.convex.cloud") }} Then you can build your `ConvexClient` using a single resource in code, and it will get the right value at compile time. val convex = ConvexClient(context.getString(R.string.convex_url)) tip You may not want these urls checked into your repository. One pattern is to create a custom `my_app.properties` file that is configured to be ignored in your `.gitignore` file. You can then read this file in your `build.gradle.kts` file. You can see this pattern in use in the [workout sample app](https://github.com/get-convex/android-convex-workout?tab=readme-ov-file#configuration) . Structuring your application[​](https://docs.convex.dev/client/android#structuring-your-application "Direct link to Structuring your application") --------------------------------------------------------------------------------------------------------------------------------------------------- The examples shown in this guide are intended to be brief, and don't provide guidance on how to structure a whole application. The official [Android application architecture](https://developer.android.com/topic/architecture/intro) docs cover best practices for building applications, and Convex also has a [sample open source application](https://github.com/get-convex/android-convex-workout/tree/main) that attempts to demonstrate what a small multi-screen application might look like. In general, do the following: 1. Embrace Flows and [unidirectional data flow](https://developer.android.com/develop/ui/compose/architecture#udf) 2. Have a clear [data layer](https://developer.android.com/topic/architecture/data-layer) (use Repository classes with `ConvexClient` as your data source) 3. Hold UI state in a [ViewModel](https://developer.android.com/topic/architecture/recommendations#viewmodel) Testing[​](https://docs.convex.dev/client/android#testing "Direct link to Testing") ------------------------------------------------------------------------------------ `ConvexClient` is an `open` class so it can be mocked or faked in unit tests. If you want to use more of the real client, you can pass a fake `MobileConvexClientInterface` in to the `ConvexClient` constructor. Just be aware that you'll need to provide JSON in Convex's undocumented [JSON format](https://github.com/get-convex/convex-mobile/blob/5babd583631a7ff6d739e1a2ab542039fd532548/android/convexmobile/src/main/java/dev/convex/android/jsonhelpers.kt#L47) . You can also use the full `ConvexClient` in Android instrumentation tests. You can setup a special backend instance for testing or run a local Convex server and run full integration tests. Under the hood[​](https://docs.convex.dev/client/android#under-the-hood "Direct link to Under the hood") --------------------------------------------------------------------------------------------------------- Convex for Android is built on top of the official [Convex Rust client](https://docs.convex.dev/client/rust) . It handles maintaining a WebSocket connection with the Convex backend and implements the full Convex protocol. All method calls on `ConvexClient` are handled via a Tokio async runtime on the Rust side and are safe to call from the application's main thread. `ConvexClient` also makes heavy use of [Kotlin's serialization framework](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serialization-guide.md) , and most of the functionality in that framework is available for you to use in your applications. Internally, `ConvexClient` enables the JSON [`ignoreUnknownKeys`](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#ignoring-unknown-keys) and [`allowSpecialFloatingPointValues`](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#allowing-special-floating-point-values) features. ### Observing WebSocket state[​](https://docs.convex.dev/client/android#observing-websocket-state "Direct link to Observing WebSocket state") You can use the `webSocketStateFlow` attribute on a client to get a `StateFlow` that will keep you up to date on the status of the Convex WebSocket connection. The connection is either in `CONNECTED` or `CONNECTING` state, as Convex always tries to maintain a connection to the backend. _Available since [version 0.7.0](https://github.com/get-convex/convex-mobile/releases/tag/kotlin%400.7.0) ._ ### Debug logging[​](https://docs.convex.dev/client/android#debug-logging "Direct link to Debug logging") While developing your application, it can be useful to see the underlying state of the Convex client. Calling the `initConvexLogging()` function in your `Application` `onCreate` method will cause Convex to output log messages to `logcat` where they can easily be viewed in during development. caution The debug logs can contain sensitive data that your application sends to/from your Convex backend. Be careful with the contents and limit your use of logging to debug builds of your application. _Available since [version 0.6.1](https://github.com/get-convex/convex-mobile/releases/tag/kotlin%400.6.1) ._ * [Installation](https://docs.convex.dev/client/android#installation) * [Connecting to a backend](https://docs.convex.dev/client/android#connecting-to-a-backend) * [Fetching data](https://docs.convex.dev/client/android#fetching-data) * [Query arguments](https://docs.convex.dev/client/android#query-arguments) * [Subscription lifetime](https://docs.convex.dev/client/android#subscription-lifetime) * [Editing data](https://docs.convex.dev/client/android#editing-data) * [Calling third-party APIs](https://docs.convex.dev/client/android#calling-third-party-apis) * [Authentication](https://docs.convex.dev/client/android#authentication) * [Auth0](https://docs.convex.dev/client/android#authentication-with-auth0) * [Clerk](https://docs.convex.dev/client/android#authentication-with-clerk) * [Custom auth providers](https://docs.convex.dev/client/android#custom-auth-providers) * [Production and dev deployments](https://docs.convex.dev/client/android#production-and-dev-deployments) * [Structuring your application](https://docs.convex.dev/client/android#structuring-your-application) * [Testing](https://docs.convex.dev/client/android#testing) * [Under the hood](https://docs.convex.dev/client/android#under-the-hood) * [Observing WebSocket state](https://docs.convex.dev/client/android#observing-websocket-state) * [Debug logging](https://docs.convex.dev/client/android#debug-logging) --- # iOS Swift Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/swift#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in an application targeting iOS and MacOS devices built with Swift and SwiftUI. This quickstart assumes that you have a Mac with Xcode, node and npm installed. If you don’t have those tools, take time to install them first. 1. Create a new iOS app in Xcode 1. Click _Create New Project_ 2. Select iOS App and click _Next_ 3. Name your project something like “ConvexQuickstart” 4. Ensure Language is set to Swift and User Interface is SwiftUI 5. Click _Next_ ![Create new iOS project](https://docs.convex.dev/screenshots/swift_qs_step_1.png) 2. Configure dependencies 1. Click on the top-level ConvexQuickstart app container in the project navigator on the left 2. Click on ConvexQuickstart under the PROJECT heading 3. Click the Package Dependencies tab 4. Click the + button (See Screenshot) 5. Paste https://github.com/get-convex/convex-swift into the search box and press enter 6. When the `convex-swift` package loads, click the _Add Package_ button 7. In the _Package Products_ dialog, select ConvexQuickstart in the _Add to Target_ dropdown 8. Click the Add Package button ![Add Convex dependency to package](https://docs.convex.dev/screenshots/swift_qs_step_2.png) 4. Install the Convex backend Open a terminal and `cd` to the directory for the Xcode project you created. Run the following commands to install the Convex client and server library. npm init -ynpm install convex 5. Start Convex Start a Convex dev deployment. Follow the command line instructions to create a new project. npx convex dev 6. Create sample data for your database Create a new `sampleData.jsonl` file in your Swift project directory 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 a table called \`tasks\` in your database Open another terminal tab by pressing ⌘+T which should open in your Swift project directory and run npx convex import --table tasks sampleData.jsonl 8. Expose a database query Create a `tasks.ts` file in the `convex/` directory within your Swift project 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 Swift struct Back in Xcode, create a `struct` at the bottom of the `ContentView` file to match the sample data // We're using the name Todo instead of Task to avoid clashing with// Swift's builtin Task type.struct Todo: Decodable { let _id: String let text: String let isCompleted: Bool} 10. Connect the app to your backend 1. Get the deployment URL of your dev server with `cat .env.local | grep CONVEX_URL` 2. Create a `ConvexClient` instance near the top of the file, just above the `ContentView` struct import SwiftUIimport ConvexMobilelet convex = ConvexClient(deploymentUrl: "YOUR_CONVEX_URL")struct ContentView: View {... 11. Create your UI Replace the default `ContentView` with the following code that will refresh the list of todo items whenever the backend data changes. struct ContentView: View { @State private var todos: [Todo] = [] var body: some View { List { ForEach(todos, id: \._id) { todo in Text(todo.text) } }.task { for await todos: [Todo] in convex.subscribe(to: "tasks:get") .replaceError(with: []).values { self.todos = todos } }.padding() }} 12. Run the app 1. Press ⌘+R or click _Product → Run_ 2. 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. ![App preview](https://docs.convex.dev/screenshots/swift_qs_final.png) See the complete [iOS Swift documentation](https://docs.convex.dev/client/swift) . --- # Svelte Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/svelte#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Svelte app. 1. Create a SvelteKit app Create a SvelteKit app using the `npx sv create` command. Other sets of options will work with the library but for this quickstart guide: * For "Which Svelte app template," choose **"SvelteKit minimal."** * For a package manager, choose **"npm."** * For "Add type checking with TypeScript," choose **"Yes, using TypeScript syntax."** * For "Select additional options," you don't need to enable anything. npx sv@latest create my-app 2. Install the Convex client and server library To get started, install the `convex` and `convex-svelte` packages. cd my-app && npm install convex convex-svelte 3. Customize the convex path SvelteKit doesn't like referencing code outside of source, so customize the convex functionsDir to be under `src/`. convex.json { "functions": "src/convex/"} 4. Set up a Convex dev deployment Next, run `npx 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 `src/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. npx convex dev 5. 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} 6. 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. npx convex import --table tasks sampleData.jsonl 7. Expose a database query Add a new file `tasks.ts` 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`. src/convex/tasks.ts import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { const tasks = await ctx.db.query("tasks").collect(); return tasks.map((task) => ({ ...task, assigner: "tom" })); },}); 8. Set up Convex Create a new file `src/routes/+layout.svelte` and set up the Convex client there to make it available on every page of your app. src/routes/+layout.svelte {@render children()} 9. Display the data in your app In `src/routes/+page.svelte` use `useQuery` to subscribe your `api.tasks.get` API function. src/routes/+page.svelte {#if query.isLoading} Loading...{:else if query.error} failed to load: {query.error.toString()}{:else}
    {#each query.data as task}
  • {task.isCompleted ? '☑' : '☐'} {task.text} assigned by {task.assigner}
  • {/each}
{/if} 10. Start the app Start the app, open [http://localhost:5173](http://localhost:5173/) in a browser, and see the list of tasks. npm run dev See the [Svelte npm package documentation](https://www.npmjs.com/package/convex-svelte) . --- # Android Kotlin Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/android#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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" template2. Name it "Convex Quickstart"3. Choose min SDK as 264. 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.8.0@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 -ynpm 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. @Serializabledata 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. @Composablefun Tasks(modifier: Modifier = Modifier) { val tasks: Result> by client.subscribe>("tasks:get") .collectAsState(Result.success(listOf())) LazyColumn( modifier = modifier ) { items(tasks.getOrElse { emptyList() }) { 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. Add a `client: ConvexClient` val and update `MainActivity.onCreate` to use the `@Composable` you just created. val client: ConvexClient by lazy { ConvexClient($YOUR_CONVEX_URL) }class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { ConvexQuickstartTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Tasks( 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.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.lazy.LazyColumnimport androidx.compose.foundation.lazy.itemsimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport dev.convex.android.ConvexClientimport 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](https://docs.convex.dev/client/android) . --- # Python Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/python#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Python app. 1. Create a Python script folder Create a folder for your Python script with a virtual environment. python3 -m venv my-app/venv 2. Install the Convex client and server libraries To get started, install the `convex` npm package which enables you to write your backend. And also install the `convex` Python client library and `python-dotenv` for working with `.env` files. cd my-app && npm init -y && npm install convex && venv/bin/pip install convex python-dotenv 3. Set up a Convex dev deployment Next, run `npx 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. npx 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. npx 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, `"tasks:get"`. convex/tasks.js import { query } from "./_generated/server";export const get = query({ args: {}, handler: async ({ db }) => { return await db.query("tasks").collect(); },}); 7. Create a script to load data from Convex In a new file `main.py`, create a `ConvexClient` and use it to fetch from your `"tasks:get"` API. main.py import osfrom convex import ConvexClientfrom dotenv import load_dotenvload_dotenv(".env.local")CONVEX_URL = os.getenv("CONVEX_URL")# or you can hardcode your deployment URL instead# CONVEX_URL = "https://happy-otter-123.convex.cloud"client = ConvexClient(CONVEX_URL)print(client.query("tasks:get"))for tasks in client.subscribe("tasks:get"): print(tasks) # this loop lasts forever, ctrl-c to exit it 8. Run the script Run the script and see the serialized list of tasks. venv/bin/python -m main See the [docs on PyPI](https://pypi.org/project/convex/) for more details. --- # Node.js Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/nodejs#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Node.js project. For instructions for subscriptions instead of point-in-time queries and more project configurations (TypeScript, bundlers, CJS vs ESM) see [Node.js notes](https://docs.convex.dev/client/javascript/node) . 1. Create a new npm project Create a new directory for your Node.js project. mkdir my-project && cd my-project && npm init -y && npm pkg set type="module" 2. Install the Convex client and server library Install the `convex` package which provides a convenient interface for working with Convex from JavaScript. Also install the `dotenv` library for loading `.env` files. npm install convex dotenv 3. Set up a Convex dev deployment Next, run `npx 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. npx 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. npx 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 `script.js`, create a `ConvexHttpClient` using the URL of your development environment. script.js import { ConvexHttpClient } from "convex/browser";import { api } from "./convex/_generated/api.js";import * as dotenv from "dotenv";dotenv.config({ path: ".env.local" });const client = new ConvexHttpClient(process.env["CONVEX_URL"]);client.query(api.tasks.get).then(console.log); 8. Run the script Run the script from the same directory and see the list of tasks logged to the terminal. node script.js See the complete [Node.js documentation](https://docs.convex.dev/client/javascript/node) . --- # Nuxt Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/nuxt#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Nuxt app. This quickstart guide uses a [community-maintained](https://docs.convex.dev/client/vue/nuxt) Nuxt client for Convex. 1. Create a Nuxt application Create a Nuxt application using the `npm create nuxt@latest my-nuxt-app` command. Convex will work with any of the official modules but to follow this quickstart skip installing them for now. npm create nuxt@latest my-nuxt-app 2. Install the Convex library To get started, install the `convex` package and the `convex-nuxt` module to your Nuxt application. cd my-nuxt-app && npm install convex && npx nuxi module add convex-nuxt 3. Add the Convex URL Add the Convex URL to your `nuxt.config.ts` file. nuxt.config.ts export default defineNuxtConfig({ compatibilityDate: '2025-07-15', devtools: { enabled: true }, modules: ['convex-nuxt'], convex: { url: process.env.CONVEX_URL },}) 4. Set up a Convex dev deployment Next, run `npx 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. npx convex dev 5. 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} 6. 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. npx convex import --table tasks sampleData.jsonl 7. (optional) Define a schema Add a new file `schema.ts` in the `convex/` folder with a description of your data. This will declare the types of your data for optional typechecking with TypeScript, and it will be also enforced at runtime. convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ tasks: defineTable({ text: v.string(), isCompleted: v.boolean(), }),}); 8. Expose a database query Add a new file `tasks.ts` 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.ts import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 9. Display the data in your app In `app.vue` use `useQuery` to subscribe your `api.tasks.get` API function. app/app.vue 10. Update script to start development server By default, Convex stores environment variables in `.env.local`, and Nuxt looks for environment variables in `.env`. To use the default `npm run dev` command, update your `package.json` to use the `--dotenv .env.local` flag. package.json { "name": "nuxt-app", "private": true, "type": "module", "scripts": { "build": "nuxt build", "dev": "nuxt dev --dotenv .env.local", "generate": "nuxt generate", "preview": "nuxt preview", "postinstall": "nuxt prepare" }, "dependencies": { "convex": "^1.25.2", "convex-nuxt": "^0.1.3", "nuxt": "^3.17.6", "vue": "^3.5.17", "vue-router": "^4.5.1" }} 11. Start the app Start the app, open [http://localhost:3000](http://localhost:3000/) in a browser, and see the list of tasks. npm run dev For more examples, take a look at the [Nuxt Convex module repository](https://github.com/chris-visser/convex-nuxt) . See the complete [Nuxt npm package documentation](https://www.npmjs.com/package/convex-nuxt) . --- # Script Tag Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/script-tag#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex from script tags in HTML. 1. Create a new npm project Create a new directory for your Convex project. mkdir my-project && cd my-project && npm init -y 2. Install the Convex client and server library Install the `convex` package which provides a convenient interface for working with Convex from JavaScript. npm install convex 3. Set up a Convex dev deployment Next, run `npx 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. npx 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. npx 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. Copy the deployment URL Open the `.env.local` file and copy the `CONVEX_URL` of your development environment for use in the HTML file. 8. Add the script to your webpage In a new file `index.html`, create a `ConvexClient` using the URL of your development environment. Open this file in a web browser and you'll see it run each time the `tasks` table is modified. index.html See the complete [Script Tag documentation](https://docs.convex.dev/client/javascript/script-tag) . --- # Bun Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/bun#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Bun project. For instructions for subscriptions instead of point-in-time queries see [Bun notes](https://docs.convex.dev/client/javascript/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](https://docs.convex.dev/client/javascript/bun) . --- # TanStack Start Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/tanstack-start#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! TanStack Start is in Release Candidate [TanStack Start](https://tanstack.com/start/latest) is a new React framework currently in the Release Candidate stage. You can try it today but there might still be bug or issues. To get setup quickly with Convex and TanStack Start run **`npm create convex@latest -- -t tanstack-start`** or follow the guide below. To use Clerk with Convex and TanStack Start, see the [TanStack Start + Clerk guide](https://docs.convex.dev/client/tanstack/tanstack-start/clerk) * * * Learn how to query data from Convex in a TanStack Start site. 1. Create a TanStack Start site Create a TanStack Start app using the `create-start-app` command: npx create-start-app@latest 2. Install the Convex client and server library To get started with Convex install the `convex` package and a few React Query-related packages. npm install convex @convex-dev/react-query @tanstack/react-router-with-query @tanstack/react-query 3. Update app/routes/\_\_root.tsx Add a `QueryClient` to the router context to make React Query usable anywhere in the TanStack Start site. app/routes/\_\_root.tsx import { QueryClient } from "@tanstack/react-query";import { createRootRouteWithContext } from "@tanstack/react-router";import { Outlet, Scripts, HeadContent } from "@tanstack/react-router";import * as React from "react";export const Route = createRootRouteWithContext<{ queryClient: QueryClient;}>()({ head: () => ({ meta: [ { charSet: "utf-8", }, { name: "viewport", content: "width=device-width, initial-scale=1", }, { title: "TanStack Start Starter", }, ], }), component: RootComponent,});function RootComponent() { return ( );}function RootDocument({ children }: { children: React.ReactNode }) { return ( {children} );} 4. Update app/router.tsx Replace the file `app/router.tsx` with these contents. This creates a `ConvexClient` and a `ConvexQueryClient` and wires in a `ConvexProvider`. app/router.tsx import { createRouter } from "@tanstack/react-router";import { QueryClient } from "@tanstack/react-query";import { routerWithQueryClient } from "@tanstack/react-router-with-query";import { ConvexQueryClient } from "@convex-dev/react-query";import { ConvexProvider } from "convex/react";import { routeTree } from "./routeTree.gen";export function getRouter() { const CONVEX_URL = (import.meta as any).env.VITE_CONVEX_URL!; if (!CONVEX_URL) { console.error("missing envar VITE_CONVEX_URL"); } const convexQueryClient = new ConvexQueryClient(CONVEX_URL); const queryClient: QueryClient = new QueryClient({ defaultOptions: { queries: { queryKeyHashFn: convexQueryClient.hashFn(), queryFn: convexQueryClient.queryFn(), }, }, }); convexQueryClient.connect(queryClient); const router = routerWithQueryClient( createRouter({ routeTree, defaultPreload: "intent", context: { queryClient }, scrollRestoration: true, Wrap: ({ children }) => ( {children} ), }), queryClient, ); return router;} 5. Set up a Convex dev deployment Next, run `npx 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. npx convex dev 6. 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} 7. 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. npx convex import --table tasks sampleData.jsonl 8. Expose a database query Add a new file `tasks.ts` 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.ts import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 9. Display the data in your app Replace the file `app/routes/index.tsx` with these contents. The `useSuspenseQuery` hook renders the API function `api.tasks.get` query result on the server initially, then it updates live in the browser. app/routes/index.tsx import { convexQuery } from "@convex-dev/react-query";import { useSuspenseQuery } from "@tanstack/react-query";import { createFileRoute } from "@tanstack/react-router";import { api } from "../../convex/_generated/api";export const Route = createFileRoute("/")({ component: Home,});function Home() { const { data } = useSuspenseQuery(convexQuery(api.tasks.get, {})); return (
{data.map(({ _id, text }) => (
{text}
))}
);} 10. Start the app Start the app, open [http://localhost:3000](http://localhost:3000/) in a browser, and see the list of tasks. npm run dev For more see the [TanStack Start with Convex](https://docs.convex.dev/client/tanstack/tanstack-start/) client documentation page. --- # Rust Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/rust#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Rust app with Tokio. 1. Create a Cargo project Create a new Cargo project. cargo new my_appcd my_app 2. Install the Convex client and server libraries To get started, install the `convex` npm package which enables you to write your backend. And also install the `convex` Rust client library, the `tokio` runtime, and `dotenvy` for working with `.env` files. npm init -y && npm install convex && cargo add convex tokio dotenvy 3. Set up a Convex dev deployment Next, run `npx 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. npx 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. npx 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, `"tasks:get"`. convex/tasks.js import { query } from "./_generated/server";export const get = query({ handler: async ({ db }) => { return await db.query("tasks").collect(); },}); 7. Connect the app to your backend In the file `src/main.rs`, create a `ConvexClient` and use it to fetch from your `"tasks:get"` API. src/main.rs use std::{ collections::BTreeMap, env,};use convex::ConvexClient;#[tokio::main]async fn main() { dotenvy::from_filename(".env.local").ok(); dotenvy::dotenv().ok(); let deployment_url = env::var("CONVEX_URL").unwrap(); let mut client = ConvexClient::new(&deployment_url).await.unwrap(); let result = client.query("tasks:get", BTreeMap::new()).await.unwrap(); println!("{result:#?}");} 8. Run the app Run the app and see the serialized list of tasks. cargo run See the complete [Rust documentation](https://docs.rs/convex/latest/convex/) . --- # Convex React | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/react#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex React is the client library enabling your React application to interact with your Convex backend. It allows your frontend code to: 1. Call your [queries](https://docs.convex.dev/functions/query-functions) , [mutations](https://docs.convex.dev/functions/mutation-functions) and [actions](https://docs.convex.dev/functions/actions) 2. Upload and display files from [File Storage](https://docs.convex.dev/file-storage) 3. Authenticate users using [Authentication](https://docs.convex.dev/auth) 4. Implement full text [Search](https://docs.convex.dev/search) over your data The Convex React client is open source and available on [GitHub](https://github.com/get-convex/convex-js) . Follow the [React Quickstart](https://docs.convex.dev/quickstart/react) to get started with React using [Vite](https://vitejs.dev/) . Installation[​](https://docs.convex.dev/client/react#installation "Direct link to Installation") ------------------------------------------------------------------------------------------------- Convex React is part of the `convex` npm package: npm install convex Connecting to a backend[​](https://docs.convex.dev/client/react#connecting-to-a-backend "Direct link to Connecting to a backend") ---------------------------------------------------------------------------------------------------------------------------------- The [`ConvexReactClient`](https://docs.convex.dev/api/classes/react.ConvexReactClient) maintains a connection to your Convex backend, and is used by the React hooks described below to call your functions. First you need to create an instance of the client by giving it your backend deployment URL. See [Configuring Deployment URL](https://docs.convex.dev/client/react/deployment-urls) on how to pass in the right value: import { ConvexProvider, ConvexReactClient } from "convex/react";const convex = new ConvexReactClient("https://.convex.cloud"); And then you make the client available to your app by passing it in to a [`ConvexProvider`](https://docs.convex.dev/api/modules/react#convexprovider) wrapping your component tree: reactDOMRoot.render( ,); Fetching data[​](https://docs.convex.dev/client/react#fetching-data "Direct link to Fetching data") ---------------------------------------------------------------------------------------------------- Your React app fetches data using the [`useQuery`](https://docs.convex.dev/api/modules/react#usequery) React hook by calling your [queries](https://docs.convex.dev/functions/query-functions) via an [`api`](https://docs.convex.dev/generated-api/api#api) object. The `npx convex dev` command generates this api object for you in the `convex/_generated/api.js` module to provide better autocompletion in JavaScript and end-to-end type safety in [TypeScript](https://docs.convex.dev/understanding/best-practices/typescript) : src/App.tsx TS import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const data = useQuery(api.functions.myQuery); return data ?? "Loading...";} The `useQuery` hook returns `undefined` while the data is first loading and afterwards the return value of your query. ### Query arguments[​](https://docs.convex.dev/client/react#query-arguments "Direct link to Query arguments") Arguments to your query follow the query name: src/App.tsx TS export function App() { const a = "Hello world"; const b = 4; const data = useQuery(api.functions.myQuery, { a, b }); //...} ### Reactivity[​](https://docs.convex.dev/client/react#reactivity "Direct link to Reactivity") The `useQuery` hook makes your app automatically reactive: when the underlying data changes in your database, your component rerenders with the new query result. The first time the hook is used it creates a subscription to your backend for a given query and any arguments you pass in. When your component unmounts, the subscription is canceled. ### Consistency[​](https://docs.convex.dev/client/react#consistency "Direct link to Consistency") Convex React ensures that your application always renders a consistent view of the query results based on a single state of the underlying database. Imagine a mutation changes some data in the database, and that 2 different `useQuery` call sites rely on this data. Your app will never render in an inconsistent state where only one of the `useQuery` call sites reflects the new data. ### Paginating queries[​](https://docs.convex.dev/client/react#paginating-queries "Direct link to Paginating queries") See [Paginating within React Components](https://docs.convex.dev/database/pagination#paginating-within-react-components) . ### Skipping queries[​](https://docs.convex.dev/client/react#skipping-queries "Direct link to Skipping queries") Advanced: Loading a query conditionally With React it can be tricky to dynamically invoke a hook, because hooks cannot be placed inside conditionals or after early returns: src/App.tsx TS import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export function App() { // the URL `param` might be null const param = new URLSearchParams(window.location.search).get("param"); // ERROR! React Hook "useQuery" is called conditionally. React Hooks must // be called in the exact same order in every component render. const data = param !== null ? useQuery(api.functions.read, { param }) : null; //...} For this reason `useQuery` can be "disabled" by passing in `"skip"` instead of its arguments: src/App.tsx TS import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const param = new URLSearchParams(window.location.search).get("param"); const data = useQuery( api.functions.read, param !== null ? { param } : "skip", ); //...} When `"skip"` is used the `useQuery` doesn't talk to your backend at all and returns `undefined`. ### One-off queries[​](https://docs.convex.dev/client/react#one-off-queries "Direct link to One-off queries") Advanced: Fetching a query from a callback Sometimes you might want to read state from the database in response to a user action, for example to validate given input, without making any changes to the database. In this case you can use a one-off [`query`](https://docs.convex.dev/api/classes/react.ConvexReactClient#query) call, similarly to calling mutations and actions. The async method `query` is exposed on the `ConvexReactClient`, which you can reference in your components via the [`useConvex()`](https://docs.convex.dev/api/modules/react#useconvex) hook. src/App.tsx TS import { useConvex } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const convex = useConvex(); return ( );} Editing data[​](https://docs.convex.dev/client/react#editing-data "Direct link to Editing data") ------------------------------------------------------------------------------------------------- Your React app edits data using the [`useMutation`](https://docs.convex.dev/api/modules/react#usemutation) React hook by calling your [mutations](https://docs.convex.dev/functions/mutation-functions) . The `convex dev` command generates this api object for you in the `convex/_generated/api.js` module to provide better autocompletion in JavaScript and end-to-end type safety in [TypeScript](https://docs.convex.dev/understanding/best-practices/typescript) : src/App.tsx TS import { useMutation } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const doSomething = useMutation(api.functions.doSomething); return ;} The hook returns an `async` function which performs the call to the mutation. ### Mutation arguments[​](https://docs.convex.dev/client/react#mutation-arguments "Direct link to Mutation arguments") Arguments to your mutation are passed to the `async` function returned from `useMutation`: src/App.tsx TS export function App() { const a = "Hello world"; const b = 4; const doSomething = useMutation(api.functions.doSomething); return ;} ### Mutation response and error handling[​](https://docs.convex.dev/client/react#mutation-response-and-error-handling "Direct link to Mutation response and error handling") The mutation can optionally return a value or throw errors, which you can [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) : src/App.tsx TS export function App() { const doSomething = useMutation(api.functions.doSomething); const onClick = () => { async function callBackend() { try { const result = await doSomething(); } catch (error) { console.error(error); } console.log(result); } void callBackend(); }; return ;} Or handle as a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) : src/App.tsx TS export function App() { const doSomething = useMutation(api.functions.doSomething); const onClick = () => { doSomething() .catch((error) => { console.error(error); }) .then((result) => { console.log(result); }); }; return ;} Learn more about [Error Handling](https://docs.convex.dev/functions/error-handling/) in functions. ### Retries[​](https://docs.convex.dev/client/react#retries "Direct link to Retries") Convex React automatically retries mutations until they are confirmed to have been written to the database. The Convex backend ensures that despite multiple retries, every mutation call only executes once. Additionally, Convex React will warn users if they try to close their browser tab while there are outstanding mutations. This means that when you call a Convex mutation, you can be sure that the user's edits won't be lost. ### Optimistic updates[​](https://docs.convex.dev/client/react#optimistic-updates "Direct link to Optimistic updates") Convex queries are fully reactive, so all query results will be automatically updated after a mutation. Sometimes you may want to update the UI before the mutation changes propagate back to the client. To accomplish this, you can configure an _optimistic update_ to execute as part of your mutation. Optimistic updates are temporary, local changes to your query results which are used to make your app more responsive. See [Optimistic Updates](https://docs.convex.dev/client/react/optimistic-updates) on how to configure them. Calling third-party APIs[​](https://docs.convex.dev/client/react#calling-third-party-apis "Direct link to Calling third-party APIs") ------------------------------------------------------------------------------------------------------------------------------------- Your React app can read data, call third-party services, and write data with a single backend call using the [`useAction`](https://docs.convex.dev/api/modules/react#useaction) React hook by calling your [actions](https://docs.convex.dev/functions/actions) . Like `useQuery` and `useMutation`, this hook is used with the `api` object generated for you in the `convex/_generated/api.js` module to provide better autocompletion in JavaScript and end-to-end type safety in [TypeScript](https://docs.convex.dev/understanding/best-practices/typescript) : src/App.tsx TS import { useAction } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const doSomeAction = useAction(api.functions.doSomeAction); return ;} The hook returns an `async` function which performs the call to the action. ### Action arguments[​](https://docs.convex.dev/client/react#action-arguments "Direct link to Action arguments") Action arguments work exactly the same as [mutation arguments](https://docs.convex.dev/client/react#mutation-arguments) . ### Action response and error handling[​](https://docs.convex.dev/client/react#action-response-and-error-handling "Direct link to Action response and error handling") Action response and error handling work exactly the same as [mutation response and error handling](https://docs.convex.dev/client/react#mutation-response-and-error-handling) . Actions do not support automatic retries or optimistic updates. Under the hood[​](https://docs.convex.dev/client/react#under-the-hood "Direct link to Under the hood") ------------------------------------------------------------------------------------------------------- The [`ConvexReactClient`](https://docs.convex.dev/api/classes/react.ConvexReactClient) connects to your Convex deployment by creating a [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) . The WebSocket provides a 2-way communication channel over TCP. This allows Convex to push new query results reactively to the client without the client needing to poll for updates. If the internet connection drops, the client will handle reconnecting and re-establishing the Convex session automatically. * [Installation](https://docs.convex.dev/client/react#installation) * [Connecting to a backend](https://docs.convex.dev/client/react#connecting-to-a-backend) * [Fetching data](https://docs.convex.dev/client/react#fetching-data) * [Query arguments](https://docs.convex.dev/client/react#query-arguments) * [Reactivity](https://docs.convex.dev/client/react#reactivity) * [Consistency](https://docs.convex.dev/client/react#consistency) * [Paginating queries](https://docs.convex.dev/client/react#paginating-queries) * [Skipping queries](https://docs.convex.dev/client/react#skipping-queries) * [One-off queries](https://docs.convex.dev/client/react#one-off-queries) * [Editing data](https://docs.convex.dev/client/react#editing-data) * [Mutation arguments](https://docs.convex.dev/client/react#mutation-arguments) * [Mutation response and error handling](https://docs.convex.dev/client/react#mutation-response-and-error-handling) * [Retries](https://docs.convex.dev/client/react#retries) * [Optimistic updates](https://docs.convex.dev/client/react#optimistic-updates) * [Calling third-party APIs](https://docs.convex.dev/client/react#calling-third-party-apis) * [Action arguments](https://docs.convex.dev/client/react#action-arguments) * [Action response and error handling](https://docs.convex.dev/client/react#action-response-and-error-handling) * [Under the hood](https://docs.convex.dev/client/react#under-the-hood) --- # Generated Code | Convex Developer Hub [Skip to main content](https://docs.convex.dev/generated-api/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex uses code generation to create code that is specific to your app's data model and API. Convex generates JavaScript files (`.js`) with TypeScript type definitions (`.d.ts`). Code generation isn't required to use Convex, but using the generated code will give you more better autocompletion in your editor and more type safety if you're using TypeScript. To generate the code, run: npx convex dev This creates a `convex/_generated` directory that contains: * [`api.js` and `api.d.ts`](https://docs.convex.dev/generated-api/api) * [`dataModel.d.ts`](https://docs.convex.dev/generated-api/data-model) * [`server.js` and `server.d.ts`](https://docs.convex.dev/generated-api/server) --- # Vue Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/vue#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Learn how to query data from Convex in a Vue app. This quickstart guide uses a [community-maintained](https://docs.convex.dev/client/vue) Vue client for Convex. 1. Create a Vue site Create a Vue site using the `npm create vue@latest my-vue-app` command. Convex will work with any set of options but to follow this quickstart most closely choose: * Yes to "Add TypeScript?" * No to everything else npm create vue@latest my-vue-app 2. Install the Convex library To get started, install the `convex` package. cd my-vue-app && npm install convex convex-vue 3. Set up a Convex dev deployment Next, run `npx 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. npx 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. npx convex import --table tasks sampleData.jsonl 6. Expose a database query Add a new file `tasks.ts` 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.ts import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 7. Wire up the ConvexProvider In `src/main.ts` set up the Convex client there to make it available on every page of your app. src/main.ts import { convexVue } from 'convex-vue'import { createApp } from 'vue'import App from './App.vue'const app = createApp(App)app.use(convexVue, { url: import.meta.env.VITE_CONVEX_URL,})app.mount('#app') 8. Display the data in your app In `src/App.vue` use `useQuery` to subscribe your `api.tasks.get` API function. src/App.vue 9. Start the app Start the app, open [http://localhost:5173](http://localhost:5173/) in a browser, and see the list of tasks. npm run dev See the complete [Vue npm package documentation](https://www.npmjs.com/package/convex-vue) . --- # JavaScript | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/javascript#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex applications can be accessed from Node.js or any JavaScript runtime that implements [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) or [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) . The reactive [Convex Client](https://docs.convex.dev/api/classes/browser.ConvexClient) allows web applications and long-running Node.js servers to subscribe to updates on Convex queries, while the [Convex HTTP client](https://docs.convex.dev/api/classes/browser.ConvexHttpClient) is typically used for server-side rendering, migrations, administrative scripts, and serverless functions to run queries at a single point in time. If you're using React, see the dedicated [`ConvexReactClient`](https://docs.convex.dev/api/classes/browser.ConvexClient) described in [React](https://docs.convex.dev/client/react) . Convex Client[​](https://docs.convex.dev/client/javascript#convex-client "Direct link to Convex Client") --------------------------------------------------------------------------------------------------------- The [`ConvexClient`](https://docs.convex.dev/api/classes/browser.ConvexClient) provides subscriptions to queries in Node.js and any JavaScript environment that supports WebSockets. script.ts TS import { ConvexClient } from "convex/browser";import { api } from "../convex/_generated/api";const client = new ConvexClient(process.env.CONVEX_URL!);// subscribe to query resultsclient.onUpdate(api.messages.listAll, {}, (messages) => console.log(messages.map((msg) => msg.body)),);// execute a mutationfunction hello() { client.mutation(api.messages.sendAnon, { body: `hello at ${new Date()}`, });} The Convex client is open source and available on [GitHub](https://github.com/get-convex/convex-js) . See the [Script Tag Quickstart](https://docs.convex.dev/quickstart/script-tag) to get started. HTTP client[​](https://docs.convex.dev/client/javascript#http-client "Direct link to HTTP client") --------------------------------------------------------------------------------------------------- The [`ConvexHttpClient`](https://docs.convex.dev/api/classes/browser.ConvexHttpClient) works in the browser, Node.js, and any JavaScript environment with `fetch`. See the [Node.js Quickstart](https://docs.convex.dev/quickstart/nodejs) . script.ts TS import { ConvexHttpClient } from "convex/browser";import { api } from "./convex/_generated/api";const client = new ConvexHttpClient(process.env["CONVEX_URL"]);// either thisconst count = await client.query(api.counter.get);// or thisclient.query(api.counter.get).then((count) => console.log(count)); Using Convex without generated `convex/_generated/api.js`[​](https://docs.convex.dev/client/javascript#using-convex-without-generated-convex_generatedapijs "Direct link to using-convex-without-generated-convex_generatedapijs") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- If the source code for your Convex function isn't located in the same project or in the same monorepos you can use the untyped `api` object called `anyApi`. script.ts TS import { ConvexClient } from "convex/browser";import { anyApi } from "convex/server";const CONVEX_URL = "http://happy-otter-123";const client = new ConvexClient(CONVEX_URL);client.onUpdate(anyApi.messages.list, {}, (messages) => console.log(messages.map((msg) => msg.body)),);setInterval( () => client.mutation(anyApi.messages.send, { body: `hello at ${new Date()}`, author: "me", }), 5000,); * [Convex Client](https://docs.convex.dev/client/javascript#convex-client) * [HTTP client](https://docs.convex.dev/client/javascript#http-client) * [Using Convex without generated `convex/_generated/api.js`](https://docs.convex.dev/client/javascript#using-convex-without-generated-convex_generatedapijs) --- # Deployment API | Convex Developer Hub [Skip to main content](https://docs.convex.dev/deployment-api#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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](https://docs.convex.dev/functions/http-actions) and a static [public HTTP API](https://docs.convex.dev/http-api/) . Deployments also provide private endpoints only for the administrators of that deployment: * [Streaming export API](https://docs.convex.dev/streaming-export-api) * [Streaming import API](https://docs.convex.dev/streaming-import-api) * [Platform APIs](https://docs.convex.dev/deployment-platform-api) A client wrapping the Platform APIs for deployments is available in the [`@convex-dev/platform` package](https://www.npmjs.com/package/@convex-dev/platform) . --- # File Storage | Convex Developer Hub [Skip to main content](https://docs.convex.dev/file-storage#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! File Storage makes it easy to implement file upload in your app, store files from and send files to third-party APIs, and to serve dynamic files to your users. All file types are supported. * [Upload](https://docs.convex.dev/file-storage/upload-files) files to store them in Convex and reference them in your database documents * [Store](https://docs.convex.dev/file-storage/store-files) files generated or fetched from third-party APIs * [Serve](https://docs.convex.dev/file-storage/serve-files) files via URL * [Delete](https://docs.convex.dev/file-storage/delete-files) files stored in Convex * Access file [metadata](https://docs.convex.dev/file-storage/file-metadata) You can manage your stored files on the [dashboard](https://docs.convex.dev/dashboard/deployments/file-storage) . **Examples:** [File Storage with HTTP Actions](https://github.com/get-convex/convex-demos/tree/main/file-storage-with-http) , [File Storage with Queries and Mutations](https://github.com/get-convex/convex-demos/tree/main/file-storage) --- # Next.js Quickstart | Convex Developer Hub [Skip to main content](https://docs.convex.dev/quickstart/nextjs#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex + Next.js Convex is an all-in-one backend and database that integrates quickly and easily with Next.js. Once you've gotten started, see how to set up [hosting](https://docs.convex.dev/production/hosting/) , [server rendering](https://docs.convex.dev/client/nextjs/app-router/server-rendering) , and [auth](https://docs.convex.dev/client/nextjs/) . To get setup quickly with Convex and Next.js run **`npm create convex@latest`** or follow the guide below. * * * Learn how to query data from Convex in a Next.js app using the App Router and TypeScript Alternatively see the [Pages Router](https://docs.convex.dev/client/nextjs/pages-router/quickstart) version of this quickstart. 1. Create a Next.js app Create a Next.js app using the `npx create-next-app` command. Choose the default option for every prompt (hit Enter). npx create-next-app@latest my-app 2. Install the Convex client and server library To get started, install the `convex` package. Navigate to your app and install `convex`. cd my-app && npm install convex 3. Set up a Convex dev deployment Next, run `npx 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. npx 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 Use the [`import`](https://docs.convex.dev/database/import-export/import) command to add a `tasks` table with the sample data into your Convex database. npx convex import --table tasks sampleData.jsonl 6. Expose a database query In the `convex/` folder, add a new file `tasks.ts` 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.ts TS import { query } from "./_generated/server";export const get = query({ args: {}, handler: async (ctx) => { return await ctx.db.query("tasks").collect(); },}); 7. Create a client component for the Convex provider For `` to work on the client, `ConvexReactClient` must be passed to it. In the `app/` folder, add a new file `ConvexClientProvider.tsx` with the following code. This creates a client component that wraps `` and passes it the ``. app/ConvexClientProvider.tsx TS "use client";import { ConvexProvider, ConvexReactClient } from "convex/react";import { ReactNode } from "react";const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);export function ConvexClientProvider({ children }: { children: ReactNode }) { return {children};} 8. Wire up the ConvexClientProvider In `app/layout.tsx`, wrap the children of the `body` element with the ``. app/layout.tsx TS import type { Metadata } from "next";import { Geist, Geist_Mono } from "next/font/google";import "./globals.css";import { ConvexClientProvider } from "./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",};export default function RootLayout({ children,}: Readonly<{ children: React.ReactNode;}>) { return ( {children} );} 9. Display the data in your app In `app/page.tsx`, use the `useQuery()` hook to fetch from your `api.tasks.get` API function. app/page.tsx TS "use client";import Image from "next/image";import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export default function Home() { const tasks = useQuery(api.tasks.get); return (
{tasks?.map(({ _id, text }) =>
{text}
)}
);} 10. Start the app Run your Next.js development server, open [http://localhost:3000](http://localhost:3000/) in a browser, and see the list of tasks. npm run dev See the complete [Next.js documentation](https://docs.convex.dev/client/nextjs/app-router/) . --- # Authentication | Convex Developer Hub [Skip to main content](https://docs.convex.dev/auth#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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[​](https://docs.convex.dev/auth#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](https://docs.convex.dev/auth/clerk) has great Next.js and React Native support * [WorkOS AuthKit](https://docs.convex.dev/auth/authkit/) is built for B2B apps and free for up to 1M users * [Auth0](https://docs.convex.dev/auth/auth0) is more established with more bells and whistles * [Custom Auth Integration](https://docs.convex.dev/auth/advanced/custom-auth) 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](https://docs.convex.dev/auth/functions-auth) and storing user information in the [Database](https://docs.convex.dev/auth/database-auth) . The Convex Auth Library[​](https://docs.convex.dev/auth#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](https://docs.convex.dev/auth/convex-auth) 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](https://docs.convex.dev/production/state/#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[​](https://docs.convex.dev/auth#debugging "Direct link to Debugging") -------------------------------------------------------------------------------- If you run into issues consult the [Debugging](https://docs.convex.dev/auth/debug) guide. Service Authentication[​](https://docs.convex.dev/auth#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[​](https://docs.convex.dev/auth#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](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # convex | Convex Developer Hub [Skip to main content](https://docs.convex.dev/api/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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. --- # Deploying Your App to Production | Convex Developer Hub [Skip to main content](https://docs.convex.dev/production#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex is built to serve live, production app traffic. Here we cover how to deploy and maintain a production version of your app. Project management[​](https://docs.convex.dev/production#project-management "Direct link to Project management") ----------------------------------------------------------------------------------------------------------------- When you sign up for Convex, a Convex team is created for you. You can [create more teams from the dashboard](https://docs.convex.dev/dashboard/teams/teams) and add other people to them as members. You can upgrade your team to the [Starter](https://www.convex.dev/pricing) plan to pay as you go or the [Professional](https://www.convex.dev/pricing) for additional features, higher built-in limits, 24h support, and discounted usage-based pricing. Each team can have multiple projects. When you run `npx convex dev` for the first time, a project is created for you automatically. You can also create a project from the dashboard. Every project has one shared production deployment and one development deployment per team member. This allows each team member to make and test changes independently before they are deployed to the production deployment. Usually all deployments belonging to a single project run the same code base (or a version of it), but Convex doesn't enforce this. You can also run the same code base on multiple different prod deployments belonging to different projects, see [staging](https://docs.convex.dev/production#staging-environment) below. Deploying to production[​](https://docs.convex.dev/production#deploying-to-production "Direct link to Deploying to production") -------------------------------------------------------------------------------------------------------------------------------- Your Convex deployments run your backend logic and in most cases you will also develop a client that uses the backend. If your client is a web app, follow the [Hosting and Deployment](https://docs.convex.dev/production/hosting/) guide, to learn how to deploy your client and your Convex backend together. You can also deploy your backend on its own. Check out the [Project Configuration](https://docs.convex.dev/production/project-configuration) page to learn more. Staging environment[​](https://docs.convex.dev/production#staging-environment "Direct link to Staging environment") -------------------------------------------------------------------------------------------------------------------- With Convex [preview deployments](https://docs.convex.dev/production/hosting/preview-deployments) your team can test out changes before deploying them to production. If you need a more permanent staging environment, you can use a separate Convex project, and deploy to it by setting the `CONVEX_DEPLOY_KEY` environment variable when running [`npx convex deploy`](https://docs.convex.dev/cli#deploy-convex-functions-to-production) . Typical team development workflow[​](https://docs.convex.dev/production#typical-team-development-workflow "Direct link to Typical team development workflow") -------------------------------------------------------------------------------------------------------------------------------------------------------------- Teams developing on Convex usually follow this workflow: 1. If this is the team's first project, one team member creates a team on the dashboard. 2. One team member creates a project by running `npx convex dev`, perhaps starting with a [quickstart](https://docs.convex.dev/quickstarts) or a [template](https://www.convex.dev/templates) . 3. The team member creates a Git repository from the initial code and shares it with their team (via GitHub, GitLab etc.). 4. Other team members pull the codebase, and get their own dev deployments by running `npx convex dev`. 5. All team members can make backend changes and test them out with their individual dev deployments. When a change is ready the team member opens a pull-request (or commits to a shared branch). * [Backup / Restore](https://docs.convex.dev/database/backup-restore) can be used to populate a dev deployment with data from a prod deployment. * [Data import](https://docs.convex.dev/database/import-export/import) can be used to populate a dev deployment with synthetic seed data. * Members of a team with the [Pro plan](https://www.convex.dev/pricing) can get separate [preview deployments](https://docs.convex.dev/production/hosting/preview-deployments) to test each other's pull-requests. 6. Deployment to production can happen [automatically](https://docs.convex.dev/production/hosting/) when changes get merged to the designated branch (say `main`). * Alternatively one of the team members can deploy to production manually by running `npx convex deploy`. ### Making safe changes[​](https://docs.convex.dev/production#making-safe-changes "Direct link to Making safe changes") Especially if your app is live you want to make sure that changes you make to your Convex codebase do not break it. Some unsafe changes are handled and caught by Convex, but others you need handle yourself. 1. **Schema must always match existing data.** Convex enforces this constraint. You cannot push a schema to a deployment with existing data that doesn't match it, unless you turn off schema enforcement. In general it safe to: 1. Add new tables to the schema. 2. Add an `optional` field to an existing table's schema, set the field on all documents in the table, and then make the field required. 3. Mark an existing field as `optional`, remove the field from all documents, and then remove the field. 4. Mark an existing field as a `union` of the existing type and a new type, modify the field on all documents to match the new type, and then change the type to the new type. 2. **Functions should be backwards compatible.** Even if your only client is a website, and you deploy it together with your backend, your users might still be running the old version of your website when your backend changes. Therefore you should make your functions backwards compatible until you are OK to break old clients. In general it is safe to: 1. Add new functions. 2. Add an `optional` named argument to an existing function. 3. Mark an existing named argument as `optional`. 4. Mark an existing named argument as a `union` of the existing type and a new type. 5. Change the behavior of the function in such a way that given the arguments from an old client its behavior will still be acceptable to the old client. 3. **Scheduled functions should be backwards compatible.** When you schedule a function to run in the future, you provide the argument values it will receive. Whenever a function runs, it always runs its currently deployed version. If you change the function between the time it was scheduled and the time it runs, you must ensure the new version will behave acceptably given the old arguments. Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) --- # CLI | Convex Developer Hub [Skip to main content](https://docs.convex.dev/cli#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page 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[​](https://docs.convex.dev/cli#configure "Direct link to Configure") ------------------------------------------------------------------------------- ### Create a new project[​](https://docs.convex.dev/cli#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[​](https://docs.convex.dev/cli#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[​](https://docs.convex.dev/cli#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[​](https://docs.convex.dev/cli#develop "Direct link to Develop") ------------------------------------------------------------------------- ### Run the Convex dev server[​](https://docs.convex.dev/cli#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](https://docs.convex.dev/functions) or the [schema](https://docs.convex.dev/database/schemas) , the new versions are pushed to your dev deployment and the [generated types](https://docs.convex.dev/generated-api/) 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](https://docs.convex.dev/cli/local-deployments) for development. ### Open the dashboard[​](https://docs.convex.dev/cli#open-the-dashboard "Direct link to Open the dashboard") npx convex dashboard Open the [Convex dashboard](https://docs.convex.dev/dashboard) . ### Open the docs[​](https://docs.convex.dev/cli#open-the-docs "Direct link to Open the docs") npx convex docs Get back to these docs! ### Run Convex functions[​](https://docs.convex.dev/cli#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[​](https://docs.convex.dev/cli#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 continuouslynpx convex dev --tail-logs always# Pause logs during deploys to see sync issues (default)npx convex dev# Don't display logs while developingnpx convex dev --tail-logs disable# Tail logs without deployingnpx convex logs Use `--prod` with `npx convex logs` to tail the prod deployment logs instead. ### Import data from a file[​](https://docs.convex.dev/cli#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](https://docs.convex.dev/database/import-export/import) . ### Export data to a file[​](https://docs.convex.dev/cli#export-data-to-a-file "Direct link to Export data to a file") npx convex export --path npx convex export --path .zipnpx convex export --include-file-storage --path See description and use-cases: [data export](https://docs.convex.dev/database/import-export/export) . ### Display data from tables[​](https://docs.convex.dev/cli#display-data-from-tables "Direct link to Display data from tables") npx convex data # lists tablesnpx convex data Display a simple view of the [dashboard data page](https://docs.convex.dev/dashboard/deployments/data) 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](https://docs.convex.dev/database/reading-data/) . The `npx convex data
` command works with [system tables](https://docs.convex.dev/database/advanced/system-tables) , such as `_storage`, in addition to your own tables. ### Show deployment health insights[​](https://docs.convex.dev/cli#show-deployment-health-insights "Direct link to Show deployment health insights") npx convex insightsnpx convex insights --detailsnpx convex insights --prod Show health insights for a Convex deployment over the last 72 hours. Reports [OCC (Optimistic Concurrency Control)](https://docs.convex.dev/error#optimistic-concurrency-control) conflicts and resource limit issues that may indicate performance problems. Add `--details` to include recent events for each insight. Use `--prod` to check the production deployment, `--preview-name ` for a preview deployment, or `--deployment-name ` for a specific deployment. ### Read and write environment variables[​](https://docs.convex.dev/cli#read-and-write-environment-variables "Direct link to Read and write environment variables") npx convex env listnpx convex env get npx convex env set npx convex env remove See and update the [deployment environment variables](https://docs.convex.dev/production/environment-variables) . You can alternatively use the [settings page on the dashboard](https://docs.convex.dev/dashboard/deployments/deployment-settings#environment-variables) . Tip: to avoid secrets from ending up in your terminal shell history, you can pass the value via stdin, from a file, or interactively. Useful commands: # Set a value interactivelynpx convex env set API_KEY# Set from MacOS clipboardpbpaste | npx convex env set API_KEY# Windows PowerShellGet-Clipboard | npx convex env set API_KEY# Read a value from a filenpx convex env set PUBLIC_KEY --from-file key.pub# Set multiple variables via a filenpx convex env set --from-file .env.defaults# Save environment variables to a filenpx convex env list >> .env.convex # appendnpx convex env list > .env.convex # overwrite# Update values after editing them locally:npx convex env set --force < .env.convex Note: to set variables on your production deployment, pass `--prod`. Deploy[​](https://docs.convex.dev/cli#deploy "Direct link to Deploy") ---------------------------------------------------------------------- ### Deploy Convex functions to production[​](https://docs.convex.dev/cli#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](https://docs.convex.dev/generated-api/) in the `convex/_generated` directory. 4. Bundle your Convex functions and their dependencies. 5. Push your functions, [indexes](https://docs.convex.dev/database/reading-data/indexes/) , and [schema](https://docs.convex.dev/database/schemas) to production. Once this command succeeds the new functions will be available immediately. ### Deploy Convex functions to a [preview deployment](https://docs.convex.dev/production/hosting/preview-deployments) [​](https://docs.convex.dev/cli#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](https://docs.convex.dev/cli/deploy-key-types#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](https://docs.convex.dev/generated-api/) in the `convex/_generated` directory. 5. Bundle your Convex functions and their dependencies. 6. Push your functions, [indexes](https://docs.convex.dev/database/reading-data/indexes/) , and [schema](https://docs.convex.dev/database/schemas) 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](https://docs.convex.dev/production/hosting/vercel#preview-deployments) or [Netlify](https://docs.convex.dev/production/hosting/netlify#deploy-previews) hosting guide for setting up frontend and backend previews together. ### Update generated code[​](https://docs.convex.dev/cli#update-generated-code "Direct link to Update generated code") npx convex codegen The [generated code](https://docs.convex.dev/generated-api/) 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. * [Configure](https://docs.convex.dev/cli#configure) * [Create a new project](https://docs.convex.dev/cli#create-a-new-project) * [Recreate project configuration](https://docs.convex.dev/cli#recreate-project-configuration) * [Log out](https://docs.convex.dev/cli#log-out) * [Develop](https://docs.convex.dev/cli#develop) * [Run the Convex dev server](https://docs.convex.dev/cli#run-the-convex-dev-server) * [Open the dashboard](https://docs.convex.dev/cli#open-the-dashboard) * [Open the docs](https://docs.convex.dev/cli#open-the-docs) * [Run Convex functions](https://docs.convex.dev/cli#run-convex-functions) * [Tail deployment logs](https://docs.convex.dev/cli#tail-deployment-logs) * [Import data from a file](https://docs.convex.dev/cli#import-data-from-a-file) * [Export data to a file](https://docs.convex.dev/cli#export-data-to-a-file) * [Display data from tables](https://docs.convex.dev/cli#display-data-from-tables) * [Show deployment health insights](https://docs.convex.dev/cli#show-deployment-health-insights) * [Read and write environment variables](https://docs.convex.dev/cli#read-and-write-environment-variables) * [Deploy](https://docs.convex.dev/cli#deploy) * [Deploy Convex functions to production](https://docs.convex.dev/cli#deploy-convex-functions-to-production) * [Deploy Convex functions to a preview deployment](https://docs.convex.dev/cli#deploy-convex-functions-to-a-preview-deployment) * [Update generated code](https://docs.convex.dev/cli#update-generated-code) --- # Management API | Convex Developer Hub [Skip to main content](https://docs.convex.dev/management-api#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! info The Convex Management API is openly available in Beta. Please contact [platforms@convex.dev](mailto:platforms@convex.dev) if your use case requires additional capabilities. You can provision and manage Convex projects and deployments with the Management API. A client wrapping the Management API is available in the [`@convex-dev/platform` package](https://www.npmjs.com/package/@convex-dev/platform) . Authorization[​](https://docs.convex.dev/management-api#authorization "Direct link to Authorization") ------------------------------------------------------------------------------------------------------ The Management API uses a Bearer token Authorization header. const token = "ey...0=";const response = await fetch( "https://api.convex.dev/v1/teams/41/list_projects", { headers: { Authorization: `Bearer ${token}`, }, },);console.log(await response.json()); [Team Access Tokens](https://docs.convex.dev/platform-apis#managing-your-own-projects) and [OAuth Application Tokens](https://docs.convex.dev/platform-apis/oauth-applications) can be used in Bearer tokens 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. Required Parameters[​](https://docs.convex.dev/management-api#required-parameters "Direct link to Required Parameters") ------------------------------------------------------------------------------------------------------------------------ Most Management APIs require a team ID or project ID. When creating a Team Access Token the team ID will be available in the Convex dashboard. OAuth applications may request the team (or project, if using project-scoped tokens) ID by calling the [Token Details](https://docs.convex.dev/management-api/get-token-details) endpoint. When using a team token, projects will be assigned IDs upon creation. The [List Projects](https://docs.convex.dev/management-api/list-projects) endpoint may also be used to retrieve the ID for a project. Responses[​](https://docs.convex.dev/management-api#responses "Direct link to Responses") ------------------------------------------------------------------------------------------ All API responses are in JSON format. Endpoints[​](https://docs.convex.dev/management-api#endpoints "Direct link to Endpoints") ------------------------------------------------------------------------------------------ An OpenAPI spec for the Management API is available at [https://api.convex.dev/v1/openapi.json](https://api.convex.dev/v1/openapi.json) . --- # AI Agents | Convex Developer Hub [Skip to main content](https://docs.convex.dev/agents#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Building AI Agents with Convex[​](https://docs.convex.dev/agents#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[​](https://docs.convex.dev/agents#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) ### Core Concepts[​](https://docs.convex.dev/agents#core-concepts "Direct link to Core Concepts") * Agents organize LLM prompting with associated models, prompts, and [Tools](https://docs.convex.dev/agents/tools) . 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](https://docs.convex.dev/agents/threads) persist [messages](https://docs.convex.dev/agents/messages) and can be shared by multiple users and agents (including [human agents](https://docs.convex.dev/agents/human-agents) ). * [Conversation context](https://docs.convex.dev/agents/context) is automatically included in each LLM call, including built-in hybrid vector/text search for messages. ### Advanced Features[​](https://docs.convex.dev/agents#advanced-features "Direct link to Advanced Features") * [Workflows](https://docs.convex.dev/agents/workflows) allow building multi-step operations that can span agents, users, durably and reliably. * [RAG](https://docs.convex.dev/agents/rag) 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](https://docs.convex.dev/agents/files) can be used in the chat history with automatic saving to [file storage](https://docs.convex.dev/file-storage) . ### Debugging and Tracking[​](https://docs.convex.dev/agents#debugging-and-tracking "Direct link to Debugging and Tracking") * [Debugging](https://docs.convex.dev/agents/debugging) is supported, including the [agent playground](https://docs.convex.dev/agents/playground) where you can inspect all metadata and iterate on prompts and context settings. * [Usage tracking](https://docs.convex.dev/agents/usage-tracking) enables usage billing for users and teams. * [Rate limiting](https://docs.convex.dev/agents/rate-limiting) 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\ ----------------------](https://docs.convex.dev/agents/getting-started) 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 agentconst 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; },}); * [Building AI Agents with Convex](https://docs.convex.dev/agents#building-ai-agents-with-convex) * [Agent Component](https://docs.convex.dev/agents#agent-component) * [Core Concepts](https://docs.convex.dev/agents#core-concepts) * [Advanced Features](https://docs.convex.dev/agents#advanced-features) * [Debugging and Tracking](https://docs.convex.dev/agents#debugging-and-tracking) --- # Convex Tutorial: Scaling Your App | Convex Developer Hub [Skip to main content](https://docs.convex.dev/tutorial/scale#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex was designed from the ground up for scale. In the previous section we already talked about how keeping your actions small and most of your logic in queries and mutations are crucial to building fast scalable backends. Let's talk about a few other ways to keep your app fast and scalable. Indexed queries[​](https://docs.convex.dev/tutorial/scale#indexed-queries "Direct link to Indexed queries") ------------------------------------------------------------------------------------------------------------ Indexes tell the database to create a lookup structure to make it really fast to filter data. If, in our chat app we wanted to build a way to look up `messages` from just one user, we'd tell Convex to index the `user` field in the `messages` table and write the query with the `withIndex` syntax. [Learn how to use indexes](https://docs.convex.dev/database/reading-data/indexes/) . Too many writes on the same document[​](https://docs.convex.dev/tutorial/scale#too-many-writes-on-the-same-document "Direct link to Too many writes on the same document") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Let's say you decide to show a counter in your app. You may write a mutation that reads a number field, adds 1, and updates the same field in the database. At some point, this pattern may cause an [optimistic concurrency control conflict](https://docs.convex.dev/error#1) . That means that the database isn't able to handle updating the document that fast. All databases have trouble with this sort of pattern. There are a [few ways to deal with this](https://docs.convex.dev/error#remediation) , including building something called a sharded counter... But before you go learn advanced scaling techniques on your own, there is a better way with [Convex Components](https://docs.convex.dev/components) . Scaling best practices with Convex Components[​](https://docs.convex.dev/tutorial/scale#scaling-best-practices-with-convex-components "Direct link to Scaling best practices with Convex Components") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ In the case of the counter above, the Convex team has already built a [scalable counter](https://www.convex.dev/components/sharded-counter) Convex component for you to use. Convex Components are deployed along with your Convex backend but have their own tables and functions. As you build more complicated features like [AI agents](https://docs.convex.dev/agents) , [workflows](https://www.convex.dev/components/workflow) , [leaderboards](https://www.convex.dev/components/aggregate) , [feature flags](https://www.convex.dev/components/launchdarkly) or [rate limiters](https://www.convex.dev/components/rate-limiter) , you may find that there is already a Convex Component that solves this problem. [Learn more about Convex Components here](https://docs.convex.dev/components) . [Components directory\ --------------------](https://www.convex.dev/components) Wrap up[​](https://docs.convex.dev/tutorial/scale#wrap-up "Direct link to Wrap up") ------------------------------------------------------------------------------------ We've covered a lot of ground in this tutorial. We started by [building a chat app](https://docs.convex.dev/tutorial/) with queries, mutations and the database that form the fundamental building blocks of the Convex sync engine. We then called an [external API](https://docs.convex.dev/tutorial/actions) from our backend, using the scheduler to coordinate the work. Finally, we learned that [Convex Components](https://docs.convex.dev/components) give you scaling best practices in neat packages. If you are looking for more tips, read our [best practices](https://docs.convex.dev/understanding/best-practices/) and join the [community](https://www.convex.dev/community) . Convex enables you to build your MVP fast and then scale to new heights. Many great products have already done so. You're in good company. --- # Mutations | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/mutation-functions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Mutations insert, update and remove data from the database, check authentication or perform other business logic, and optionally return a response to the client application. This is an example mutation, taking in named arguments, writing data to the database and returning a result: convex/myFunctions.ts TS import { mutation } from "./_generated/server";import { v } from "convex/values";// Create a new task with the given textexport const createTask = mutation({ args: { text: v.string() }, handler: async (ctx, args) => { const newTaskId = await ctx.db.insert("tasks", { text: args.text }); return newTaskId; },}); Read on to understand how to build mutations yourself. Mutation names[​](https://docs.convex.dev/functions/mutation-functions#mutation-names "Direct link to Mutation names") ----------------------------------------------------------------------------------------------------------------------- Mutations follow the same naming rules as queries, see [Query names](https://docs.convex.dev/functions/query-functions#query-names) . Queries and mutations can be defined in the same file when using named exports. The `mutation` constructor[​](https://docs.convex.dev/functions/mutation-functions#the-mutation-constructor "Direct link to the-mutation-constructor") ------------------------------------------------------------------------------------------------------------------------------------------------------- To declare a mutation in Convex use the `mutation` constructor function. Pass it an object with a `handler` function, which performs the mutation: convex/myFunctions.ts TS import { mutation } from "./_generated/server";export const mutateSomething = mutation({ args: {}, handler: () => { // implementation will be here },}); Unlike a query, a mutation can but does not have to return a value. ### Mutation arguments[​](https://docs.convex.dev/functions/mutation-functions#mutation-arguments "Direct link to Mutation arguments") Just like queries, mutations accept named arguments, and the argument values are accessible as fields of the second parameter of the `handler` function: convex/myFunctions.ts TS import { mutation } from "./_generated/server";export const mutateSomething = mutation({ handler: (_, args: { a: number; b: number }) => { // do something with `args.a` and `args.b` // optionally return a value return "success"; },}); Arguments and responses are automatically serialized and deserialized, and you can pass and return most value-like JavaScript data to and from your mutation. To both declare the types of arguments and to validate them, add an `args` object using `v` validators: 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) => { // do something with `args.a` and `args.b` },}); See [argument validation](https://docs.convex.dev/functions/validation) for the full list of supported types and validators. The first parameter to the handler function is reserved for the mutation context. ### Mutation responses[​](https://docs.convex.dev/functions/mutation-functions#mutation-responses "Direct link to Mutation responses") Queries can return values of any supported [Convex type](https://docs.convex.dev/functions/validation) which will be automatically serialized and deserialized. Mutations can also return `undefined`, which is not a valid Convex value. When a mutation returns `undefined` **it is translated to `null`** on the client. ### Mutation context[​](https://docs.convex.dev/functions/mutation-functions#mutation-context "Direct link to Mutation context") The `mutation` constructor enables writing data to the database, and other Convex features by passing a [MutationCtx](https://docs.convex.dev/generated-api/server#mutationctx) object to the handler function as the first parameter: 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: (ctx, args) => { // Do something with `ctx` },}); Which part of the mutation context is used depends on what your mutation needs to do: * To read from and write to the database use the `db` field. Note that we make the handler function an `async` function so we can `await` the promise returned by `db.insert()`: convex/myFunctions.ts TS import { mutation } from "./_generated/server";import { v } from "convex/values";export const addItem = mutation({ args: { text: v.string() }, handler: async (ctx, args) => { await ctx.db.insert("tasks", { text: args.text }); },}); Read on about [Writing Data](https://docs.convex.dev/database/writing-data) . * To generate upload URLs for storing files use the `storage` field. Read on about [File Storage](https://docs.convex.dev/file-storage) . * To check user authentication use the `auth` field. Read on about [Authentication](https://docs.convex.dev/auth) . * To schedule functions to run in the future, use the `scheduler` field. Read on about [Scheduled Functions](https://docs.convex.dev/scheduling/scheduled-functions) . Splitting up mutation code via helpers[​](https://docs.convex.dev/functions/mutation-functions#splitting-up-mutation-code-via-helpers "Direct link to Splitting up mutation code via helpers") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When you want to split up the code in your mutation or reuse logic across multiple Convex functions you can define and call helper TypeScript functions: convex/myFunctions.ts TS import { v } from "convex/values";import { mutation, MutationCtx } from "./_generated/server";export const addItem = mutation({ args: { text: v.string() }, handler: async (ctx, args) => { await ctx.db.insert("tasks", { text: args.text }); await trackChange(ctx, "addItem"); },});async function trackChange(ctx: MutationCtx, type: "addItem" | "removeItem") { await ctx.db.insert("changes", { type });} Mutations can call helpers that take a [QueryCtx](https://docs.convex.dev/generated-api/server#queryctx) as argument, since the mutation context can do everything query context can. You can `export` helpers to use them across multiple files. They will not be callable from outside of your Convex functions. See [Type annotating server side helpers](https://docs.convex.dev/understanding/best-practices/typescript#type-annotating-server-side-helpers) for more guidance on TypeScript types. Using NPM packages[​](https://docs.convex.dev/functions/mutation-functions#using-npm-packages "Direct link to Using NPM packages") ----------------------------------------------------------------------------------------------------------------------------------- Mutations can import NPM packages installed in `node_modules`. Not all NPM packages are supported, see [Runtimes](https://docs.convex.dev/functions/runtimes#default-convex-runtime) for more details. npm install @faker-js/faker convex/myFunctions.ts TS import { faker } from "@faker-js/faker";import { mutation } from "./_generated/server";export const randomName = mutation({ args: {}, handler: async (ctx) => { faker.seed(); await ctx.db.insert("tasks", { text: "Greet " + faker.person.fullName() }); },}); Calling mutations from clients[​](https://docs.convex.dev/functions/mutation-functions#calling-mutations-from-clients "Direct link to Calling mutations from clients") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- To call a mutation from [React](https://docs.convex.dev/client/react) use the [`useMutation`](https://docs.convex.dev/client/react#editing-data) hook along with the generated [`api`](https://docs.convex.dev/generated-api/api) object. src/myApp.tsx TS import { useMutation } from "convex/react";import { api } from "../convex/_generated/api";export function MyApp() { const mutateSomething = useMutation(api.myFunctions.mutateSomething); const handleClick = () => { mutateSomething({ a: 1, b: 2 }); }; // pass `handleClick` to a button // ...} See the [React](https://docs.convex.dev/client/react) client documentation for all the ways queries can be called. When mutations are called from the [React](https://docs.convex.dev/client/react) or [Rust](https://docs.convex.dev/client/rust) clients, they are executed one at a time in a single, ordered queue. You don't have to worry about mutations editing the database in a different order than they were triggered. Transactions[​](https://docs.convex.dev/functions/mutation-functions#transactions "Direct link to Transactions") ----------------------------------------------------------------------------------------------------------------- Mutations run **transactionally**. This means that: 1. All database reads inside the transaction get a consistent view of the data in the database. You don't have to worry about a concurrent update changing the data in the middle of the execution. 2. All database writes get committed together. If the mutation writes some data to the database, but later throws an error, no data is actually written to the database. For this to work, similarly to queries, mutations must be deterministic, and cannot call third party APIs. To call third party APIs, use [actions](https://docs.convex.dev/functions/actions) . Limits[​](https://docs.convex.dev/functions/mutation-functions#limits "Direct link to Limits") ----------------------------------------------------------------------------------------------- Mutations have a limit to the amount of data they can read and write at once to guarantee good performance. Learn more in [Read/write limit errors](https://docs.convex.dev/functions/error-handling/#readwrite-limit-errors) . For information on other limits, see [Limits](https://docs.convex.dev/production/state/limits) . * [Mutation names](https://docs.convex.dev/functions/mutation-functions#mutation-names) * [The `mutation` constructor](https://docs.convex.dev/functions/mutation-functions#the-mutation-constructor) * [Mutation arguments](https://docs.convex.dev/functions/mutation-functions#mutation-arguments) * [Mutation responses](https://docs.convex.dev/functions/mutation-functions#mutation-responses) * [Mutation context](https://docs.convex.dev/functions/mutation-functions#mutation-context) * [Splitting up mutation code via helpers](https://docs.convex.dev/functions/mutation-functions#splitting-up-mutation-code-via-helpers) * [Using NPM packages](https://docs.convex.dev/functions/mutation-functions#using-npm-packages) * [Calling mutations from clients](https://docs.convex.dev/functions/mutation-functions#calling-mutations-from-clients) * [Transactions](https://docs.convex.dev/functions/mutation-functions#transactions) * [Limits](https://docs.convex.dev/functions/mutation-functions#limits) --- # Queries | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/query-functions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Queries are the bread and butter of your backend API. They fetch data from the database, check authentication or perform other business logic, and return data back to the client application. This is an example query, taking in named arguments, reading data from the database and returning a result: convex/myFunctions.ts TS import { query } from "./_generated/server";import { v } from "convex/values";// Return the last 100 tasks in a given task list.export const getTaskList = query({ args: { taskListId: v.id("taskLists") }, handler: async (ctx, args) => { const tasks = await ctx.db .query("tasks") .withIndex("by_task_list_id", (q) => q.eq("taskListId", args.taskListId)) .order("desc") .take(100); return tasks; },}); Read on to understand how to build queries yourself. Query names[​](https://docs.convex.dev/functions/query-functions#query-names "Direct link to Query names") ----------------------------------------------------------------------------------------------------------- Queries are defined in TypeScript files inside your `convex/` directory. The path and name of the file, as well as the way the function is exported from the file, determine the name the client will use to call it: convex/myFunctions.ts TS // This function will be referred to as `api.myFunctions.myQuery`.export const myQuery = …;// This function will be referred to as `api.myFunctions.sum`.export const sum = …; To structure your API you can nest directories inside the `convex/` directory: convex/foo/myQueries.ts TS // This function will be referred to as `api.foo.myQueries.listMessages`.export const listMessages = …; Default exports receive the name `default`. convex/myFunctions.ts TS // This function will be referred to as `api.myFunctions.default`.export default …; The same rules apply to [mutations](https://docs.convex.dev/functions/mutation-functions) and [actions](https://docs.convex.dev/functions/actions) , while [HTTP actions](https://docs.convex.dev/functions/http-actions) use a different routing approach. Client libraries in languages other than JavaScript and TypeScript use strings instead of API objects: * `api.myFunctions.myQuery` is `"myFunctions:myQuery"` * `api.foo.myQueries.myQuery` is `"foo/myQueries:myQuery"`. * `api.myFunction.default` is `"myFunction:default"` or `"myFunction"`. The `query` constructor[​](https://docs.convex.dev/functions/query-functions#the-query-constructor "Direct link to the-query-constructor") ------------------------------------------------------------------------------------------------------------------------------------------- To actually declare a query in Convex you use the `query` constructor function. Pass it an object with a `handler` function, which returns the query result: convex/myFunctions.ts TS import { query } from "./_generated/server";export const myConstantString = query({ args: {}, handler: () => { return "My never changing string"; },}); ### Query arguments[​](https://docs.convex.dev/functions/query-functions#query-arguments "Direct link to Query arguments") Queries accept named arguments. The argument values are accessible as fields of the second parameter of the handler function: convex/myFunctions.ts TS import { query } from "./_generated/server";export const sum = query({ handler: (_, args: { a: number; b: number }) => { return args.a + args.b; },}); Arguments and responses are automatically serialized and deserialized, and you can pass and return most value-like JavaScript data to and from your query. To both declare the types of arguments and to validate them, add an `args` object using `v` validators: convex/myFunctions.ts TS import { query } from "./_generated/server";import { v } from "convex/values";export const sum = query({ args: { a: v.number(), b: v.number() }, handler: (_, args) => { return args.a + args.b; },}); See [argument validation](https://docs.convex.dev/functions/validation) for the full list of supported types and validators. The first parameter of the handler function contains the query context. ### Query responses[​](https://docs.convex.dev/functions/query-functions#query-responses "Direct link to Query responses") Queries can return values of any supported [Convex type](https://docs.convex.dev/functions/validation) which will be automatically serialized and deserialized. Queries can also return `undefined`, which is not a valid Convex value. When a query returns `undefined` **it is translated to `null`** on the client. ### Query context[​](https://docs.convex.dev/functions/query-functions#query-context "Direct link to Query context") The `query` constructor enables fetching data, and other Convex features by passing a [QueryCtx](https://docs.convex.dev/generated-api/server#queryctx) object to the handler function as the first parameter: convex/myFunctions.ts TS import { query } from "./_generated/server";import { v } from "convex/values";export const myQuery = query({ args: { a: v.number(), b: v.number() }, handler: (ctx, args) => { // Do something with `ctx` },}); Which part of the query context is used depends on what your query needs to do: * To fetch from the database use the `db` field. Note that we make the handler function an `async` function so we can `await` the promise returned by `db.get()`: convex/myFunctions.ts TS import { query } from "./_generated/server";import { v } from "convex/values";export const getTask = query({ args: { id: v.id("tasks") }, handler: async (ctx, args) => { return await ctx.db.get("tasks", args.id); },}); Read more about [Reading Data](https://docs.convex.dev/database/reading-data/) . * To return URLs to stored files use the `storage` field. Read more about [File Storage](https://docs.convex.dev/file-storage) . * To check user authentication use the `auth` field. Read more about [Authentication](https://docs.convex.dev/auth) . Splitting up query code via helpers[​](https://docs.convex.dev/functions/query-functions#splitting-up-query-code-via-helpers "Direct link to Splitting up query code via helpers") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- When you want to split up the code in your query or reuse logic across multiple Convex functions you can define and call helper TypeScript functions: convex/myFunctions.ts TS import { Id } from "./_generated/dataModel";import { query, QueryCtx } from "./_generated/server";import { v } from "convex/values";export const getTaskAndAuthor = query({ args: { id: v.id("tasks") }, handler: async (ctx, args) => { const task = await ctx.db.get("tasks", args.id); if (task === null) { return null; } return { task, author: await getUserName(ctx, task.authorId ?? null) }; },});async function getUserName(ctx: QueryCtx, userId: Id<"users"> | null) { if (userId === null) { return null; } return (await ctx.db.get("users", userId))?.name;} You can `export` helpers to use them across multiple files. They will not be callable from outside of your Convex functions. See [Type annotating server side helpers](https://docs.convex.dev/understanding/best-practices/typescript#type-annotating-server-side-helpers) for more guidance on TypeScript types. Using NPM packages[​](https://docs.convex.dev/functions/query-functions#using-npm-packages "Direct link to Using NPM packages") -------------------------------------------------------------------------------------------------------------------------------- Queries can import NPM packages installed in `node_modules`. Not all NPM packages are supported, see [Runtimes](https://docs.convex.dev/functions/runtimes#default-convex-runtime) for more details. npm install @faker-js/faker convex/myFunctions.ts TS import { query } from "./_generated/server";import { faker } from "@faker-js/faker";export const randomName = query({ args: {}, handler: () => { faker.seed(); return faker.person.fullName(); },}); Calling queries from clients[​](https://docs.convex.dev/functions/query-functions#calling-queries-from-clients "Direct link to Calling queries from clients") -------------------------------------------------------------------------------------------------------------------------------------------------------------- To call a query from [React](https://docs.convex.dev/client/react) use the [`useQuery`](https://docs.convex.dev/client/react#fetching-data) hook along with the generated [`api`](https://docs.convex.dev/generated-api/api) object. src/MyApp.tsx TS import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export function MyApp() { const data = useQuery(api.myFunctions.sum, { a: 1, b: 2 }); // do something with `data`} See the [React](https://docs.convex.dev/client/react) client documentation for all the ways queries can be called. Caching & reactivity & consistency[​](https://docs.convex.dev/functions/query-functions#caching--reactivity--consistency "Direct link to Caching & reactivity & consistency") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Queries have three awesome attributes: 1. **Caching**: Convex caches query results automatically. If many clients request the same query, with the same arguments, they will receive a cached response. 2. **Reactivity**: clients can subscribe to queries to receive new results when the underlying data changes. 3. **Consistency**: All database reads inside a single query call are performed at the same logical timestamp. Concurrent writes do not affect the query results. To have these attributes the handler function must be _deterministic_, which means that given the same arguments (including the query context) it will return the same response. For this reason queries cannot `fetch` from third party APIs. To call third party APIs, use [actions](https://docs.convex.dev/functions/actions) . You might wonder whether you can use non-deterministic language functionality like `Math.random()` or `Date.now()`. The short answer is that Convex takes care of implementing these in a way that you don't have to think about the deterministic constraint. See [Runtimes](https://docs.convex.dev/functions/runtimes#default-convex-runtime) for more details on the Convex runtime. Limits[​](https://docs.convex.dev/functions/query-functions#limits "Direct link to Limits") -------------------------------------------------------------------------------------------- Queries have a limit to the amount of data they can read at once to guarantee good performance. Check out these limits in [Read/write limit errors](https://docs.convex.dev/functions/error-handling/#readwrite-limit-errors) . For information on other limits, see [Limits](https://docs.convex.dev/production/state/limits) . * [Query names](https://docs.convex.dev/functions/query-functions#query-names) * [The `query` constructor](https://docs.convex.dev/functions/query-functions#the-query-constructor) * [Query arguments](https://docs.convex.dev/functions/query-functions#query-arguments) * [Query responses](https://docs.convex.dev/functions/query-functions#query-responses) * [Query context](https://docs.convex.dev/functions/query-functions#query-context) * [Splitting up query code via helpers](https://docs.convex.dev/functions/query-functions#splitting-up-query-code-via-helpers) * [Using NPM packages](https://docs.convex.dev/functions/query-functions#using-npm-packages) * [Calling queries from clients](https://docs.convex.dev/functions/query-functions#calling-queries-from-clients) * [Caching & reactivity & consistency](https://docs.convex.dev/functions/query-functions#caching--reactivity--consistency) * [Limits](https://docs.convex.dev/functions/query-functions#limits) --- # Internal Functions | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/internal-functions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Internal functions can only be called by other [functions](https://docs.convex.dev/functions) and cannot be called directly from a [Convex client](https://docs.convex.dev/client/react) . By default your Convex functions are public and accessible to clients. Public functions may be called by malicious users in ways that cause surprising results. Internal functions help you mitigate this risk. We recommend using internal functions any time you're writing logic that should not be called from a client. While internal functions help mitigate risk by reducing the public surface area of your application, you can still validate internal invariants using [argument validation](https://docs.convex.dev/functions/validation) and/or [authentication](https://docs.convex.dev/auth/functions-auth) . Use cases for internal functions[​](https://docs.convex.dev/functions/internal-functions#use-cases-for-internal-functions "Direct link to Use cases for internal functions") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Leverage internal functions by: * Calling them from [actions](https://docs.convex.dev/functions/actions#action-context) via `runQuery` and `runMutation` * Calling them from [HTTP actions](https://docs.convex.dev/functions/http-actions) via `runQuery`, `runMutation`, and `runAction` * [Scheduling](https://docs.convex.dev/scheduling/scheduled-functions) them from other functions to run in the future * Scheduling them to run periodically from [cron jobs](https://docs.convex.dev/scheduling/cron-jobs) * Running them using the [Dashboard](https://docs.convex.dev/dashboard/deployments/functions#running-functions) * Running them from the [CLI](https://docs.convex.dev/cli#run-convex-functions) Defining internal functions[​](https://docs.convex.dev/functions/internal-functions#defining-internal-functions "Direct link to Defining internal functions") -------------------------------------------------------------------------------------------------------------------------------------------------------------- An internal function is defined using `internalQuery`, `internalMutation`, or `internalAction`. For example: convex/plans.ts TS import { internalMutation } from "./_generated/server";import { v } from "convex/values";export const markPlanAsProfessional = internalMutation({ args: { planId: v.id("plans") }, handler: async (ctx, args) => { await ctx.db.patch("plans", args.planId, { planType: "professional" }); },}); If you need to pass complicated objects to internal functions you might prefer to not use argument validation. Note though that if you're using `internalQuery` or `internalMutation` it's a better idea to pass around document IDs instead of documents, to ensure the query or mutation is working with the up-to-date state of the database. Internal function without argument validation convex/plans.ts TS import { internalAction } from "./_generated/server";import { Doc } from "./_generated/dataModel";export const markPlanAsProfessional = internalAction({ handler: async (actionCtx, args) => { // perform an action, perhaps calling a third-party API },}); Calling internal functions[​](https://docs.convex.dev/functions/internal-functions#calling-internal-functions "Direct link to Calling internal functions") ----------------------------------------------------------------------------------------------------------------------------------------------------------- Internal functions can be called from actions and scheduled from actions and mutation using the [`internal`](https://docs.convex.dev/generated-api/api#internal) object. For example, consider this public `upgrade` action that calls the internal `plans.markPlanAsProfessional` mutation we defined above: convex/changes.ts TS import { action } from "./_generated/server";import { internal } from "./_generated/api";import { v } from "convex/values";export const upgrade = action({ args: { planId: v.id("plans"), }, handler: async (ctx, args) => { // Call out to payment provider (e.g. Stripe) to charge customer const response = await fetch("https://..."); if (response.ok) { // Mark the plan as "professional" in the Convex DB await ctx.runMutation(internal.plans.markPlanAsProfessional, { planId: args.planId, }); } },}); In this example a user should not be able to directly call `internal.plans.markPlanAsProfessional` without going through the `upgrade` action — if they did, then they would get a free upgrade. You can define public and internal functions in the same file. * [Use cases for internal functions](https://docs.convex.dev/functions/internal-functions#use-cases-for-internal-functions) * [Defining internal functions](https://docs.convex.dev/functions/internal-functions#defining-internal-functions) * [Calling internal functions](https://docs.convex.dev/functions/internal-functions#calling-internal-functions) --- # Bundling | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/bundling#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Bundling is the process of gathering, optimizing and transpiling the JS/TS source code of [functions](https://docs.convex.dev/functions) and their dependencies. During development and when deploying, the code is transformed to a format that Convex [runtimes](https://docs.convex.dev/functions/runtimes) 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](https://docs.convex.dev/functions/bundling#external-packages) config. Bundling for Convex[​](https://docs.convex.dev/functions/bundling#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[​](https://docs.convex.dev/functions/bundling#bundling-limitations "Direct link to Bundling limitations") ------------------------------------------------------------------------------------------------------------------------------- The nature of bundling comes with a few limitations. ### Code size limits[​](https://docs.convex.dev/functions/bundling#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](https://docs.convex.dev/production/state/limits) . 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, just generate 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](https://docs.convex.dev/functions/bundling#external-packages) . ### Dynamic dependencies[​](https://docs.convex.dev/functions/bundling#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](https://docs.convex.dev/functions/runtimes#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[​](https://docs.convex.dev/functions/bundling#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](https://docs.convex.dev/functions/runtimes#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[​](https://docs.convex.dev/functions/bundling#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[​](https://docs.convex.dev/functions/bundling#specifying-external-packages "Direct link to Specifying external packages") Create a [`convex.json`](https://docs.convex.dev/production/project-configuration#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](https://docs.convex.dev/functions/actions#choosing-the-runtime-use-node) . ### Troubleshooting external packages[​](https://docs.convex.dev/functions/bundling#troubleshooting-external-packages "Direct link to Troubleshooting external packages") #### Incorrect package versions[​](https://docs.convex.dev/functions/bundling#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[​](https://docs.convex.dev/functions/bundling#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: // ❌ oldimport { Foo } from "some-module";// ✅ newimport SomeModule from "some-module";const { Foo } = SomeModule; ### Limitations[​](https://docs.convex.dev/functions/bundling#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) . * [Bundling for Convex](https://docs.convex.dev/functions/bundling#bundling-for-convex) * [Bundling limitations](https://docs.convex.dev/functions/bundling#bundling-limitations) * [Code size limits](https://docs.convex.dev/functions/bundling#code-size-limits) * [Dynamic dependencies](https://docs.convex.dev/functions/bundling#dynamic-dependencies) * [External packages](https://docs.convex.dev/functions/bundling#external-packages) * [Package installation on the server](https://docs.convex.dev/functions/bundling#package-installation-on-the-server) * [Specifying external packages](https://docs.convex.dev/functions/bundling#specifying-external-packages) * [Troubleshooting external packages](https://docs.convex.dev/functions/bundling#troubleshooting-external-packages) * [Limitations](https://docs.convex.dev/functions/bundling#limitations) --- # Runtimes | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/runtimes#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex functions can run in two runtimes: * Default [Convex runtime](https://docs.convex.dev/functions/runtimes#default-convex-runtime) * Opt-in [Node.js runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime) Default Convex runtime[​](https://docs.convex.dev/functions/runtimes#default-convex-runtime "Direct link to Default Convex runtime") ------------------------------------------------------------------------------------------------------------------------------------- All Convex backend functions are written in JavaScript or TypeScript. By default all functions run in a custom JavaScript runtime very similar to the [Cloudflare Workers runtime](https://blog.cloudflare.com/cloud-computing-without-containers/) with access to most [web standard globals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects) . The default runtime has many advantages including: * **No cold starts**. The runtime is always up, and ready to handle any function at a moments notice. * **Latest web JavaScript standards**. The runtime is based on V8 that also powers Google Chrome. This ensures it provides an interface very similar to your frontend code, allowing further simplification to your code. * **Low overhead access to your data**. The runtime is designed to have low overhead access to your data via query & mutation functions, allowing you to access your database via a simple [JavaScript interface](https://docs.convex.dev/database/reading-data/) . ### Supported APIs[​](https://docs.convex.dev/functions/runtimes#supported-apis "Direct link to Supported APIs") The default runtime supports most npm libraries that work in the browser, [Deno](https://deno.com/) , and [Cloudflare workers](https://developers.cloudflare.com/workers/) . If your library isn't supported, you can use an action with the [Node.js runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime) , or reach out in [Discord](https://convex.dev/community) . We are improving support all the time. #### Network APIs[​](https://docs.convex.dev/functions/runtimes#network-apis "Direct link to Network APIs") * [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) * [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) * [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) * [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) — in [Actions](https://docs.convex.dev/functions/runtimes#actions) only. See [Networking](https://docs.convex.dev/production/networking) for egress IP addresses. * [File](https://developer.mozilla.org/en-US/docs/Web/API/File) * [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) * [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) * [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) * [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) #### Encoding APIs[​](https://docs.convex.dev/functions/runtimes#encoding-apis "Direct link to Encoding APIs") * [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) * [TextEncoder](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) * [atob](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/atob) * [btoa](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/btoa) #### Web Stream APIs[​](https://docs.convex.dev/functions/runtimes#web-stream-apis "Direct link to Web Stream APIs") * [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) * [ReadableStreamBYOBReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamBYOBReader) * [ReadableStreamDefaultReader](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) * [TransformStream](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) * [WritableStream](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) * [WritableStreamDefaultWriter](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter) #### Web Crypto APIs[​](https://docs.convex.dev/functions/runtimes#web-crypto-apis "Direct link to Web Crypto APIs") * [crypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) * [CryptoKey](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) * [SubtleCrypto](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) ### Restrictions on queries and mutations[​](https://docs.convex.dev/functions/runtimes#restrictions-on-queries-and-mutations "Direct link to Restrictions on queries and mutations") Query and mutation functions are further **restricted by the runtime to be [deterministic](https://en.wikipedia.org/wiki/Deterministic_algorithm) **. This allows Convex to automatically retry them by the system as necessary. Determinism means that no matter how many times your function is run, as long as it is given the same arguments, it will have identical side effects and return the same value. You don't have to think all that much about maintaining these properties of determinism when you write your Convex functions. Convex will provide helpful error messages as you go, so you can't _accidentally_ do something forbidden. #### Using randomness and time in queries and mutations[​](https://docs.convex.dev/functions/runtimes#using-randomness-and-time-in-queries-and-mutations "Direct link to Using randomness and time in queries and mutations") Convex provides a "seeded" strong pseudo-random number generator at `Math.random()` so that it can guarantee the determinism of your function. The random number generator's seed is an implicit parameter to your function. Multiple calls to `Math.random()` in one function call will return different random values. Note that Convex does not reevaluate the JavaScript modules on every function run, so a call to `Math.random()` stored in a global variable will not change between function runs. To ensure the logic within your function is reproducible, the system time used globally (outside of any function) is "frozen" at deploy time, while the system time during Convex function execution is "frozen" when the function begins. `Date.now()` will return the same result for the entirety of your function's execution. For example, const globalRand = Math.random(); // `globalRand` does not change between runs.const globalNow = Date.now(); // `globalNow` is the time when Convex functions were deployed.export const updateSomething = mutation({ args: {}, handler: () => { const now1 = Date.now(); // `now1` is the time when the function execution started. const rand1 = Math.random(); // `rand1` has a new value for each function run. // implementation const now2 = Date.now(); // `now2` === `now1` const rand2 = Math.random(); // `rand1` !== `rand2` },}); ### Actions[​](https://docs.convex.dev/functions/runtimes#actions "Direct link to Actions") Actions are unrestricted by the same rules of determinism as query and mutation functions. Notably actions are allowed to call third-party HTTP endpoints via the browser-standard [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) function. By default actions also run in Convex’s custom JavaScript runtime with all of its advantages including no cold starts and a browser-like API environment. They can also live in the same file as your query and mutation functions. Node.js runtime[​](https://docs.convex.dev/functions/runtimes#nodejs-runtime "Direct link to Node.js runtime") --------------------------------------------------------------------------------------------------------------- Some JavaScript and TypeScript libraries use features that are not included in the default Convex runtime. Convex actions provide an escape hatch to [Node.js](https://nodejs.org/en/about) via the `"use node"` directive at the top of a file that contains your action. [Learn more](https://docs.convex.dev/functions/actions#choosing-the-runtime-use-node) . Use of the Node.js environment is restricted to **action functions only**. If you want to use a library designed for Node.js and interact with the Convex database, you need to call the Node.js library from an action, and use [`runQuery`](https://docs.convex.dev/functions/actions#action-context) or [`runMutation`](https://docs.convex.dev/functions/actions#action-context) helper to call a query or mutation. Every `.ts` and `.js` file in the convex directory [is bundled](https://docs.convex.dev/functions/bundling) either for the default Convex JavaScript runtime or Node.js, along with any code it imports. Files with the `"use node"` directive should not contain any Convex queries or mutations since they cannot be run in the Node.js runtime. Additionally, files without the `"use node"` directive should not import any files with the `"use node"` directive. Files that contain no Convex functions, like a `convex/utils.ts` file, also need the "use node" directive if they use Node.js-specific libraries. If you encounter bundling errors about Node.js-specific imports like `fs` / `node:fs` not being available when deploying convex functions, running `npx convex dev --once --debug-node-apis` gives more information about these. It uses a slower bundling method to track the train of imports, narrowing down which import is responsible for the error. Note that argument size limits are lower (5MiB instead of 16MiB). ### Node.js version configuration[​](https://docs.convex.dev/functions/runtimes#nodejs-version-configuration "Direct link to Node.js version configuration") By default, all actions ran in the Node.js environment are executed in Node.js 20. This version is configurable in the [convex.json](https://docs.convex.dev/production/project-configuration#configuring-the-nodejs-version) file. We currently support Node.js 20 and 22. When pushing a new Node.js version to the server, the new code for your functions may be executed in the old Node.js version for up a few minutes. Note: This configuration is not supported when running the self-hosted Convex backend. The node version that is specified in the [.nvmrc](https://github.com/get-convex/convex-backend/blob/main/.nvmrc) will be used instead. * [Default Convex runtime](https://docs.convex.dev/functions/runtimes#default-convex-runtime) * [Supported APIs](https://docs.convex.dev/functions/runtimes#supported-apis) * [Restrictions on queries and mutations](https://docs.convex.dev/functions/runtimes#restrictions-on-queries-and-mutations) * [Actions](https://docs.convex.dev/functions/runtimes#actions) * [Node.js runtime](https://docs.convex.dev/functions/runtimes#nodejs-runtime) * [Node.js version configuration](https://docs.convex.dev/functions/runtimes#nodejs-version-configuration) --- # Argument and Return Value Validation | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/validation#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Argument and return value validators ensure that [queries](https://docs.convex.dev/functions/query-functions) , [mutations](https://docs.convex.dev/functions/mutation-functions) , and [actions](https://docs.convex.dev/functions/actions) are called with the correct types of arguments and return the expected types of return values. **This is important for security!** Without argument validation, a malicious user can call your public functions with unexpected arguments and cause surprising results. [TypeScript](https://docs.convex.dev/understanding/best-practices/typescript) alone won't help because TypeScript types aren't present at runtime. We recommend adding argument validation for all public functions in production apps. For non-public functions that are not called by clients, we recommend [internal functions](https://docs.convex.dev/functions/internal-functions) and optionally validation. **Example:** [Argument Validation](https://github.com/get-convex/convex-demos/tree/main/args-validation) Adding validators[​](https://docs.convex.dev/functions/validation#adding-validators "Direct link to Adding validators") ------------------------------------------------------------------------------------------------------------------------ To add argument validation to your functions, pass an object with `args` and `handler` properties to the `query`, `mutation` or `action` constructor. To add return value validation, use the `returns` property in this object: convex/message.ts TS import { mutation, query } from "./_generated/server";import { v } from "convex/values";export const send = mutation({ args: { body: v.string(), author: v.string(), }, returns: v.null(), handler: async (ctx, args) => { const { body, author } = args; await ctx.db.insert("messages", { body, author }); },}); If you define your function with an argument validator, there is no need to include [TypeScript](https://docs.convex.dev/understanding/best-practices/typescript) type annotations! The type of your function will be inferred automatically. Similarly, if you define a return value validator, the return type of your function will be inferred from the validator, and TypeScript will check that it matches the inferred return type of the `handler` function. Unlike TypeScript, validation for an object will throw if the object contains properties that are not declared in the validator. If the client supplies arguments not declared in `args`, or if the function returns a value that does not match the validator declared in `returns`. This is helpful to prevent bugs caused by mistyped names of arguments or returning more data than intended to a client. Even `args: {}` is a helpful use of validators because TypeScript will show an error on the client if you try to pass any arguments to the function which doesn't expect them. Supported types[​](https://docs.convex.dev/functions/validation#supported-types "Direct link to Supported types") ------------------------------------------------------------------------------------------------------------------ All functions, both public and internal, can accept and return the following data types. Each type has a corresponding validator that can be accessed on the [`v`](https://docs.convex.dev/api/modules/values#v) object imported from `"convex/values"`. The [database](https://docs.convex.dev/database) can store the exact same set of [data types](https://docs.convex.dev/database/types) . Additionally you can also express type unions, literals, `any` types, and optional fields. ### Convex values[​](https://docs.convex.dev/functions/validation#convex-values "Direct link to Convex values") Convex supports the following types of values: | Convex Type | TS/JS Type | Example Usage | Validator for [Argument Validation](https://docs.convex.dev/functions/validation)
and [Schemas](https://docs.convex.dev/database/schemas) | `json` Format for [Export](https://docs.convex.dev/database/import-export) | Notes | | --- | --- | --- | --- | --- | --- | | Id | [Id](https://docs.convex.dev/database/document-ids)
([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)
) | `doc._id` | `v.id(tableName)` | string | See [Document IDs](https://docs.convex.dev/database/document-ids)
. | | Null | [null](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#null_type) | `null` | `v.null()` | null | JavaScript's `undefined` is not a valid Convex value. Functions the return `undefined` or do not return will return `null` when called from a client. Use `null` instead. | | Int64 | [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#bigint_type) | `3n` | `v.int64()` | string (base10) | Int64s only support BigInts between -2^63 and 2^63-1. Convex supports `bigint`s in [most modern browsers](https://caniuse.com/bigint)
. | | Float64 | [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) | `3.1` | `v.number()` | number / string | Convex supports all IEEE-754 double-precision floating point numbers (such as NaNs). Inf and NaN are JSON serialized as strings. | | Boolean | [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type) | `true` | `v.boolean()` | bool | | | String | [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) | `"abc"` | `v.string()` | string | Strings are stored as UTF-8 and must be valid Unicode sequences. Strings must be smaller than the 1MB total size limit when encoded as UTF-8. | | Bytes | [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) | `new ArrayBuffer(8)` | `v.bytes()` | string (base64) | Convex supports first class bytestrings, passed in as `ArrayBuffer`s. Bytestrings must be smaller than the 1MB total size limit for Convex types. | | Array | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | `[1, 3.2, "abc"]` | `v.array(values)` | array | Arrays can have at most 8192 values. | | Object | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#objects) | `{a: "abc"}` | `v.object({property: value})` | object | Convex only supports "plain old JavaScript objects" (objects that do not have a custom prototype). Convex includes all [enumerable properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)
. Objects can have at most 1024 entries. Field names must be nonempty and not start with "$" or "\_". | | Record | [Record](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) | `{"a": "1", "b": "2"}` | `v.record(keys, values)` | object | Records are objects at runtime, but can have dynamic keys. Keys must be only ASCII characters, nonempty, and not start with "$" or "\_". | ### Unions[​](https://docs.convex.dev/functions/validation#unions "Direct link to Unions") You can describe fields that could be one of multiple types using `v.union`: import { mutation } from "./_generated/server";import { v } from "convex/values";export default mutation({ args: { stringOrNull: v.union(v.string(), v.null()), }, handler: async (ctx, { stringOrNull }) => { //... },}); For convenience, `v.nullable(foo)` is equivalent to `v.union(foo, v.null())`. ### Literals[​](https://docs.convex.dev/functions/validation#literals "Direct link to Literals") Fields that are a constant can be expressed with `v.literal`. This is especially useful when combined with unions: import { mutation } from "./_generated/server";import { v } from "convex/values";export default mutation({ args: { oneTwoOrThree: v.union( v.literal("one"), v.literal("two"), v.literal("three"), ), }, handler: async (ctx, { oneTwoOrThree }) => { //... },}); ### Record objects[​](https://docs.convex.dev/functions/validation#record-objects "Direct link to Record objects") You can describe objects that map arbitrary keys to values with `v.record`: import { mutation } from "./_generated/server";import { v } from "convex/values";export default mutation({ args: { simpleMapping: v.record(v.string(), v.boolean()), }, handler: async (ctx, { simpleMapping }) => { //... },}); You can use other types of string validators for the keys: defineTable({ userIdToValue: v.record(v.id("users"), v.boolean()),}); Notes: * This type corresponds to the [Record](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) type in TypeScript. * You cannot use string literals as a `record` key. * Using `v.string()` as a `record` key validator will only allow ASCII characters. ### Any[​](https://docs.convex.dev/functions/validation#any "Direct link to Any") Fields that could take on any value can be represented with `v.any()`: import { mutation } from "./_generated/server";import { v } from "convex/values";export default mutation({ args: { anyValue: v.any(), }, handler: async (ctx, { anyValue }) => { //... },}); This corresponds to the `any` type in TypeScript. ### Optional fields[​](https://docs.convex.dev/functions/validation#optional-fields "Direct link to Optional fields") You can describe optional fields by wrapping their type with `v.optional(...)`: import { mutation } from "./_generated/server";import { v } from "convex/values";export default mutation({ args: { optionalString: v.optional(v.string()), optionalNumber: v.optional(v.number()), }, handler: async (ctx, { optionalString, optionalNumber }) => { //... },}); This corresponds to marking fields as optional with `?` in TypeScript. Extracting TypeScript types[​](https://docs.convex.dev/functions/validation#extracting-typescript-types "Direct link to Extracting TypeScript types") ------------------------------------------------------------------------------------------------------------------------------------------------------ The [`Infer`](https://docs.convex.dev/api/modules/values#infer) type allows you to turn validator calls into TypeScript types. This can be useful to remove duplication between your validators and TypeScript types: import { mutation } from "./_generated/server";import { Infer, v } from "convex/values";const nestedObject = v.object({ property: v.string(),});// Resolves to `{property: string}`.export type NestedObject = Infer;export default mutation({ args: { nested: nestedObject, }, handler: async (ctx, { nested }) => { //... },}); ### Reusing and extending validators[​](https://docs.convex.dev/functions/validation#reusing-and-extending-validators "Direct link to Reusing and extending validators") Validators can be defined once and shared between functions and table schemas. const statusValidator = v.union(v.literal("active"), v.literal("inactive"));const userValidator = v.object({ name: v.string(), email: v.email(), status: statusValidator, profileUrl: v.optional(v.string()),});const schema = defineSchema({ users: defineTable(userValidator).index("by_email", ["email"]),}); You can create new object validators from existing ones by adding or removing fields using `.pick`, `.omit`, `.extend`, and `.partial` on object validators. // Creates a new validator with only the name and profileUrl fields.const publicUser = userValidator.pick("name", "profileUrl");// Creates a new validator with all fields except the specified fields.const userWithoutStatus = userValidator.omit("status", "profileUrl");// Creates a validator where all fields are optional.// This is useful for validating patches to a document.const userPatch = userWithoutStatus.partial();// Creates a new validator adding system fields to the user validator.const userDocument = userValidator.extend({ _id: v.id("users"), _creationTime: v.number(),}); Notes: * Object validators don't allow extra properties, objects with properties that aren't specified will fail validation. * Top-level table fields cannot start with `_` because they are reserved for system fields like `_id` and `_creationTime`. * [Adding validators](https://docs.convex.dev/functions/validation#adding-validators) * [Supported types](https://docs.convex.dev/functions/validation#supported-types) * [Convex values](https://docs.convex.dev/functions/validation#convex-values) * [Unions](https://docs.convex.dev/functions/validation#unions) * [Literals](https://docs.convex.dev/functions/validation#literals) * [Record objects](https://docs.convex.dev/functions/validation#record-objects) * [Any](https://docs.convex.dev/functions/validation#any) * [Optional fields](https://docs.convex.dev/functions/validation#optional-fields) * [Extracting TypeScript types](https://docs.convex.dev/functions/validation#extracting-typescript-types) * [Reusing and extending validators](https://docs.convex.dev/functions/validation#reusing-and-extending-validators) --- # Actions | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/actions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page 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](https://docs.convex.dev/functions/query-functions) and [mutations](https://docs.convex.dev/functions/mutation-functions) . **Example:** [GIPHY Action](https://github.com/get-convex/convex-demos/tree/main/giphy-action) Action names[​](https://docs.convex.dev/functions/actions#action-names "Direct link to Action names") ------------------------------------------------------------------------------------------------------ Actions follow the same naming rules as queries, see [Query names](https://docs.convex.dev/functions/query-functions#query-names) . The `action` constructor[​](https://docs.convex.dev/functions/actions#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[​](https://docs.convex.dev/functions/actions#action-arguments-and-responses "Direct link to Action arguments and responses") Action arguments and responses follow the same rules as [mutations](https://docs.convex.dev/functions/mutation-functions#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[​](https://docs.convex.dev/functions/actions#action-context "Direct link to Action context") The `action` constructor enables interacting with the database, and other Convex features by passing an [ActionCtx](https://docs.convex.dev/api/interfaces/server.GenericActionCtx) 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](https://docs.convex.dev/functions/internal-functions) 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](https://docs.convex.dev/functions/internal-functions) 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](https://docs.convex.dev/file-storage) . * To check user authentication use the `auth` field. Auth is propagated automatically when calling queries and mutations from the action. Read on about [Authentication](https://docs.convex.dev/auth) . * To schedule functions to run in the future, use the `scheduler` field. Read on about [Scheduled Functions](https://docs.convex.dev/scheduling/scheduled-functions) . * To search a vector index, use the `vectorSearch` field. Read on about [Vector Search](https://docs.convex.dev/search/vector-search) . ### Dealing with circular type inference[​](https://docs.convex.dev/functions/actions#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](https://docs.convex.dev/understanding/best-practices/typescript#type-annotating-server-side-helpers) page for other types which might be helpful when annotating the result. Choosing the runtime ("use node")[​](https://docs.convex.dev/functions/actions#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](https://docs.convex.dev/functions/runtimes) . Splitting up action code via helpers[​](https://docs.convex.dev/functions/actions#splitting-up-action-code-via-helpers "Direct link to Splitting up action code via helpers") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Just like with [queries](https://docs.convex.dev/functions/query-functions#splitting-up-query-code-via-helpers) and [mutations](https://docs.convex.dev/functions/mutation-functions#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](https://docs.convex.dev/api/interfaces/server.GenericActionCtx) only has the `auth` field in common with [QueryCtx](https://docs.convex.dev/generated-api/server#queryctx) and [MutationCtx](https://docs.convex.dev/generated-api/server#mutationctx) . Calling actions from clients[​](https://docs.convex.dev/functions/actions#calling-actions-from-clients "Direct link to Calling actions from clients") ------------------------------------------------------------------------------------------------------------------------------------------------------ To call an action from [React](https://docs.convex.dev/client/react) use the [`useAction`](https://docs.convex.dev/api/modules/react#useaction) hook along with the generated [`api`](https://docs.convex.dev/generated-api/api) 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](https://docs.convex.dev/functions/mutation-functions#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](https://docs.convex.dev/functions/mutation-functions) which captures the user intent by writing into the database and then [schedules](https://docs.convex.dev/scheduling/scheduled-functions) 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[​](https://docs.convex.dev/functions/actions#limits "Direct link to Limits") ------------------------------------------------------------------------------------ Actions time out after 10 minutes. [Node.js](https://docs.convex.dev/functions/runtimes#nodejs-runtime) and [Convex runtime](https://docs.convex.dev/functions/runtimes#default-convex-runtime) have 512MB and 64MB memory limit respectively. Please [contact us](https://docs.convex.dev/production/contact) 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](https://docs.convex.dev/production/state/limits) . If you need to allowlist Convex's outbound IP addresses in an external service's firewall, see [Networking](https://docs.convex.dev/production/networking) . Error handling[​](https://docs.convex.dev/functions/actions#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[​](https://docs.convex.dev/functions/actions#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[​](https://docs.convex.dev/functions/actions#best-practices "Direct link to Best practices") ------------------------------------------------------------------------------------------------------------ ### `await ctx.runAction` should only be used for crossing JS runtimes[​](https://docs.convex.dev/functions/actions#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](https://docs.convex.dev/functions/runtimes) . 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](https://docs.convex.dev/understanding/best-practices/#use-helper-functions-to-write-shared-code) and call the helper instead. ### Avoid `await ctx.runMutation` / `await ctx.runQuery`[​](https://docs.convex.dev/functions/actions#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[​](https://docs.convex.dev/functions/actions#related-components "Direct link to Related Components") ------------------------------------------------------------------------------------------------------------------------ [Convex Component\ \ ### Action Cache\ \ Cache expensive or frequently run actions. Allows configurable cache duration and forcing updates.](https://www.convex.dev/components/action-cache) [Convex Component\ \ ### 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\ \ ### 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) * [Action names](https://docs.convex.dev/functions/actions#action-names) * [The `action` constructor](https://docs.convex.dev/functions/actions#the-action-constructor) * [Action arguments and responses](https://docs.convex.dev/functions/actions#action-arguments-and-responses) * [Action context](https://docs.convex.dev/functions/actions#action-context) * [Dealing with circular type inference](https://docs.convex.dev/functions/actions#dealing-with-circular-type-inference) * [Choosing the runtime ("use node")](https://docs.convex.dev/functions/actions#choosing-the-runtime-use-node) * [Splitting up action code via helpers](https://docs.convex.dev/functions/actions#splitting-up-action-code-via-helpers) * [Calling actions from clients](https://docs.convex.dev/functions/actions#calling-actions-from-clients) * [Limits](https://docs.convex.dev/functions/actions#limits) * [Error handling](https://docs.convex.dev/functions/actions#error-handling) * [Dangling promises](https://docs.convex.dev/functions/actions#dangling-promises) * [Best practices](https://docs.convex.dev/functions/actions#best-practices) * [`await ctx.runAction` should only be used for crossing JS runtimes](https://docs.convex.dev/functions/actions#await-ctxrunaction-should-only-be-used-for-crossing-js-runtimes) * [Avoid `await ctx.runMutation` / `await ctx.runQuery`](https://docs.convex.dev/functions/actions#avoid-await-ctxrunmutation--await-ctxrunquery) * [Related Components](https://docs.convex.dev/functions/actions#related-components) --- # Debugging | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/debugging#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Debugging is the process of figuring out why your code isn't behaving as you expect. Debugging during development[​](https://docs.convex.dev/functions/debugging#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](https://docs.convex.dev/functions/runtimes#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`](https://docs.convex.dev/client/react) , 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](https://docs.convex.dev/functions/error-handling/#differences-in-error-reporting-between-dev-and-prod) . 2. In your Convex dashboard on the [Logs page](https://docs.convex.dev/dashboard/deployments/logs) . 3. In your terminal with [`npx convex dev`](https://docs.convex.dev/cli#tail-deployment-logs) during development or [`npx convex logs`](https://docs.convex.dev/cli#tail-deployment-logs) , which only prints logs. ### Using a debugger[​](https://docs.convex.dev/functions/debugging#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](https://docs.convex.dev/testing/convex-test#debugging-tests) . Debugging in production[​](https://docs.convex.dev/functions/debugging#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](https://docs.convex.dev/production/integrations/) integrations to enable your team easy access to historical logs and additional information logged by your client. Finding relevant logs by Request ID[​](https://docs.convex.dev/functions/debugging#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](https://docs.convex.dev/dashboard/deployments/logs#filter-logs) for details. * [Debugging during development](https://docs.convex.dev/functions/debugging#debugging-during-development) * [Using a debugger](https://docs.convex.dev/functions/debugging#using-a-debugger) * [Debugging in production](https://docs.convex.dev/functions/debugging#debugging-in-production) * [Finding relevant logs by Request ID](https://docs.convex.dev/functions/debugging#finding-relevant-logs-by-request-id) --- # Error Handling | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/error-handling/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page There are four reasons why your Convex [queries](https://docs.convex.dev/functions/query-functions) and [mutations](https://docs.convex.dev/functions/mutation-functions) may hit errors: 1. [Application Errors](https://docs.convex.dev/functions/error-handling/#application-errors-expected-failures) : The function code hits a logical condition that should stop further processing, and your code throws a `ConvexError` 2. Developer Errors: There is a bug in the function (like calling `db.get("documents", null)` instead of `db.get("documents", id)`). 3. [Read/Write Limit Errors](https://docs.convex.dev/functions/error-handling/#readwrite-limit-errors) : The function is retrieving or writing too much data. 4. Internal Convex Errors: There is a problem within Convex (like a network blip). Convex will automatically handle internal Convex errors. If there are problems on our end, we'll automatically retry your queries and mutations until the problem is resolved and your queries and mutations succeed. On the other hand, you must decide how to handle application, developer and read/write limit errors. When one of these errors happens, the best practices are to: 1. Show the user some appropriate UI. 2. Send the error to an exception reporting service using the [Exception Reporting Integration](https://docs.convex.dev/production/integrations/exception-reporting) . 3. Log the incident using `console.*` and set up reporting with [Log Streaming](https://docs.convex.dev/production/integrations/log-streams/) . This can be done in addition to the above options, and doesn't require an exception to be thrown. Additionally, you might also want to send client-side errors to a service like [Sentry](https://sentry.io/) to capture additional information for debugging and observability. Errors in queries[​](https://docs.convex.dev/functions/error-handling/#errors-in-queries "Direct link to Errors in queries") ----------------------------------------------------------------------------------------------------------------------------- If your query function hits an error, the error will be sent to the client and thrown from your `useQuery` call site. **The best way to handle these errors is with a React [error boundary component](https://reactjs.org/docs/error-boundaries.html) .** Error boundaries allow you to catch errors thrown in their child component tree, render fallback UI, and send information about the error to your exception handling service. Adding error boundaries to your app is a great way to handle errors in Convex query functions as well as other errors in your React components. If you are using Sentry, you can use their [`Sentry.ErrorBoundary`](https://docs.sentry.io/platforms/javascript/guides/react/components/errorboundary/) component. With error boundaries, you can decide how granular you'd like your fallback UI to be. One simple option is to wrap your entire application in a single error boundary like: , Then any error in any of your components will be caught by the boundary and render the same fallback UI. On the other hand, if you'd like to enable some portions of your app to continue functioning even if other parts hit errors, you can instead wrap different parts of your app in separate error boundaries. Retrying Unlike other frameworks, there is no concept of "retrying" if your query function hits an error. Because Convex functions are [deterministic](https://docs.convex.dev/functions/query-functions#caching--reactivity--consistency) , if the query function hits an error, retrying will always produce the same error. There is no point in running the query function with the same arguments again. Errors in mutations[​](https://docs.convex.dev/functions/error-handling/#errors-in-mutations "Direct link to Errors in mutations") ----------------------------------------------------------------------------------------------------------------------------------- If a mutation hits an error, this will 1. Cause the promise returned from your mutation call to be rejected. 2. Cause your [optimistic update](https://docs.convex.dev/client/react/optimistic-updates) to be rolled back. If you have an exception service like [Sentry](https://sentry.io/) configured, it should report "unhandled promise rejections" like this automatically. That means that with no additional work your mutation errors should be reported. Note that errors in mutations won't be caught by your error boundaries because the error doesn't happen as part of rendering your components. If you would like to render UI specifically in response to a mutation failure, you can use `.catch` on your mutation call. For example: sendMessage(newMessageText).catch((error) => { // Do something with `error` here}); If you're using an `async` handled function you can also use `try...catch`: try { await sendMessage(newMessageText);} catch (error) { // Do something with `error` here} Reporting caught errors If you handle your mutation error, it will no longer become an unhandled promise rejection. You may need to report this error to your exception handling service manually. Errors in action functions[​](https://docs.convex.dev/functions/error-handling/#errors-in-action-functions "Direct link to Errors in action functions") -------------------------------------------------------------------------------------------------------------------------------------------------------- Unlike queries and mutations, [actions](https://docs/functions/actions.mdx) may have side-effects and therefore can't be automatically retried by Convex when errors occur. For example, say your action sends a email. If it fails part-way through, Convex has no way of knowing if the email was already sent and can't safely retry the action. It is responsibility of the caller to handle errors raised by actions and retry if appropriate. Differences in error reporting between dev and prod[​](https://docs.convex.dev/functions/error-handling/#differences-in-error-reporting-between-dev-and-prod "Direct link to Differences in error reporting between dev and prod") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Using a dev deployment any server error thrown on the client will include the original error message and a server-side stack trace to ease debugging. Using a production deployment any server error will be redacted to only include the name of the function and a generic `"Server Error"` message, with no stack trace. Server [application errors](https://docs.convex.dev/functions/error-handling/application-errors) will still include their custom `data`. Both development and production deployments log full errors with stack traces which can be found on the [Logs](https://docs.convex.dev/dashboard/deployments/logs) page of a given deployment. Application errors, expected failures[​](https://docs.convex.dev/functions/error-handling/#application-errors-expected-failures "Direct link to Application errors, expected failures") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- If you have expected ways your functions might fail, you can either return different values or throw `ConvexError`s. See [Application Errors](https://docs.convex.dev/functions/error-handling/application-errors) . Read/write limit errors[​](https://docs.convex.dev/functions/error-handling/#readwrite-limit-errors "Direct link to Read/write limit errors") ---------------------------------------------------------------------------------------------------------------------------------------------- To ensure uptime and guarantee performance, Convex will catch queries and mutations that try to read or write too much data. These limits are enforced at the level of a single query or mutation function execution. The exact limits are listed in [Limits](https://docs.convex.dev/production/state/limits#transactions) . Documents are "scanned" by the database to figure out which documents should be returned from `db.query`. So for example `db.query("table").take(5).collect()` will only need to scan 5 documents, but `db.query("table").filter(...).first()` might scan up to as many documents as there are in `"table"`, to find the first one that matches the given filter. The number of calls to `db.get` and `db.query` has a limit to prevent a single query from subscribing to too many index ranges, or a mutation from reading from too many ranges that could cause conflicts. In general, if you're running into these limits frequently, we recommend [indexing your queries](https://docs.convex.dev/database/reading-data/indexes/) to reduce the number of documents scanned, allowing you to avoid unnecessary reads. Queries that scan large swaths of your data may look innocent at first, but can easily blow up at any production scale. If your functions are close to hitting these limits they will log a warning. For information on other limits, see [here](https://docs.convex.dev/production/state/limits) . Debugging Errors[​](https://docs.convex.dev/functions/error-handling/#debugging-errors "Direct link to Debugging Errors") -------------------------------------------------------------------------------------------------------------------------- See [Debugging](https://docs.convex.dev/functions/debugging) and specifically [Finding relevant logs by Request ID](https://docs.convex.dev/functions/debugging#finding-relevant-logs-by-request-id) . Related Components[​](https://docs.convex.dev/functions/error-handling/#related-components "Direct link to Related Components") -------------------------------------------------------------------------------------------------------------------------------- [Convex Component\ \ ### 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\ \ ### Workflow\ \ Simplify programming long running code flows. Workflows execute durably with configurable retries and delays.](https://www.convex.dev/components/workflow) * [Errors in queries](https://docs.convex.dev/functions/error-handling/#errors-in-queries) * [Errors in mutations](https://docs.convex.dev/functions/error-handling/#errors-in-mutations) * [Errors in action functions](https://docs.convex.dev/functions/error-handling/#errors-in-action-functions) * [Differences in error reporting between dev and prod](https://docs.convex.dev/functions/error-handling/#differences-in-error-reporting-between-dev-and-prod) * [Application errors, expected failures](https://docs.convex.dev/functions/error-handling/#application-errors-expected-failures) * [Read/write limit errors](https://docs.convex.dev/functions/error-handling/#readwrite-limit-errors) * [Debugging Errors](https://docs.convex.dev/functions/error-handling/#debugging-errors) * [Related Components](https://docs.convex.dev/functions/error-handling/#related-components) --- # HTTP Actions | Convex Developer Hub [Skip to main content](https://docs.convex.dev/functions/http-actions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page HTTP actions allow you to build an HTTP API right in Convex! convex/http.ts TS import { httpRouter } from "convex/server";import { httpAction } from "./_generated/server";const http = httpRouter();http.route({ path: "/", method: "GET", handler: httpAction(async (ctx, request) => { return new Response(`Hello from ${request.url}`); }),});export default http; HTTP actions take in a [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and return a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) following the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) . HTTP actions can manipulate the request and response directly, and interact with data in Convex indirectly by running [queries](https://docs.convex.dev/functions/query-functions) , [mutations](https://docs.convex.dev/functions/mutation-functions) , and [actions](https://docs.convex.dev/functions/actions) . HTTP actions might be used for receiving webhooks from external applications or defining a public HTTP API. HTTP actions are exposed at `https://.convex.site` (e.g. `https://happy-animal-123.convex.site`). **Example:** [HTTP Actions](https://github.com/get-convex/convex-demos/tree/main/http) Defining HTTP actions[​](https://docs.convex.dev/functions/http-actions#defining-http-actions "Direct link to Defining HTTP actions") -------------------------------------------------------------------------------------------------------------------------------------- HTTP action handlers are defined using the [`httpAction`](https://docs.convex.dev/generated-api/server#httpaction) constructor, similar to the `action` constructor for normal actions: convex/myHttpActions.ts TS import { httpAction } from "./_generated/server";export const doSomething = httpAction(async () => { // implementation will be here return new Response();}); The first argument to the `handler` is an [`ActionCtx`](https://docs.convex.dev/api/interfaces/server.GenericActionCtx) object, which provides [`auth`](https://docs.convex.dev/api/interfaces/server.Auth) , [`storage`](https://docs.convex.dev/api/interfaces/server.StorageActionWriter) , and [`scheduler`](https://docs.convex.dev/api/interfaces/server.Scheduler) , as well as `runQuery`, `runMutation`, `runAction`. The second argument contains the [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) data. HTTP actions do not support argument validation, as the parsing of arguments from the incoming Request is left entirely to you. Here's an example: convex/messages.ts TS import { httpAction } from "./_generated/server";import { internal } from "./_generated/api";export const postMessage = httpAction(async (ctx, request) => { const { author, body } = await request.json(); await ctx.runMutation(internal.messages.sendOne, { body: `Sent via HTTP action: ${body}`, author, }); return new Response(null, { status: 200, });}); To expose the HTTP Action, export an instance of [`HttpRouter`](https://docs.convex.dev/api/classes/server.HttpRouter) from the `convex/http.ts` file. To create the instance call the `httpRouter` function. On the `HttpRouter` you can expose routes using the `route` method: convex/http.ts TS import { httpRouter } from "convex/server";import { postMessage, getByAuthor, getByAuthorPathSuffix } from "./messages";const http = httpRouter();http.route({ path: "/postMessage", method: "POST", handler: postMessage,});// Define additional routeshttp.route({ path: "/getMessagesByAuthor", method: "GET", handler: getByAuthor,});// Define a route using a path prefixhttp.route({ // Will match /getAuthorMessages/User+123 and /getAuthorMessages/User+234 etc. pathPrefix: "/getAuthorMessages/", method: "GET", handler: getByAuthorPathSuffix,});// Convex expects the router to be the default export of `convex/http.js`.export default http; You can now call this action via HTTP and interact with data stored in the Convex Database. HTTP actions are exposed on `https://.convex.site`. export DEPLOYMENT_NAME=... # example: "happy-animal-123"curl -d '{ "author": "User 123", "body": "Hello world" }' \ -H 'content-type: application/json' "https://$DEPLOYMENT_NAME.convex.site/postMessage" Like other Convex functions, you can view your HTTP actions in the [Functions view](https://docs.convex.dev/dashboard/deployments/functions) of [your dashboard](https://dashboard.convex.dev/) and view logs produced by them in the [Logs view](https://docs.convex.dev/dashboard/deployments/logs) . Limits[​](https://docs.convex.dev/functions/http-actions#limits "Direct link to Limits") ----------------------------------------------------------------------------------------- HTTP actions run in the same environment as queries and mutations so also do not have access to Node.js-specific JavaScript APIs. HTTP actions can call [actions](https://docs.convex.dev/functions/actions) , which can run in Node.js. Like [actions](https://docs.convex.dev/functions/actions#error-handling) , HTTP actions may have side-effects and will not be automatically retried by Convex when errors occur. It is a responsibility of the caller to handle errors and retry the request if appropriate. Request and response size is limited to 20MB. HTTP actions support request and response body types of `.text()`, `.json()`, `.blob()`, and `.arrayBuffer()`. Note that you don't need to define an HTTP action to call your queries, mutations and actions over HTTP if you control the caller, since you can use use the JavaScript [`ConvexHttpClient`](https://docs.convex.dev/api/classes/browser.ConvexHttpClient) or the [Python client](https://docs.convex.dev/client/python) to call these functions directly. Debugging[​](https://docs.convex.dev/functions/http-actions#debugging "Direct link to Debugging") -------------------------------------------------------------------------------------------------- ### Step 1: Check that your HTTP actions were deployed.[​](https://docs.convex.dev/functions/http-actions#step-1-check-that-your-http-actions-were-deployed "Direct link to Step 1: Check that your HTTP actions were deployed.") Check the [functions page](https://dashboard.convex.dev/deployment/functions) in the dashboard and make sure there's an entry called `http`. If not, double check that you've defined your HTTP actions with the `httpRouter` in a file called `http.js` or `http.ts` (the name of the file must match exactly), and that `npx convex dev` has no errors. ### Step 2: Check that you can access your endpoint using curl[​](https://docs.convex.dev/functions/http-actions#step-2-check-that-you-can-access-your-endpoint-using-curl "Direct link to Step 2: Check that you can access your endpoint using curl") Get your URL from the dashboard under [Settings](https://dashboard.convex.dev/deployment/settings) > URL and Deploy Key. Make sure this is the URL that ends in **`.convex.site`**, and not `.convex.cloud`. E.g. `https://happy-animal-123.convex.site` Run a `curl` command to hit one of your defined endpoints, potentially defining a new endpoint specifically for testing curl -X GET https://.convex.site/myEndpoint Check the [logs page](https://dashboard.convex.dev/deployment/logs) in the dashboard to confirm that there's an entry for your HTTP action. ### Step 3: Check the request being made by your browser[​](https://docs.convex.dev/functions/http-actions#step-3-check-the-request-being-made-by-your-browser "Direct link to Step 3: Check the request being made by your browser") If you've determined that your HTTP actions have been deployed and are accessible via curl, but there are still issues requesting them from your app, check the exact requests being made by your browser. Open the _Network_ tab in your browser's developer tools, and trigger your HTTP requests. Check that this URL matches what you tested earlier with curl -- it ends in `.convex.site` and has the right deployment name. You should be able to see these requests in the dashboard [logs page](https://dashboard.convex.dev/deployment/logs) . If you see "CORS error" or messages in the browser console like `Access to fetch at '...' from origin '...' has been blocked by CORS policy`, you likely need to configure CORS headers and potentially add a handler for the pre-flight `OPTIONS` request. See [this section](https://docs.convex.dev/functions/http-actions#cors) below. Common patterns[​](https://docs.convex.dev/functions/http-actions#common-patterns "Direct link to Common patterns") -------------------------------------------------------------------------------------------------------------------- ### File Storage[​](https://docs.convex.dev/functions/http-actions#file-storage "Direct link to File Storage") HTTP actions can be used to handle uploading and fetching stored files, see: * [Uploading files via an HTTP action](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-an-http-action) * [Serving files from HTTP actions](https://docs.convex.dev/file-storage/serve-files#serving-files-from-http-actions) ### CORS[​](https://docs.convex.dev/functions/http-actions#cors "Direct link to CORS") To make requests to HTTP actions from a website you need to add [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers to your HTTP actions. There are existing resources for exactly which CORS headers are required based on the use case. [This site](https://httptoolkit.com/will-it-cors/) provides an interactive walkthrough for what CORS headers to add. Here's an example of adding CORS headers to a Convex HTTP action: convex/http.ts TS import { httpRouter } from "convex/server";import { httpAction } from "./_generated/server";import { api } from "./_generated/api";import { Id } from "./_generated/dataModel";const http = httpRouter();http.route({ path: "/sendImage", method: "POST", handler: httpAction(async (ctx, request) => { // Step 1: Store the file const blob = await request.blob(); const storageId = await ctx.storage.store(blob); // Step 2: Save the storage ID to the database via a mutation const author = new URL(request.url).searchParams.get("author"); if (author === null) { return new Response("Author is required", { status: 400, }); } await ctx.runMutation(api.messages.sendImage, { storageId, author }); // Step 3: Return a response with the correct CORS headers return new Response(null, { status: 200, // CORS headers headers: new Headers({ // e.g. https://mywebsite.com, configured on your Convex dashboard "Access-Control-Allow-Origin": process.env.CLIENT_ORIGIN!, Vary: "origin", }), }); }),}); Here's an example of handling a pre-flight `OPTIONS` request: convex/http.ts TS // Pre-flight request for /sendImagehttp.route({ path: "/sendImage", method: "OPTIONS", handler: httpAction(async (_, request) => { // Make sure the necessary headers are present // for this to be a valid pre-flight request const headers = request.headers; if ( headers.get("Origin") !== null && headers.get("Access-Control-Request-Method") !== null && headers.get("Access-Control-Request-Headers") !== null ) { return new Response(null, { headers: new Headers({ // e.g. https://mywebsite.com, configured on your Convex dashboard "Access-Control-Allow-Origin": process.env.CLIENT_ORIGIN!, "Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Headers": "Content-Type, Digest", "Access-Control-Max-Age": "86400", }), }); } else { return new Response(); } }),}); ### Authentication[​](https://docs.convex.dev/functions/http-actions#authentication "Direct link to Authentication") You can leverage Convex's built-in [authentication](https://docs.convex.dev/auth) integration and access a user identity from [`ctx.auth.getUserIdentity()`](https://docs.convex.dev/api/interfaces/server.Auth#getuseridentity) . To do this call your endpoint with an `Authorization` header including a JWT token: myPage.ts TS const jwtToken = "...";fetch("https://.convex.site/myAction", { headers: { Authorization: `Bearer ${jwtToken}`, },}); * [Defining HTTP actions](https://docs.convex.dev/functions/http-actions#defining-http-actions) * [Limits](https://docs.convex.dev/functions/http-actions#limits) * [Debugging](https://docs.convex.dev/functions/http-actions#debugging) * [Step 1: Check that your HTTP actions were deployed.](https://docs.convex.dev/functions/http-actions#step-1-check-that-your-http-actions-were-deployed) * [Step 2: Check that you can access your endpoint using curl](https://docs.convex.dev/functions/http-actions#step-2-check-that-you-can-access-your-endpoint-using-curl) * [Step 3: Check the request being made by your browser](https://docs.convex.dev/functions/http-actions#step-3-check-the-request-being-made-by-your-browser) * [Common patterns](https://docs.convex.dev/functions/http-actions#common-patterns) * [File Storage](https://docs.convex.dev/functions/http-actions#file-storage) * [CORS](https://docs.convex.dev/functions/http-actions#cors) * [Authentication](https://docs.convex.dev/functions/http-actions#authentication) --- # Document IDs | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/document-ids#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page **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](https://docs.convex.dev/database/types#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`](https://docs.convex.dev/generated-api/data-model#id) TypeScript type based on your [schema](https://docs.convex.dev/database/schemas) 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`](https://docs.convex.dev/generated-api/data-model#id) type can be used to distinguish IDs from other strings at compile time. References and relationships[​](https://docs.convex.dev/database/document-ids#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](https://docs.convex.dev/database/reading-data/) 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[​](https://docs.convex.dev/database/document-ids#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[​](https://docs.convex.dev/database/document-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`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseReader#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](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) * [References and relationships](https://docs.convex.dev/database/document-ids#references-and-relationships) * [Trading off deeply nested documents vs. relationships](https://docs.convex.dev/database/document-ids#trading-off-deeply-nested-documents-vs-relationships) * [Serializing IDs](https://docs.convex.dev/database/document-ids#serializing-ids) --- # Backup & Restore | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/backup-restore#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex supports Backup & Restore of data via the [dashboard](https://dashboard.convex.dev/deployment/settings/backups) . ![Backups Page](https://docs.convex.dev/assets/images/backups-7e17da1541fc3eb26194a96ab33414ea.png) 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[​](https://docs.convex.dev/database/backup-restore#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[​](https://docs.convex.dev/database/backup-restore#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[​](https://docs.convex.dev/database/backup-restore#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](https://docs.convex.dev/cli#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](https://docs.convex.dev/file-storage) 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[​](https://docs.convex.dev/database/backup-restore#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](https://docs.convex.dev/database/import-export/import#restore-data-from-a-backup-zip-file) . FAQ[​](https://docs.convex.dev/database/backup-restore#faq "Direct link to FAQ") --------------------------------------------------------------------------------- ### Are there any limitations?[​](https://docs.convex.dev/database/backup-restore#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?[​](https://docs.convex.dev/database/backup-restore#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](https://docs.convex.dev/production/state/limits#database) for pricing details. ### What does the backup not contain?[​](https://docs.convex.dev/database/backup-restore#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`](https://docs.convex.dev/database/advanced/system-tables) system table. 3. Environment variables. Environment variables can be copied from Settings in the Convex dashboard. * [Periodic Backups](https://docs.convex.dev/database/backup-restore#periodic-backups) * [Restoring from backup](https://docs.convex.dev/database/backup-restore#restoring-from-backup) * [Restoring in an emergency](https://docs.convex.dev/database/backup-restore#restoring-in-an-emergency) * [Using the downloaded backup](https://docs.convex.dev/database/backup-restore#using-the-downloaded-backup) * [FAQ](https://docs.convex.dev/database/backup-restore#faq) * [Are there any limitations?](https://docs.convex.dev/database/backup-restore#are-there-any-limitations) * [How are they priced?](https://docs.convex.dev/database/backup-restore#how-are-they-priced) * [What does the backup not contain?](https://docs.convex.dev/database/backup-restore#what-does-the-backup-not-contain) --- # Writing Data | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/writing-data#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [Mutations](https://docs.convex.dev/functions/mutation-functions) can insert, update, and remove data from database tables. Inserting new documents[​](https://docs.convex.dev/database/writing-data#inserting-new-documents "Direct link to Inserting new documents") ------------------------------------------------------------------------------------------------------------------------------------------- You can create new documents in the database with the [`db.insert`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseWriter#insert) method: convex/tasks.ts TS import { mutation } from "./_generated/server";import { v } from "convex/values";export const createTask = mutation({ args: { text: v.string() }, handler: async (ctx, args) => { const taskId = await ctx.db.insert("tasks", { text: args.text }); // do something with `taskId` },}); The second argument to `db.insert` is a JavaScript object with data for the new document. The same types of values that can be passed into and returned from [queries](https://docs.convex.dev/functions/query-functions) and [mutations](https://docs.convex.dev/functions/mutation-functions) can be written into the database. See [Data Types](https://docs.convex.dev/database/types) for the full list of supported types. The `insert` method returns a globally unique ID for the newly inserted document. Updating existing documents[​](https://docs.convex.dev/database/writing-data#updating-existing-documents "Direct link to Updating existing documents") ------------------------------------------------------------------------------------------------------------------------------------------------------- Given an existing document ID the document can be updated using the following methods: 1. The [`db.patch`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseWriter#patch) method will patch an existing document, shallow merging it with the given partial document. New fields are added. Existing fields are overwritten. Fields set to `undefined` are removed. convex/tasks.ts TS import { mutation } from "./_generated/server";import { v } from "convex/values";export const updateTask = mutation({ args: { id: v.id("tasks") }, handler: async (ctx, args) => { const { id } = args; console.log(await ctx.db.get("tasks", id)); // { text: "foo", status: { done: true }, _id: ... } // Add `tag` and overwrite `status`: await ctx.db.patch("tasks", id, { tag: "bar", status: { archived: true } }); console.log(await ctx.db.get("tasks", id)); // { text: "foo", tag: "bar", status: { archived: true }, _id: ... } // Unset `tag` by setting it to `undefined` await ctx.db.patch("tasks", id, { tag: undefined }); console.log(await ctx.db.get("tasks", id)); // { text: "foo", status: { archived: true }, _id: ... } },}); 2. The [`db.replace`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseWriter#replace) method will replace the existing document entirely, potentially removing existing fields: convex/tasks.ts TS import { mutation } from "./_generated/server";import { v } from "convex/values";export const replaceTask = mutation({ args: { id: v.id("tasks") }, handler: async (ctx, args) => { const { id } = args; console.log(await ctx.db.get("tasks", id)); // { text: "foo", _id: ... } // Replace the whole document await ctx.db.replace("tasks", id, { invalid: true }); console.log(await ctx.db.get("tasks", id)); // { invalid: true, _id: ... } },}); Deleting documents[​](https://docs.convex.dev/database/writing-data#deleting-documents "Direct link to Deleting documents") ---------------------------------------------------------------------------------------------------------------------------- Given an existing document ID the document can be removed from the table with the [`db.delete`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseWriter#delete) method. convex/tasks.ts TS import { mutation } from "./_generated/server";import { v } from "convex/values";export const deleteTask = mutation({ args: { id: v.id("tasks") }, handler: async (ctx, args) => { await ctx.db.delete("tasks", args.id); },}); Bulk inserts or updates[​](https://docs.convex.dev/database/writing-data#bulk-inserts-or-updates "Direct link to Bulk inserts or updates") ------------------------------------------------------------------------------------------------------------------------------------------- If you are used to SQL you might be looking for some sort of bulk insert or bulk update statement. In Convex the entire `mutation` function is automatically a single transaction. You can just insert or update in a loop in the mutation function. Convex queues up all database changes in the function and executes them all in a single transaction when the function ends, leading to a single efficient change to the database. /** * Bulk insert multiple products into the database. * * Equivalent to the SQL: * ```sql * INSERT INTO products (product_id, product_name, category, price, in_stock) * VALUES * ('Laptop Pro', 'Electronics', 1299.99, true), * ('Wireless Mouse', 'Electronics', 24.95, true), * ('Ergonomic Keyboard', 'Electronics', 89.50, true), * ('Ultra HD Monitor', 'Electronics', 349.99, false), * ('Wireless Headphones', 'Audio', 179.99, true); * ``` */export const bulkInsertProducts = mutation({ args: { products: v.array( v.object({ product_name: v.string(), category: v.string(), price: v.number(), in_stock: v.boolean(), }), ), }, handler: async (ctx, args) => { const { products } = args; // Insert in a loop. This is efficient because Convex queues all the changes // to be executed in a single transaction when the mutation ends. for (const product of products) { const id = await ctx.db.insert("products", { product_name: product.product_name, category: product.category, price: product.price, in_stock: product.in_stock, }); } },}); Migrations[​](https://docs.convex.dev/database/writing-data#migrations "Direct link to Migrations") ---------------------------------------------------------------------------------------------------- Database migrations are done through the migration component. The component is designed to run online migrations to safely evolve your database schema over time. It allows you to resume from failures, and validate changes with dry runs. [Convex Component\ \ ### Migrations\ \ Framework for long running data migrations of live data.](https://www.convex.dev/components/migrations) Write performance and limits[​](https://docs.convex.dev/database/writing-data#write-performance-and-limits "Direct link to Write performance and limits") ---------------------------------------------------------------------------------------------------------------------------------------------------------- To prevent accidental writes of large amounts of records, queries and mutations enforce limits detailed [here](https://docs.convex.dev/production/state/limits#transactions) . * [Inserting new documents](https://docs.convex.dev/database/writing-data#inserting-new-documents) * [Updating existing documents](https://docs.convex.dev/database/writing-data#updating-existing-documents) * [Deleting documents](https://docs.convex.dev/database/writing-data#deleting-documents) * [Bulk inserts or updates](https://docs.convex.dev/database/writing-data#bulk-inserts-or-updates) * [Migrations](https://docs.convex.dev/database/writing-data#migrations) * [Write performance and limits](https://docs.convex.dev/database/writing-data#write-performance-and-limits) --- # Data Types | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/types#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page All Convex documents are defined as JavaScript objects. These objects can have field values of any of the types below. You can codify the shape of documents within your tables by [defining a schema](https://docs.convex.dev/database/schemas) . Convex values[​](https://docs.convex.dev/database/types#convex-values "Direct link to Convex values") ------------------------------------------------------------------------------------------------------ Convex supports the following types of values: | Convex Type | TS/JS Type | Example Usage | Validator for [Argument Validation](https://docs.convex.dev/functions/validation)
and [Schemas](https://docs.convex.dev/database/schemas) | `json` Format for [Export](https://docs.convex.dev/database/import-export) | Notes | | --- | --- | --- | --- | --- | --- | | Id | [Id](https://docs.convex.dev/database/document-ids)
([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)
) | `doc._id` | `v.id(tableName)` | string | See [Document IDs](https://docs.convex.dev/database/document-ids)
. | | Null | [null](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#null_type) | `null` | `v.null()` | null | JavaScript's `undefined` is not a valid Convex value. Functions the return `undefined` or do not return will return `null` when called from a client. Use `null` instead. | | Int64 | [bigint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#bigint_type) | `3n` | `v.int64()` | string (base10) | Int64s only support BigInts between -2^63 and 2^63-1. Convex supports `bigint`s in [most modern browsers](https://caniuse.com/bigint)
. | | Float64 | [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type) | `3.1` | `v.number()` | number / string | Convex supports all IEEE-754 double-precision floating point numbers (such as NaNs). Inf and NaN are JSON serialized as strings. | | Boolean | [boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type) | `true` | `v.boolean()` | bool | | | String | [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) | `"abc"` | `v.string()` | string | Strings are stored as UTF-8 and must be valid Unicode sequences. Strings must be smaller than the 1MB total size limit when encoded as UTF-8. | | Bytes | [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) | `new ArrayBuffer(8)` | `v.bytes()` | string (base64) | Convex supports first class bytestrings, passed in as `ArrayBuffer`s. Bytestrings must be smaller than the 1MB total size limit for Convex types. | | Array | [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | `[1, 3.2, "abc"]` | `v.array(values)` | array | Arrays can have at most 8192 values. | | Object | [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#objects) | `{a: "abc"}` | `v.object({property: value})` | object | Convex only supports "plain old JavaScript objects" (objects that do not have a custom prototype). Convex includes all [enumerable properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)
. Objects can have at most 1024 entries. Field names must be nonempty and not start with "$" or "\_". | | Record | [Record](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) | `{"a": "1", "b": "2"}` | `v.record(keys, values)` | object | Records are objects at runtime, but can have dynamic keys. Keys must be only ASCII characters, nonempty, and not start with "$" or "\_". | System fields[​](https://docs.convex.dev/database/types#system-fields "Direct link to System fields") ------------------------------------------------------------------------------------------------------ Every document in Convex has two automatically-generated system fields: * `_id`: The [document ID](https://docs.convex.dev/database/document-ids) of the document. * `_creationTime`: The time this document was created, in milliseconds since the Unix epoch. Limits[​](https://docs.convex.dev/database/types#limits "Direct link to Limits") --------------------------------------------------------------------------------- Convex values must be less than 1MB in total size. You can calculate the exact size of any value using [`getConvexSize`](https://docs.convex.dev/api/modules/values#getconvexsize) from `convex/values`. Documents can have nested values, either objects or arrays that contain other Convex types. Convex types can have at most 16 levels of nesting, and the cumulative size of a nested tree of values must be under the 1MB limit. Table names may contain alphanumeric characters ("a" to "z", "A" to "Z", and "0" to "9") and underscores ("\_"), and they cannot start with an underscore. For information on other limits, see [here](https://docs.convex.dev/production/state/limits) . If any of these limits don't work for you, [let us know](https://convex.dev/community) ! Working with `undefined`[​](https://docs.convex.dev/database/types#working-with-undefined "Direct link to working-with-undefined") ----------------------------------------------------------------------------------------------------------------------------------- The TypeScript value `undefined` is not a valid Convex value, so it cannot be used in Convex function arguments or return values, or in stored documents. 1. Objects/records with `undefined` values are the same as if the field were missing: `{a: undefined}` is transformed into `{}` when passed to a function or stored in the database. You can think of Convex function calls and the Convex database as serializing the data with `JSON.stringify`, which similarly removes `undefined` values. 2. Validators for object fields can use `v.optional(...)` to indicate that the field might not be present. * If an object's field "a" is missing, i.e. `const obj = {};`, then `obj.a === undefined`. This is a property of TypeScript/JavaScript, not specific to Convex. 3. You can use `undefined` in filters and index queries, and it will match documents that do not have the field. i.e. `.withIndex("by_a", q=>q.eq("a", undefined))` matches document `{}` and `{b: 1}`, but not `{a: 1}` or `{a: null, b: 1}`. * In Convex's ordering scheme, `undefined < null < all other values`, so you can match documents that _have_ a field via `q.gte("a", null as any)` or `q.gt("a", undefined)`. 4. There is exactly one case where `{a: undefined}` is different from `{}`: when passed to `ctx.db.patch`. Passing `{a: undefined}` removes the field "a" from the document, while passing `{}` does not change the field "a". See [Updating existing documents](https://docs.convex.dev/database/writing-data#updating-existing-documents) . 5. Since `undefined` gets stripped from function arguments but has meaning in `ctx.db.patch`, there are some tricks to pass patch's argument from the client. * If the client passing `args={}` (or `args={a: undefined}` which is equivalent) should leave the field "a" unchanged, use `ctx.db.patch(id, args)`. * If the client passing `args={}` should remove the field "a", use `ctx.db.patch(id, {a: undefined, ...args})`. * If the client passing `args={}` should leave the field "a" unchanged and `args={a: null}` should remove it, you could do if (args.a === null) { args.a = undefined;}await ctx.db.patch(tableName, id, args); 6. Functions that return a plain `undefined`/`void` are treated as if they returned `null`. 7. Arrays containing `undefined` values, like `[undefined]`, throw an error when used as Convex values. If you would prefer to avoid the special behaviors of `undefined`, you can use `null` instead, which _is_ a valid Convex value. Working with dates and times[​](https://docs.convex.dev/database/types#working-with-dates-and-times "Direct link to Working with dates and times") --------------------------------------------------------------------------------------------------------------------------------------------------- Convex does not have a special data type for working with dates and times. How you store dates depends on the needs of your application: 1. If you only care about a point in time, you can store a [UTC timestamp](https://en.wikipedia.org/wiki/Unix_time) . We recommend following the `_creationTime` field example, which stores the timestamp as a `number` in milliseconds. In your functions and on the client you can create a JavaScript `Date` by passing the timestamp to its constructor: `new Date(timeInMsSinceEpoch)`. You can then print the date and time in the desired time zone (such as your user's machine's configured time zone). * To get the current UTC timestamp in your function and store it in the database, use `Date.now()` 2. If you care about a calendar date or a specific clock time, such as when implementing a booking app, you should store the actual date and/or time as a string. If your app supports multiple timezones you should store the timezone as well. [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) is a common format for storing dates and times together in a single string like `"2024-03-21T14:37:15Z"`. If your users can choose a specific time zone you should probably store it in a separate `string` field, usually using the [IANA time zone name](https://en.wikipedia.org/wiki/Tz_database#Names_of_time_zones) (although you could concatenate the two fields with unique character like `"|"`). For more sophisticated printing (formatting) and manipulation of dates and times use one of the popular JavaScript libraries: [date-fns](https://date-fns.org/) , [Day.js](https://day.js.org/) , [Luxon](https://moment.github.io/luxon/) or [Moment.js](https://momentjs.com/) . * [Convex values](https://docs.convex.dev/database/types#convex-values) * [System fields](https://docs.convex.dev/database/types#system-fields) * [Limits](https://docs.convex.dev/database/types#limits) * [Working with `undefined`](https://docs.convex.dev/database/types#working-with-undefined) * [Working with dates and times](https://docs.convex.dev/database/types#working-with-dates-and-times) --- # Reading Data | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/reading-data/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [Query](https://docs.convex.dev/functions/query-functions) and [mutation](https://docs.convex.dev/functions/mutation-functions) functions can read data from database tables using _document ids_ and _document queries_. Reading a single document[​](https://docs.convex.dev/database/reading-data/#reading-a-single-document "Direct link to Reading a single document") -------------------------------------------------------------------------------------------------------------------------------------------------- Given a single document's id you can read its data with the [`db.get`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseReader#get) method: 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); // do something with `task` },}); **Note**: You should use the `v.id` validator like in the example above to make sure you are not exposing data from tables other than the ones you intended. Querying documents[​](https://docs.convex.dev/database/reading-data/#querying-documents "Direct link to Querying documents") ----------------------------------------------------------------------------------------------------------------------------- Document queries always begin by choosing the table to query with the [`db.query`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseReader#query) method: convex/tasks.ts TS import { query } from "./_generated/server";export const listTasks = query({ args: {}, handler: async (ctx) => { const tasks = await ctx.db.query("tasks").collect(); // do something with `tasks` },}); Then you can: 1. filter 2. order 3. and `await` the results We'll see how this works in the examples below. Filtering your query[​](https://docs.convex.dev/database/reading-data/#filtering-your-query "Direct link to Filtering your query") ----------------------------------------------------------------------------------------------------------------------------------- The best way to filter in Convex is to use indexes. Indexes build a special internal structure in your database to speed up lookups. There are two steps to using indexes: 1. Define the index in your `convex/schema.ts` file. 2. Query via the `withIndex()` syntax. ### 1\. Define the index[​](https://docs.convex.dev/database/reading-data/#1-define-the-index "Direct link to 1. Define the index") If you aren't familiar with how to create a Convex schema, read the [schema doc](https://docs.convex.dev/database/schemas) . Let’s assume you’re building a chat app and want to get all messages in a particular channel. You can define a new index called `by_channel` on the `messages` table by using the `.index()` method in your schema. convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";// Define a messages table with an index.export default defineSchema({ messages: defineTable({ channel: v.id("channels"), body: v.string(), user: v.id("users"), }).index("by_channel", ["channel"]),}); ### 2\. Filter a query with an index[​](https://docs.convex.dev/database/reading-data/#2-filter-a-query-with-an-index "Direct link to 2. Filter a query with an index") In your query function, you can now filter your `messages` table by using the `by_channel` index. const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q.eq("channel", channel)) .collect(); In Convex, you must explicitly use the `withIndex()` syntax to ensure your database uses the index. This differs from a more traditional SQL database, where the database implicitly chooses to use an index based on heuristics. The Convex approach leads to fewer surprises in the long run. You can create an index across multiple fields at once, query a specific range of data, and change the order of your query result. [Read the complete index documentation](https://docs.convex.dev/database/reading-data/indexes/) to learn more. Convex also supports a slower filtering mechanism that effectively loops through the table to match the filter. This can be useful if you know your table will be small (low thousands of rows), you're prototyping, or you want to filter an index query further. You can read more about filters [here](https://docs.convex.dev/database/reading-data/filters) . Ordering[​](https://docs.convex.dev/database/reading-data/#ordering "Direct link to Ordering") ----------------------------------------------------------------------------------------------- By default Convex always returns documents ordered by [`_creationTime`](https://docs.convex.dev/database/types#system-fields) . You can use [`.order("asc" | "desc")`](https://docs.convex.dev/api/interfaces/server.Query#order) to pick whether the order is ascending or descending. If the order isn't specified, it defaults to ascending. // Get all messages, oldest to newest.const messages = await ctx.db.query("messages").order("asc").collect(); // Get all messages, newest to oldest.const messages = await ctx.db.query("messages").order("desc").collect(); If you need to sort on a field other than `_creationTime` and your document query returns a small number of documents (on the order of hundreds rather than thousands of documents), consider sorting in JavaScript: // Get top 10 most liked messages, assuming messages is a fairly small table:const messages = await ctx.db.query("messages").collect();const topTenMostLikedMessages = recentMessages .sort((a, b) => b.likes - a.likes) .slice(0, 10); For document queries that return larger numbers of documents, you'll want to use an [index](https://docs.convex.dev/database/reading-data/indexes/) to improve the performance. Document queries that use indexes will be [ordered based on the columns in the index](https://docs.convex.dev/database/reading-data/indexes/#sorting-with-indexes) and can avoid slow table scans. // Get the top 20 most liked messages of all time, using the "by_likes" index.const messages = await ctx.db .query("messages") .withIndex("by_likes") .order("desc") .take(20); See [Limits](https://docs.convex.dev/database/reading-data/indexes/#limits) for details. ### Ordering of different types of values[​](https://docs.convex.dev/database/reading-data/#ordering-of-different-types-of-values "Direct link to Ordering of different types of values") A single field can have values of any [Convex type](https://docs.convex.dev/database/types) . When there are values of different types in an indexed field, their ascending order is as follows: No value set (`undefined`) < Null (`null`) < Int64 (`bigint`) < Float64 (`number`) < Boolean (`boolean`) < String (`string`) < Bytes (`ArrayBuffer`) < Array (`Array`) < Object (`Object`) The same ordering is used by the filtering comparison operators `q.lt()`, `q.lte()`, `q.gt()` and `q.gte()`. Retrieving results[​](https://docs.convex.dev/database/reading-data/#retrieving-results "Direct link to Retrieving results") ----------------------------------------------------------------------------------------------------------------------------- Most of our previous examples have ended the document query with the [`.collect()`](https://docs.convex.dev/api/interfaces/server.Query#collect) method, which returns all the documents that match your filters. Here are the other options for retrieving results. ### Taking `n` results[​](https://docs.convex.dev/database/reading-data/#taking-n-results "Direct link to taking-n-results") [`.take(n)`](https://docs.convex.dev/api/interfaces/server.Query#take) selects only the first `n` results that match your query. const users = await ctx.db.query("users").take(5); ### Finding the first result[​](https://docs.convex.dev/database/reading-data/#finding-the-first-result "Direct link to Finding the first result") [`.first()`](https://docs.convex.dev/api/interfaces/server.Query#first) selects the first document that matches your query and returns `null` if no documents were found. // We expect only one user with that email address.const userOrNull = await ctx.db .query("users") .withIndex("by_email", (q) => q.eq("email", "test@example.com")) .first(); ### Using a unique result[​](https://docs.convex.dev/database/reading-data/#using-a-unique-result "Direct link to Using a unique result") [`.unique()`](https://docs.convex.dev/api/interfaces/server.Query#unique) selects the single document from your query or returns `null` if no documents were found. If there are multiple results it will throw an exception. // Our counter table only has one document.const counterOrNull = await ctx.db.query("counter").unique(); ### Loading a page of results[​](https://docs.convex.dev/database/reading-data/#loading-a-page-of-results "Direct link to Loading a page of results") [`.paginate(opts)`](https://docs.convex.dev/api/interfaces/server.OrderedQuery#paginate) loads a page of results and returns a [`Cursor`](https://docs.convex.dev/api/modules/server#cursor) for loading additional results. See [Paginated Queries](https://docs.convex.dev/database/pagination) to learn more. More complex queries[​](https://docs.convex.dev/database/reading-data/#more-complex-queries "Direct link to More complex queries") ----------------------------------------------------------------------------------------------------------------------------------- Convex prefers to have a few, simple ways to walk through and select documents from tables. In Convex, there is no specific query language for complex logic like a join, an aggregation, or a group by. Instead, you can write the complex logic in JavaScript! Convex guarantees that the results will be consistent. ### Join[​](https://docs.convex.dev/database/reading-data/#join "Direct link to Join") Table join might look like: convex/events.ts TS import { query } from "./_generated/server";import { v } from "convex/values";export const eventAttendees = query({ args: { eventId: v.id("events") }, handler: async (ctx, args) => { const event = await ctx.db.get("events", args.eventId); return Promise.all( (event?.attendeeIds ?? []).map((userId) => ctx.db.get("users", userId)), ); },}); ### Aggregation[​](https://docs.convex.dev/database/reading-data/#aggregation "Direct link to Aggregation") Here's an example of computing an average: convex/purchases.ts TS import { query } from "./_generated/server";import { v } from "convex/values";export const averagePurchasePrice = query({ args: { email: v.string() }, handler: async (ctx, args) => { const userPurchases = await ctx.db .query("purchases") .withIndex("by_buyer", (q) => q.eq("buyer", args.email)) .collect(); const sum = userPurchases.reduce((a, { value: b }) => a + b, 0); return sum / userPurchases.length; },}); > If you need more scalable aggregate options (for example to handle frequent updates or large tables), consider using the [Sharded Counter](https://www.convex.dev/components/sharded-counter) > or [Aggregate](https://www.convex.dev/components/aggregate) > components. These components can help you handle high-throughput counters, sums, or computations without looping through the whole table. ### Group by[​](https://docs.convex.dev/database/reading-data/#group-by "Direct link to Group by") Here's an example of grouping and counting: convex/purchases.ts TS import { query } from "./_generated/server";import { v } from "convex/values";export const numPurchasesPerBuyer = query({ args: { email: v.string() }, handler: async (ctx, args) => { const userPurchases = await ctx.db.query("purchases").collect(); return userPurchases.reduce( (counts, { buyer }) => ({ ...counts, [buyer]: counts[buyer] ?? 0 + 1, }), {} as Record, ); },}); Explore the syntax on the dashboard[​](https://docs.convex.dev/database/reading-data/#explore-the-syntax-on-the-dashboard "Direct link to Explore the syntax on the dashboard") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can try out the syntax described above directly from the dashboard by [writing a custom test query](https://docs.convex.dev/dashboard/deployments/data#writing-custom-queries) . * [Reading a single document](https://docs.convex.dev/database/reading-data/#reading-a-single-document) * [Querying documents](https://docs.convex.dev/database/reading-data/#querying-documents) * [Filtering your query](https://docs.convex.dev/database/reading-data/#filtering-your-query) * [1\. Define the index](https://docs.convex.dev/database/reading-data/#1-define-the-index) * [2\. Filter a query with an index](https://docs.convex.dev/database/reading-data/#2-filter-a-query-with-an-index) * [Ordering](https://docs.convex.dev/database/reading-data/#ordering) * [Ordering of different types of values](https://docs.convex.dev/database/reading-data/#ordering-of-different-types-of-values) * [Retrieving results](https://docs.convex.dev/database/reading-data/#retrieving-results) * [Taking `n` results](https://docs.convex.dev/database/reading-data/#taking-n-results) * [Finding the first result](https://docs.convex.dev/database/reading-data/#finding-the-first-result) * [Using a unique result](https://docs.convex.dev/database/reading-data/#using-a-unique-result) * [Loading a page of results](https://docs.convex.dev/database/reading-data/#loading-a-page-of-results) * [More complex queries](https://docs.convex.dev/database/reading-data/#more-complex-queries) * [Join](https://docs.convex.dev/database/reading-data/#join) * [Aggregation](https://docs.convex.dev/database/reading-data/#aggregation) * [Group by](https://docs.convex.dev/database/reading-data/#group-by) * [Explore the syntax on the dashboard](https://docs.convex.dev/database/reading-data/#explore-the-syntax-on-the-dashboard) --- # Data Import & Export | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/import-export/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! If you're bootstrapping your app from existing data, Convex provides three ways to get the data in: * Import from csv/json into a single table via the [CLI](https://docs.convex.dev/database/import-export/import#single-table-import) . * Restore from a backup via the [dashboard](https://docs.convex.dev/database/backup-restore) or [CLI](https://docs.convex.dev/database/import-export/import#restore-data-from-a-backup-zip-file) . * [Streaming import](https://docs.convex.dev/production/integrations/streaming-import-export) from any existing database via Airbyte destination connector. You can export data from Convex in two ways. * Download a backup as a zip from the [dashboard](https://docs.convex.dev/database/backup-restore) . * Set up [streaming export](https://docs.convex.dev/production/integrations/streaming-import-export) to any external database via Fivetran or Airbyte. Great for connecting to a custom BI setup (eg [Snowflake](https://www.snowflake.com/) , [Databricks](https://www.databricks.com/) , or [BigQuery](https://cloud.google.com/bigquery) ): Data Import & Export is in beta Data Import & Export is currently a [beta feature](https://docs.convex.dev/production/state/#beta-features) . If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community) ! --- # Schemas | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/schemas#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page A schema is a description of 1. the tables in your Convex project 2. the type of documents within your tables While it is possible to use Convex _without_ defining a schema, adding a `schema.ts` file will ensure that the documents in your tables are the correct type. If you're using [TypeScript](https://docs.convex.dev/understanding/best-practices/typescript) , adding a schema will also give you end-to-end type safety throughout your app. We recommend beginning your project without a schema for rapid prototyping and then adding a schema once you've solidified your plan. To learn more see our [Schema Philosophy](https://docs.convex.dev/database/advanced/schema-philosophy) . **Example:** [TypeScript and Schemas](https://github.com/get-convex/convex-demos/tree/main/typescript) Writing schemas[​](https://docs.convex.dev/database/schemas#writing-schemas "Direct link to Writing schemas") -------------------------------------------------------------------------------------------------------------- Schemas are defined in a `schema.ts` file in your `convex/` directory and look like: convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ messages: defineTable({ body: v.string(), user: v.id("users"), }), users: defineTable({ name: v.string(), tokenIdentifier: v.string(), }).index("by_token", ["tokenIdentifier"]),}); This schema (which is based on our [users and auth example](https://github.com/get-convex/convex-demos/tree/main/users-and-auth) ), has 2 tables: messages and users. Each table is defined using the [`defineTable`](https://docs.convex.dev/api/modules/server#definetable) function. Within each table, the document type is defined using the validator builder, [`v`](https://docs.convex.dev/api/modules/values#v) . In addition to the fields listed, Convex will also automatically add `_id` and `_creationTime` fields. To learn more, see [System Fields](https://docs.convex.dev/database/types#system-fields) . Generating a Schema While writing your schema, it can be helpful to consult the [Convex Dashboard](https://docs.convex.dev/dashboard/deployments/data#generating-a-schema) . The "Generate Schema" button in the "Data" view suggests a schema declaration based on the data in your tables. ### Validators[​](https://docs.convex.dev/database/schemas#validators "Direct link to Validators") The validator builder, [`v`](https://docs.convex.dev/api/modules/values#v) is used to define the type of documents in each table. It has methods for each of [Convex's types](https://docs.convex.dev/database/types) : convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ documents: defineTable({ id: v.id("documents"), string: v.string(), number: v.number(), boolean: v.boolean(), nestedObject: v.object({ property: v.string(), }), }),}); It additionally allows you to define unions, optional property, string literals, and more. [Argument validation](https://docs.convex.dev/functions/validation) and schemas both use the same validator builder, `v`. #### Optional fields[​](https://docs.convex.dev/database/schemas#optional-fields "Direct link to Optional fields") You can describe optional fields by wrapping their type with `v.optional(...)`: defineTable({ optionalString: v.optional(v.string()), optionalNumber: v.optional(v.number()),}); This corresponds to marking fields as optional with `?` in TypeScript. #### Unions[​](https://docs.convex.dev/database/schemas#unions "Direct link to Unions") You can describe fields that could be one of multiple types using `v.union`: defineTable({ stringOrNumber: v.union(v.string(), v.number()),}); If your table stores multiple different types of documents, you can use `v.union` at the top level: defineTable( v.union( v.object({ kind: v.literal("StringDocument"), value: v.string(), }), v.object({ kind: v.literal("NumberDocument"), value: v.number(), }), ),); In this schema, documents either have a `kind` of `"StringDocument"` and a string for their `value`: { "kind": "StringDocument", "value": "abc"} or they have a `kind` of `"NumberDocument"` and a number for their `value`: { "kind": "NumberDocument", "value": 123} #### Literals[​](https://docs.convex.dev/database/schemas#literals "Direct link to Literals") Fields that are a constant can be expressed with `v.literal`: defineTable({ oneTwoOrThree: v.union( v.literal("one"), v.literal("two"), v.literal("three"), ),}); #### Record objects[​](https://docs.convex.dev/database/schemas#record-objects "Direct link to Record objects") You can describe objects that map arbitrary keys to values with `v.record`: defineTable({ simpleMapping: v.record(v.string(), v.boolean()),}); You can use other types of string validators for the keys: import { mutation } from "./_generated/server";import { v } from "convex/values";export default mutation({ args: { userIdToValue: v.record(v.id("users"), v.boolean()), }, handler: async ({ db }, { userIdToValue }) => { //... },}); Notes: * This type corresponds to the [Record](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) type in TypeScript * You cannot use string literals as a `record` key * Using `v.string()` as a `record` key validator will only allow ASCII characters #### Any[​](https://docs.convex.dev/database/schemas#any "Direct link to Any") Fields or documents that could take on any value can be represented with `v.any()`: defineTable({ anyValue: v.any(),}); This corresponds to the `any` type in TypeScript. ### Options[​](https://docs.convex.dev/database/schemas#options "Direct link to Options") These options are passed as part of the [options](https://docs.convex.dev/api/interfaces/server.DefineSchemaOptions) argument to [`defineSchema`](https://docs.convex.dev/api/modules/server#defineschema) . #### `schemaValidation: boolean`[​](https://docs.convex.dev/database/schemas#schemavalidation-boolean "Direct link to schemavalidation-boolean") Whether Convex should validate at runtime that your documents match your schema. By default, Convex will enforce that all new and existing documents match your schema. You can disable `schemaValidation` by passing in `schemaValidation: false`: defineSchema( { // Define tables here. }, { schemaValidation: false, },); When `schemaValidation` is disabled, Convex will not validate that new or existing documents match your schema. You'll still get schema-specific TypeScript types, but there will be no validation at runtime that your documents match those types. #### `strictTableNameTypes: boolean`[​](https://docs.convex.dev/database/schemas#stricttablenametypes-boolean "Direct link to stricttablenametypes-boolean") Whether the TypeScript types should allow accessing tables not in the schema. By default, the TypeScript table name types produced by your schema are strict. That means that they will be a union of strings (ex. `"messages" | "users"`) and only support accessing tables explicitly listed in your schema. Sometimes it's useful to only define part of your schema. For example, if you are rapidly prototyping, it could be useful to try out a new table before adding it your `schema.ts` file. You can disable `strictTableNameTypes` by passing in `strictTableNameTypes: false`: defineSchema( { // Define tables here. }, { strictTableNameTypes: false, },); When `strictTableNameTypes` is disabled, the TypeScript types will allow access to tables not listed in the schema and their document type will be `any`. Regardless of the value of `strictTableNameTypes`, your schema will only validate documents in the tables listed in the schema. You can still create and modify documents in other tables in JavaScript or on the dashboard (they just won't be validated). Schema validation[​](https://docs.convex.dev/database/schemas#schema-validation "Direct link to Schema validation") -------------------------------------------------------------------------------------------------------------------- Schemas are pushed automatically in [`npx convex dev`](https://docs.convex.dev/cli#run-the-convex-dev-server) and [`npx convex deploy`](https://docs.convex.dev/cli#deploy-convex-functions-to-production) . The first push after a schema is added or modified will validate that all existing documents match the schema. If there are documents that fail validation, the push will fail. After the schema is pushed, Convex will validate that all future document inserts and updates match the schema. Schema validation is skipped if [`schemaValidation`](https://docs.convex.dev/database/schemas#schemavalidation-boolean) is set to `false`. Note that schemas only validate documents in the tables listed in the schema. You can still create and modify documents in other tables (they just won't be validated). ### Circular references[​](https://docs.convex.dev/database/schemas#circular-references "Direct link to Circular references") You might want to define a schema with circular ID references like: convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ users: defineTable({ preferencesId: v.id("preferences"), }), preferences: defineTable({ userId: v.id("users"), }),}); In this schema, documents in the `users` table contain a reference to documents in `preferences` and vice versa. Because schema validation enforces your schema on every `db.insert`, `db.replace`, and `db.patch` call, creating circular references like this is not possible. The easiest way around this is to make one of the references nullable: convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ users: defineTable({ preferencesId: v.id("preferences"), }), preferences: defineTable({ userId: v.union(v.id("users"), v.null()), }),}); This way you can create a preferences document first, then create a user document, then set the reference on the preferences document: convex/users.ts TS import { mutation } from "./_generated/server";export default mutation({ handler: async (ctx) => { const preferencesId = await ctx.db.insert("preferences", {}); const userId = await ctx.db.insert("users", { preferencesId }); await ctx.db.patch("preferences", preferencesId, { userId }); },}); [Let us know](https://docs.convex.dev/production/contact) if you need better support for circular references. TypeScript types[​](https://docs.convex.dev/database/schemas#typescript-types "Direct link to TypeScript types") ----------------------------------------------------------------------------------------------------------------- Once you've defined a schema, [`npx convex dev`](https://docs.convex.dev/cli#run-the-convex-dev-server) will produce new versions of [`dataModel.d.ts`](https://docs.convex.dev/generated-api/data-model) and [`server.d.ts`](https://docs.convex.dev/generated-api/server) with types based on your schema. ### `Doc`[​](https://docs.convex.dev/database/schemas#doctablename "Direct link to doctablename") The [`Doc`](https://docs.convex.dev/generated-api/data-model#doc) TypeScript type from [`dataModel.d.ts`](https://docs.convex.dev/generated-api/data-model) provides document types for all of your tables. You can use these both when writing Convex functions and in your React components: MessageView.tsx import { Doc } from "../convex/_generated/dataModel";function MessageView(props: { message: Doc<"messages"> }) { ...} If you need the type for a portion of a document, use the [`Infer` type helper](https://docs.convex.dev/functions/validation#extracting-typescript-types) . ### `query` and `mutation`[​](https://docs.convex.dev/database/schemas#query-and-mutation "Direct link to query-and-mutation") The [`query`](https://docs.convex.dev/generated-api/server#query) and [`mutation`](https://docs.convex.dev/generated-api/server#mutation) functions in [`server.js`](https://docs.convex.dev/generated-api/server) have the same API as before but now provide a `db` with more precise types. Functions like [`db.insert(table, document)`](https://docs.convex.dev/api/interfaces/server.GenericDatabaseWriter#insert) now understand your schema. Additionally [database queries](https://docs.convex.dev/database/reading-data/) will now return the correct document type (not `any`). Related posts from [![Stack](https://docs.convex.dev/img/stack-logo-dark.svg)![Stack](https://docs.convex.dev/img/stack-logo-light.svg)](https://stack.convex.dev/) * [Writing schemas](https://docs.convex.dev/database/schemas#writing-schemas) * [Validators](https://docs.convex.dev/database/schemas#validators) * [Optional fields](https://docs.convex.dev/database/schemas#optional-fields) * [Unions](https://docs.convex.dev/database/schemas#unions) * [Literals](https://docs.convex.dev/database/schemas#literals) * [Record objects](https://docs.convex.dev/database/schemas#record-objects) * [Any](https://docs.convex.dev/database/schemas#any) * [Options](https://docs.convex.dev/database/schemas#options) * [`schemaValidation: boolean`](https://docs.convex.dev/database/schemas#schemavalidation-boolean) * [`strictTableNameTypes: boolean`](https://docs.convex.dev/database/schemas#stricttablenametypes-boolean) * [Schema validation](https://docs.convex.dev/database/schemas#schema-validation) * [Circular references](https://docs.convex.dev/database/schemas#circular-references) * [TypeScript types](https://docs.convex.dev/database/schemas#typescript-types) * [`Doc`](https://docs.convex.dev/database/schemas#doctablename) * [`query` and `mutation`](https://docs.convex.dev/database/schemas#query-and-mutation) --- # Paginated Queries | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/pagination#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Paginated queries are [queries](https://docs.convex.dev/functions/query-functions) that return a list of results in incremental pages. This can be used to build components with "Load More" buttons or "infinite scroll" UIs where more results are loaded as the user scrolls. **Example:** [Paginated Messaging App](https://github.com/get-convex/convex-demos/tree/main/pagination) Using pagination in Convex is as simple as: 1. Writing a paginated query function that calls [`.paginate(paginationOpts)`](https://docs.convex.dev/api/interfaces/server.OrderedQuery#paginate) . 2. Using the [`usePaginatedQuery`](https://docs.convex.dev/api/modules/react#usepaginatedquery) React hook. Like other Convex queries, paginated queries are completely reactive. Writing paginated query functions[​](https://docs.convex.dev/database/pagination#writing-paginated-query-functions "Direct link to Writing paginated query functions") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- Convex uses cursor-based pagination. This means that paginated queries return a string called a [`Cursor`](https://docs.convex.dev/api/modules/server#cursor) that represents the point in the results that the current page ended. To load more results, you simply call the query function again, passing in the cursor. To build this in Convex, define a query function that: 1. Takes in a single arguments object with a `paginationOpts` property of type [`PaginationOptions`](https://docs.convex.dev/api/interfaces/server.PaginationOptions) . * `PaginationOptions` is an object with `numItems` and `cursor` fields. * Use `paginationOptsValidator` exported from `"convex/server"` to [validate](https://docs.convex.dev/functions/validation) this argument * The arguments object may include properties as well. 2. Calls [`.paginate(paginationOpts)`](https://docs.convex.dev/api/interfaces/server.OrderedQuery#paginate) on a [database query](https://docs.convex.dev/database/reading-data/) , passing in the `PaginationOptions` and returning its result. * The returned `page` in the [`PaginationResult`](https://docs.convex.dev/api/interfaces/server.PaginationResult) is an array of documents. You may [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) or [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) it before returning it. convex/messages.ts TS import { v } from "convex/values";import { query, mutation } from "./_generated/server";import { paginationOptsValidator } from "convex/server";export const list = query({ args: { paginationOpts: paginationOptsValidator }, handler: async (ctx, args) => { const foo = await ctx.db .query("messages") .order("desc") .paginate(args.paginationOpts); return foo; },}); ### Additional arguments[​](https://docs.convex.dev/database/pagination#additional-arguments "Direct link to Additional arguments") You can define paginated query functions that take arguments in addition to `paginationOpts`: convex/messages.ts TS export const listWithExtraArg = query({ args: { paginationOpts: paginationOptsValidator, author: v.string() }, handler: async (ctx, args) => { return await ctx.db .query("messages") .withIndex("by_author", (q) => q.eq("author", args.author)) .order("desc") .paginate(args.paginationOpts); },}); ### Transforming results[​](https://docs.convex.dev/database/pagination#transforming-results "Direct link to Transforming results") You can apply arbitrary [transformations](https://docs.convex.dev/database/reading-data/#more-complex-queries) to the `page` property of the object returned by `paginate`, which contains the array of documents: convex/messages.ts TS export const listWithTransformation = query({ args: { paginationOpts: paginationOptsValidator }, handler: async (ctx, args) => { const results = await ctx.db .query("messages") .order("desc") .paginate(args.paginationOpts); return { ...results, page: results.page.map((message) => ({ author: message.author.slice(0, 1), body: message.body.toUpperCase(), })), }; },}); Paginating within React Components[​](https://docs.convex.dev/database/pagination#paginating-within-react-components "Direct link to Paginating within React Components") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- To paginate within a React component, use the [`usePaginatedQuery`](https://docs.convex.dev/api/modules/react#usepaginatedquery) hook. This hook gives you a simple interface for rendering the current items and requesting more. Internally, this hook manages the continuation cursors. The arguments to this hook are: * The name of the paginated query function. * The arguments object to pass to the query function, excluding the `paginationOpts` (that's injected by the hook). * An options object with the `initialNumItems` to load on the first page. The hook returns an object with: * `results`: An array of the currently loaded results. * `isLoading` - Whether the hook is currently loading results. * `status`: The status of the pagination. The possible statuses are: * `"LoadingFirstPage"`: The hook is loading the first page of results. * `"CanLoadMore"`: This query may have more items to fetch. Call `loadMore` to fetch another page. * `"LoadingMore"`: We're currently loading another page of results. * `"Exhausted"`: We've paginated to the end of the list. * `loadMore(n)`: A callback to fetch more results. This will only fetch more results if the status is `"CanLoadMore"`. src/App.tsx TS import { usePaginatedQuery } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const { results, status, loadMore } = usePaginatedQuery( api.messages.list, {}, { initialNumItems: 5 }, ); return (
{results?.map(({ _id, body }) =>
{body}
)}
);} You can also pass additional arguments in the arguments object if your function expects them: src/App.tsx TS import { usePaginatedQuery } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const { results, status, loadMore } = usePaginatedQuery( api.messages.listWithExtraArg, { author: "Alex" }, { initialNumItems: 5 }, ); return (
{results?.map(({ _id, body }) =>
{body}
)}
);} ### Reactivity[​](https://docs.convex.dev/database/pagination#reactivity "Direct link to Reactivity") Like any other Convex query functions, paginated queries are **completely reactive**. Your React components will automatically rerender if items in your paginated list are added, removed or changed. One consequence of this is that **page sizes in Convex may change!** If you request a page of 10 items and then one item is removed, this page may "shrink" to only have 9 items. Similarly if new items are added, a page may "grow" beyond its initial size. Paginating manually[​](https://docs.convex.dev/database/pagination#paginating-manually "Direct link to Paginating manually") ----------------------------------------------------------------------------------------------------------------------------- If you're paginating outside of React, you can manually call your paginated function multiple times to collect the items: download.ts TS import { ConvexHttpClient } from "convex/browser";import { api } from "../convex/_generated/api";import * as dotenv from "dotenv";dotenv.config();const client = new ConvexHttpClient(process.env.VITE_CONVEX_URL!);/** * Logs an array containing all messages from the paginated query "listMessages" * by combining pages of results into a single array. */async function getAllMessages() { let continueCursor = null; let isDone = false; let page; const results = []; while (!isDone) { ({ continueCursor, isDone, page } = await client.query(api.messages.list, { paginationOpts: { numItems: 5, cursor: continueCursor }, })); console.log("got", page.length); results.push(...page); } console.log(results);}getAllMessages(); * [Writing paginated query functions](https://docs.convex.dev/database/pagination#writing-paginated-query-functions) * [Additional arguments](https://docs.convex.dev/database/pagination#additional-arguments) * [Transforming results](https://docs.convex.dev/database/pagination#transforming-results) * [Paginating within React Components](https://docs.convex.dev/database/pagination#paginating-within-react-components) * [Reactivity](https://docs.convex.dev/database/pagination#reactivity) * [Paginating manually](https://docs.convex.dev/database/pagination#paginating-manually) --- # Indexes | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/reading-data/indexes/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Indexes are a data structure that allow you to speed up your [document queries](https://docs.convex.dev/database/reading-data/#querying-documents) by telling Convex how to organize your documents. Indexes also allow you to change the order of documents in query results. For a more in-depth introduction to indexing see [Indexes and Query Performance](https://docs.convex.dev/database/reading-data/indexes/indexes-and-query-perf) . Defining indexes[​](https://docs.convex.dev/database/reading-data/indexes/#defining-indexes "Direct link to Defining indexes") ------------------------------------------------------------------------------------------------------------------------------- Indexes are defined as part of your Convex [schema](https://docs.convex.dev/database/schemas) . Each index consists of: 1. A name. * Must be unique per table. 2. An ordered list of fields to index. * To specify a field on a nested document, use a dot-separated path like `properties.name`. To add an index onto a table, use the [`index`](https://docs.convex.dev/api/classes/server.TableDefinition#index) method on your table's schema: convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";// Define a messages table with two indexes.export default defineSchema({ messages: defineTable({ channel: v.id("channels"), body: v.string(), user: v.id("users"), }) .index("by_channel", ["channel"]) .index("by_channel_user", ["channel", "user"]),}); The `by_channel` index is ordered by the `channel` field defined in the schema. For messages in the same channel, they are ordered by the [system-generated `_creationTime` field](https://docs.convex.dev/database/types#system-fields) which is added to all indexes automatically. By contrast, the `by_channel_user` index orders messages in the same `channel` by the `user` who sent them, and only then by `_creationTime`. Indexes are created in [`npx convex dev`](https://docs.convex.dev/cli#run-the-convex-dev-server) and [`npx convex deploy`](https://docs.convex.dev/cli#deploy-convex-functions-to-production) . You may notice that the first deploy that defines an index is a bit slower than normal. This is because Convex needs to _backfill_ your index. The more data in your table, the longer it will take Convex to organize it in index order. If you need to add indexes to large tables, use a [staged index](https://docs.convex.dev/database/reading-data/indexes/#staged-indexes) . You can feel free to query an index in the same deploy that defines it. Convex will ensure that the index is backfilled before the new query and mutation functions are registered. Be careful when removing indexes In addition to adding new indexes, `npx convex deploy` will delete indexes that are no longer present in your schema. Make sure that your indexes are completely unused before removing them from your schema! Querying documents using indexes[​](https://docs.convex.dev/database/reading-data/indexes/#querying-documents-using-indexes "Direct link to Querying documents using indexes") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- A query for "messages in `channel` created 1-2 minutes ago" over the `by_channel` index would look like: const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q .eq("channel", channel) .gt("_creationTime", Date.now() - 2 * 60000) .lt("_creationTime", Date.now() - 60000), ) .collect(); The [`.withIndex`](https://docs.convex.dev/api/interfaces/server.QueryInitializer#withindex) method defines which index to query and how Convex will use that index to select documents. The first argument is the name of the index and the second is an _index range expression_. An index range expression is a description of which documents Convex should consider when running the query. The choice of index both affects how you write the index range expression and what order the results are returned in. For instance, by making both a `by_channel` and `by_channel_user` index, we can get results within a channel ordered by `_creationTime` or by `user`, respectively. If you were to use the `by_channel_user` index like this: const messages = await ctx.db .query("messages") .withIndex("by_channel_user", (q) => q.eq("channel", channel)) .collect(); The results would be all of the messages in a `channel` ordered by `user`, then by `_creationTime`. If you were to use `by_channel_user` like this: const messages = await ctx.db .query("messages") .withIndex("by_channel_user", (q) => q.eq("channel", channel).eq("user", user), ) .collect(); The results would be the messages in the given `channel` sent by `user`, ordered by `_creationTime`. An index range expression is always a chained list of: 1. 0 or more equality expressions defined with [`.eq`](https://docs.convex.dev/api/interfaces/server.IndexRangeBuilder#eq) . 2. \[Optionally\] A lower bound expression defined with [`.gt`](https://docs.convex.dev/api/interfaces/server.IndexRangeBuilder#gt) or [`.gte`](https://docs.convex.dev/api/interfaces/server.IndexRangeBuilder#gte) . 3. \[Optionally\] An upper bound expression defined with [`.lt`](https://docs.convex.dev/api/interfaces/server.IndexRangeBuilder#lt) or [`.lte`](https://docs.convex.dev/api/interfaces/server.IndexRangeBuilder#lte) . **You must step through fields in index order.** Each equality expression must compare a different index field, starting from the beginning and in order. The upper and lower bounds must follow the equality expressions and compare the next field. For example, it is not possible to write a query like: // DOES NOT COMPILE!const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q .gt("_creationTime", Date.now() - 2 * 60000) .lt("_creationTime", Date.now() - 60000), ) .collect(); This query is invalid because the `by_channel` index is ordered by `(channel, _creationTime)` and this query range has a comparison on `_creationTime` without first restricting the range to a single `channel`. Because the index is sorted first by `channel` and then by `_creationTime`, it isn't a useful index for finding messages in all channels created 1-2 minutes ago. The TypeScript types within `withIndex` will guide you through this. To better understand what queries can be run over which indexes, see [Introduction to Indexes and Query Performance](https://docs.convex.dev/database/reading-data/indexes/indexes-and-query-perf) . **The performance of your query is based on the specificity of the range.** For example, if the query is const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q .eq("channel", channel) .gt("_creationTime", Date.now() - 2 * 60000) .lt("_creationTime", Date.now() - 60000), ) .collect(); then query's performance would be based on the number of messages in `channel` created 1-2 minutes ago. If the index range is not specified, all documents in the index will be considered in the query. Picking a good index range For performance, define index ranges that are as specific as possible! If you are querying a large table and you're unable to add any equality conditions with `.eq`, you should consider defining a new index. `.withIndex` is designed to only allow you to specify ranges that Convex can efficiently use your index to find. For all other filtering you can use the [`.filter`](https://docs.convex.dev/api/interfaces/server.Query#filter) method. For example to query for "messages in `channel` **not** created by me" you could do: const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q.eq("channel", channel)) .filter((q) => q.neq(q.field("user"), myUserId)) .collect(); In this case the performance of this query will be based on how many messages are in the channel. Convex will consider each message in the channel and only return the messages where the `user` field doesn't match `myUserId`. Sorting with indexes[​](https://docs.convex.dev/database/reading-data/indexes/#sorting-with-indexes "Direct link to Sorting with indexes") ------------------------------------------------------------------------------------------------------------------------------------------- Queries that use `withIndex` are ordered by the columns specified in the index. The order of the columns in the index dictates the priority for sorting. The values of the columns listed first in the index are compared first. Subsequent columns are only compared as tie breakers only if all earlier columns match. Since Convex automatically includes `_creationTime` as the last column in all indexes, `_creationTime` will always be the final tie breaker if all other columns in the index are equal. For example, `by_channel_user` includes `channel`, `user`, and `_creationTime`. So queries on `messages` that use `.withIndex("by_channel_user")` will be sorted first by channel, then by user within each channel, and finally by the creation time. Sorting with indexes allows you to satisfy use cases like displaying the top `N` scoring users, the most recent `N` transactions, or the most `N` liked messages. For example, to get the top 10 highest scoring players in your game, you might define an index on the player's highest score: export default defineSchema({ players: defineTable({ username: v.string(), highestScore: v.number(), }).index("by_highest_score", ["highestScore"]),}); You can then efficiently find the top 10 highest scoring players using your index and [`take(10)`](https://docs.convex.dev/api/interfaces/server.Query#take) : const topScoringPlayers = await ctx.db .query("users") .withIndex("by_highest_score") .order("desc") .take(10); In this example, the range expression is omitted because we're looking for the highest scoring players of all time. This particular query is reasonably efficient for large data sets only because we're using `take()`. If you use an index without a range expression, you should always use one of the following in conjunction with `withIndex`: 1. [`.first()`](https://docs.convex.dev/api/interfaces/server.Query#first) 2. [`.unique()`](https://docs.convex.dev/api/interfaces/server.Query#unique) 3. [`.take(n)`](https://docs.convex.dev/api/interfaces/server.Query#take) 4. [`.paginate(ops)`](https://docs.convex.dev/database/pagination) These APIs allow you to efficiently limit your query to a reasonable size without performing a full table scan. Full Table Scans When your query fetches documents from the database, it will scan the rows in the range you specify. If you are using `.collect()`, for instance, it will scan all of the rows in the range. So if you use `withIndex` without a range expression, you will be [scanning the whole table](https://docs.convex.dev/database/indexes/indexes-and-query-perf#full-table-scans) , which can be slow when your table has thousands of rows. `.filter()` doesn't affect which documents are scanned. Using `.first()` or `.unique()` or `.take(n)` will only scan rows until it has enough documents. You can include a range expression to satisfy more targeted queries. For example, to get the top scoring players in Canada, you might use both `take()` and a range expression: // query the top 10 highest scoring players in Canada.const topScoringPlayers = await ctx.db .query("users") .withIndex("by_country_highest_score", (q) => q.eq("country", "CA")) .order("desc") .take(10); Staged indexes[​](https://docs.convex.dev/database/reading-data/indexes/#staged-indexes "Direct link to Staged indexes") ------------------------------------------------------------------------------------------------------------------------- By default, index creation happens synchronously when you deploy code. For large tables, the process of [backfilling the index](https://docs.convex.dev/database/reading-data/indexes/indexes-and-query-perf#backfilling-and-maintaining-indexes) for the existing table can be slow. Staged indexes are a way to create an index on a large table asynchronously without blocking deploy. This can be useful if you are working on multiple features at once. To create a staged index, use the following syntax in your `schema.ts`. export default defineSchema({ messages: defineTable({ channel: v.id("channels"), }).index("by_channel", { fields: ["channel"], staged: true }),}); Staged indexes cannot be used until enabled Staged indexes cannot be used in queries until you enable them. To enable them, they must first finish backfilling. You can check the backfill progress via the [_Indexes_ pane](https://docs.convex.dev/dashboard/deployments/data/#view-the-indexes-of-a-table) on the dashboard data page. Once it is complete, you can enable the index and use it by removing the `staged` option. export default defineSchema({ messages: defineTable({ channel: v.id("channels"), }).index("by_channel", { fields: ["channel"] }),}); Limits[​](https://docs.convex.dev/database/reading-data/indexes/#limits "Direct link to Limits") ------------------------------------------------------------------------------------------------- Convex supports indexes containing up to 16 fields. You can define 32 indexes on each table. Indexes can't contain duplicate fields. No reserved fields (starting with `_`) are allowed in indexes. The `_creationTime` field is automatically added to the end of every index to ensure a stable ordering. It should not be added explicitly in the index definition, and it's counted towards the index fields limit. The `by_creation_time` index is created automatically (and is what is used in database queries that don't specify an index). The `by_id` index is reserved. * [Defining indexes](https://docs.convex.dev/database/reading-data/indexes/#defining-indexes) * [Querying documents using indexes](https://docs.convex.dev/database/reading-data/indexes/#querying-documents-using-indexes) * [Sorting with indexes](https://docs.convex.dev/database/reading-data/indexes/#sorting-with-indexes) * [Staged indexes](https://docs.convex.dev/database/reading-data/indexes/#staged-indexes) * [Limits](https://docs.convex.dev/database/reading-data/indexes/#limits) --- # OCC and Atomicity | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/advanced/occ#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page In [Queries](https://docs.convex.dev/functions/query-functions) , we mentioned that determinism was important in the way optimistic concurrency control (OCC) was used within Convex. In this section, we'll dive much deeper into _why_. Convex Financial, Inc.[​](https://docs.convex.dev/database/advanced/occ#convex-financial-inc "Direct link to Convex Financial, Inc.") -------------------------------------------------------------------------------------------------------------------------------------- Imagine that you're building a banking app, and therefore your databases stores accounts with balances. You want your users to be able to give each other money, so you write a mutation function that transfers funds from one user's account to another. One run of that transaction might read Alice's account balance, and then Bob's. You then propose to deduct $5 from Alice's account and increase Bob's balance by the same $5. Here's our pseudocode: $14 <- READ Alice$11 <- READ BobWRITE Alice $9WRITE Bob $16 This ledger balance transfer is a classic database scenario that requires a guarantee that these write operations will only apply together. It is a really bad thing if only one operation succeeds! $14 <- READ Alice$11 <- READ BobWRITE Alice $9*crash* // $5 lost from your bank You need a guarantee that this can never happen. You require transaction atomicity, and Convex provides it. The problem of data correctness is much deeper. Concurrent transactions that read and edit the same records can create _data races_. In the case of our app it's entirely possible that someone deducts Alice's balance right after we read it. Maybe she bought a Coke Zero at the airport with her debit card for $3. $5 Transfer $3 Debit Card Charge----------------------------------------------------------$14 <- READ Alice$11 <- READ Bob $14 <- READ Alice WRITE Alice $11WRITE Alice $9 // Free coke!WRITE Bob $16 Clearly, we need to prevent these types of data races from happening. We need a way to handle these concurrent conflicts. Generally, there are two common approaches. Most traditional databases choose a _pessimistic locking_ strategy. (Pessimism in this case means the strategy assumes conflict will happen ahead of time so seeks to prevent it.) With pessimistic locking, you first need to acquire a lock on Alice's record, and then acquire a lock on Bob's record. Then you can proceed to conduct your transaction, knowing that any other transaction that needed to touch those records will wait until you are done and all your writes are committed. After decades of experience, the drawbacks of pessimistic locking are well understood and undeniable. The biggest limitation arises from real-life networks and computers being inherently unreliable. If the lock holder goes missing for whatever reason half way through its transaction, everyone else that wants to modify any of those records is waiting indefinitely. Not good! Optimistic concurrency control is, as the name states, optimistic. It assumes the transaction will succeed and doesn't worry about locking anything ahead of time. Very brash! How can it be so sure? It does this by treating the transaction as a _declarative proposal_ to write records on the basis of any read record versions (the "read set"). At the end of the transaction, the writes all commit if every version in the read set is still the latest version of that record. This means no concurrent conflict occurred. Now using our version read set, let's see how OCC would have prevented the soda-catastrophe above: $5 Transfer $3 Debit Card Charge----------------------------------------------------------(v1, $14) <- READ Alice(v7, $11) <- READ Bob (v1, $14) <- READ Alice WRITE Alice $11 IF Alice.v = v1WRITE Alice = $9, Bob = $16 IF Alice.v = v1, Bob.v = v7 // Fails! Alice is = v2 This is akin to being unable to push your Git repository because you're not at HEAD. We all know in that circumstance, we need to pull, and rebase or merge, etc. When OCC loses, determinism wins[​](https://docs.convex.dev/database/advanced/occ#when-occ-loses-determinism-wins "Direct link to When OCC loses, determinism wins") --------------------------------------------------------------------------------------------------------------------------------------------------------------------- A naive optimistic concurrency control solution would be to solve this the same way that Git does: require the user/application to resolve the conflict and determine if it is safe to retry. In Convex, however, we don't need to do that. We know the transaction is deterministic. It didn't charge money to Stripe, it didn't write a permanent value out to the filesystem. It had no effect at all other than proposing some atomic changes to Convex tables that were not applied. The determinism means that we can simply re-run the transaction; you never need to worry about temporary data races. We can run several retries if necessary until we succeed to execute the transaction without any conflicts. tip In fact, the Git analogy stays very apt. An OCC conflict means we cannot push because our HEAD is out of date, so we need to rebase our changes and try again. And determinism is what guarantees there is never a "merge conflict", so (unlike with Git) this rebase operation will always eventually succeed without developer intervention. Snapshot Isolation vs Serializability[​](https://docs.convex.dev/database/advanced/occ#snapshot-isolation-vs-serializability "Direct link to Snapshot Isolation vs Serializability") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- It is common for optimistic multi-version concurrency control databases to provide a guarantee of [snapshot isolation](https://en.wikipedia.org/wiki/Snapshot_isolation) . This [isolation level](https://en.wikipedia.org/wiki/Isolation_(database_systems)) provides the illusion that all transactions execute on an atomic snapshot of the data but it is vulnerable to [anomalies](https://en.wikipedia.org/wiki/Snapshot_isolation#Definition) where certain combinations of concurrent transactions can yield incorrect results. The implementation of optimistic concurrency control in Convex instead provides true [serializability](https://en.wikipedia.org/wiki/Serializability) and will yield correct results regardless of what transactions are issued concurrently. No need to think about this[​](https://docs.convex.dev/database/advanced/occ#no-need-to-think-about-this "Direct link to No need to think about this") ------------------------------------------------------------------------------------------------------------------------------------------------------- The beauty of this approach is that you can simply write your mutation functions as if they will _always succeed_, and always be guaranteed to be atomic. Aside from sheer curiosity about how Convex works, day to day there's no need to worry about conflicts, locking, or atomicity when you make changes to your tables and documents. The "obvious way" to write your mutation functions will just work. * [Convex Financial, Inc.](https://docs.convex.dev/database/advanced/occ#convex-financial-inc) * [When OCC loses, determinism wins](https://docs.convex.dev/database/advanced/occ#when-occ-loses-determinism-wins) * [Snapshot Isolation vs Serializability](https://docs.convex.dev/database/advanced/occ#snapshot-isolation-vs-serializability) * [No need to think about this](https://docs.convex.dev/database/advanced/occ#no-need-to-think-about-this) --- # Convex Tutorial: Calling External Services | Convex Developer Hub [Skip to main content](https://docs.convex.dev/tutorial/actions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! In the [previous step](https://docs.convex.dev/tutorial/) , you built a fully self-contained chat app. Data in, data out. In order to power the automatic reactivity we just saw while providing strong database transactions, query and mutation functions in Convex are not allowed to make `fetch` calls to the outside world. Real apps aren't this simple. They often need to talk to the rest of the internet directly from the backend. Convex lets you do this too via **action** functions. Action functions let the sync engine access the external world by scheduling out work that can then write data back via mutations. Let's make our chat app a bit smarter by letting anyone in the chat get the Wikipedia summary of a topic using the Wikipedia API. Your first `action`[​](https://docs.convex.dev/tutorial/actions#your-first-action "Direct link to your-first-action") ---------------------------------------------------------------------------------------------------------------------- **Add the following action to your `convex/chat.ts` file.** // Update your server import like this:import { query, mutation, internalAction } from "./_generated/server";//...export const getWikipediaSummary = internalAction({ args: { topic: v.string() }, handler: async (ctx, args) => { const response = await fetch( "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=" + args.topic, ); return getSummaryFromJSON(await response.json()); },});function getSummaryFromJSON(data: any) { const firstPageId = Object.keys(data.query.pages)[0]; return data.query.pages[firstPageId].extract;} Let's walk through it: 1. First, we created a new Convex action function called `getWikipediaSummary`. We used `internalAction` because we want this function to be private to the Convex backend and not exposed as a public API. This function does a simple fetch to the Wikipedia API with our topic. 2. Next, we have a helper TypeScript function called `getSummaryFromJSON` to pull out the summary text from the JSON response. 3. The `getWikipediaSummary` function calls our helper function like any other TypeScript function. This is great and all, but how do I use it? To quickly test this function in the Convex dashboard, go to [https://dashboard.convex.dev](https://dashboard.convex.dev/deployment/functions) and navigate to your project. Click on the Functions in the left nav, and then click on the `getWikipediaSummary` function. Click "Run Function". The function runner UI will pop up. Try making a few searches. Running a few Wikipedia queries Hooking it up to your app[​](https://docs.convex.dev/tutorial/actions#hooking-it-up-to-your-app "Direct link to Hooking it up to your app") -------------------------------------------------------------------------------------------------------------------------------------------- It's awesome that we can call Wikipedia, but we still need to show up in our chat. So, let's hook it all up. **Update your existing `sendMessage` mutation like this:** // Import the api referenceimport { api, internal } from "./_generated/api";//...export const sendMessage = mutation({ args: { user: v.string(), body: v.string(), }, handler: async (ctx, args) => { console.log("This TypeScript function is running on the server."); await ctx.db.insert("messages", { user: args.user, body: args.body, }); // Add the following lines: if (args.body.startsWith("/wiki")) { // Get the string after the first space const topic = args.body.slice(args.body.indexOf(" ") + 1); await ctx.scheduler.runAfter(0, internal.chat.getWikipediaSummary, { topic, }); } },}); Wait a second! What's with this `ctx.scheduler` stuff? Convex comes with a powerful durable function scheduler. It's a fundamental part of the sync engine, and it's the way you coordinate asynchronous functions in Convex. In the case of mutations, it's the only way to call an action to fetch from the outside world. The really cool part is, if for some reason your mutation throws an exception, then nothing is scheduled. This is because mutations are transactions, and scheduling is just a write in the database to tell Convex to run this function at a future time. Ok so, we can schedule our action, but we still need to write the summary back to the chat. **Let's go back and update our `getWikipediaSummary` action:** export const getWikipediaSummary = internalAction({ args: { topic: v.string() }, handler: async (ctx, args) => { const response = await fetch( "https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=" + args.topic, ); // Replace the `return ...` with the following. const summary = getSummaryFromJSON(await response.json()); await ctx.scheduler.runAfter(0, api.chat.sendMessage, { user: "Wikipedia", body: summary, }); },}); Just like scheduling the action, we're now scheduling our `sendMessage` mutation to send the result of our Wikipedia lookup to our chat. Go ahead, now play with your app! Chat with Wikipedia The scheduler, actions, and the sync engine[​](https://docs.convex.dev/tutorial/actions#the-scheduler-actions-and-the-sync-engine "Direct link to The scheduler, actions, and the sync engine") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ![Sync engine with actions](https://docs.convex.dev/assets/images/ConvexSyncAction-29b050dc3377673c0d3d21cc60efd709.png) Queries and mutations are the only ways to interact with the database and the scheduler enables building sophisticated workflows with actions in between. [Actions](https://docs.convex.dev/functions/actions) are normal serverless functions like AWS Lambda and Google Cloud Run. They help model flows like calling AI APIs and using the Vector Store. They serve as an escape hatch. They deal with the reality of the messy outside world with few guarantees. Actions are not part of the sync engine. To talk to the database they have to talk through query and mutation functions. This restriction lets Convex enforce transactional guarantees in the database and keep the sync engine fast and nimble. The best way to structure your application for scale is to minimize the work that happens in an action. Only the part that needs the [non-determinism](https://en.wikipedia.org/wiki/Deterministic_algorithm) , like making the external `fetch` call should use them. Keeping them as small as possible is the most scalable way to build Convex apps, enabling the highest throughput. The scheduler allows your app to keep most of its important logic in queries and mutations and structure your code as workflows in and out of actions. What you built[​](https://docs.convex.dev/tutorial/actions#what-you-built "Direct link to What you built") ----------------------------------------------------------------------------------------------------------- In this section of the tutorial, you built an action to talk to the outside world and used the scheduler to trigger this work. You learned that keeping our actions small and keeping most of our work in queries and mutations are fundamental to building scalable Convex backends. Next up[​](https://docs.convex.dev/tutorial/actions#next-up "Direct link to Next up") -------------------------------------------------------------------------------------- You've now learned the most important concepts in Convex. As a full-featured backend, Convex is capable of many things such as [authentication](https://docs.convex.dev/auth) , [file storage](https://docs.convex.dev/file-storage) and [search](https://docs.convex.dev/search) . You can add those features as needed by following the documentation. We touched a little bit on setting your app up for success. As your application scales, you will run into new challenges. Let's learn how to deal with some of these challenges in the [next section →](https://docs.convex.dev/tutorial/scale) . [Scaling your app\ ----------------\ \ Learn how to scale your Convex application using indexes, handling write conflicts, and leveraging Convex Components for best practices.](https://docs.convex.dev/tutorial/scale) --- # Dev workflow | Convex Developer Hub [Skip to main content](https://docs.convex.dev/understanding/workflow#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Let's walk through everything that needs to happen from creating a new project to launching your app in production. This doc assumes you are building an app with Convex and React and you already have a basic React app already up and running. You can follow one of our [quickstarts](https://docs.convex.dev/quickstarts) to set this up. Installing and running Convex[​](https://docs.convex.dev/understanding/workflow#installing-and-running-convex "Direct link to Installing and running Convex") -------------------------------------------------------------------------------------------------------------------------------------------------------------- You install Convex adding the npm dependency to your app: npm i convex Then you create your Convex project and start the backend dev loop: npx convex dev The first time you run the `npx convex dev` command you'll be asked whether you want start developing locally without an account or create an account. ### Developing without an account[​](https://docs.convex.dev/understanding/workflow#developing-without-an-account "Direct link to Developing without an account") `npx convex dev` will prompt you for the name of your project, and then start running the open-source Convex backend locally on your machine (this is also called a "deployment"). The data for your project will be saved in the `~/.convex` directory. 1. The name of your project will get saved to your `.env.local` file so future runs of `npx convex dev` will know to use this project. 2. A `convex/` folder will be created (if it doesn't exist), where you'll write your Convex backend functions. You can run `npx convex login` in the future to create an account and link any existing projects. ### Developing with an account[​](https://docs.convex.dev/understanding/workflow#developing-with-an-account "Direct link to Developing with an account") `npx convex dev` will prompt you through creating an account if one doesn't exist, and will add your credentials to `~/.convex/config.json` on your machine. You can run `npx convex logout` to log you machine out of the account in the future. Next, `npx convex dev` will create a new project and provision a new personal development deployment for this project: 1. Deployment details will automatically be added to your `.env.local` file so future runs of `npx convex dev` will know which dev deployment to connect to. 2. A `convex/` folder will be created (if it doesn't exist), where you'll write your Convex backend functions. ![Convex directory in your app](https://docs.convex.dev/assets/images/convex-directory-1ede9882007bf42d249b0561f2892c54.png) Running the dev loop[​](https://docs.convex.dev/understanding/workflow#running-the-dev-loop "Direct link to Running the dev loop") ----------------------------------------------------------------------------------------------------------------------------------- Keep the `npx convex dev` command running while you're working on your Convex app. This continuously pushes backend code you write in the `convex/` folder to your deployment. It also keeps the necessary TypeScript types up-to-date as you write your backend code. When you're developing with a locally running deployment, `npx convex dev` is also responsible for running your deployment. You can then add new server functions to your Convex backend: convex/tasks.ts import { query } from "./_generated/server";import { v } from "convex/values";// Return the last 100 tasks in a given task list.export const getTaskList = query({ args: { taskListId: v.id("taskLists") }, handler: async (ctx, args) => { const tasks = await ctx.db .query("tasks") .withIndex("taskListId", (q) => q.eq("taskListId", args.taskListId)) .order("desc") .take(100); return tasks; },}); When you write and save this code in your editor, several things happen: 1. The `npx convex dev` command typechecks your code and updates the `convex/_generated` directory. 2. The contents of your `convex/` directory get uploaded to your dev deployment. 3. Your Convex dev deployment analyzes your code and finds all Convex functions. In this example, it determines that `tasks.getTaskList` is a new public query function. 4. If there are any changes to the [schema](https://docs.convex.dev/database/schemas) , the deployment will automatically enforce them. 5. The `npx convex dev` command updates generated TypeScript code in the `convex/_generated` directory to provide end to end type safety for your functions. tip Check in everything in your `convex/_generated/` directory. This it ensures that your code immediately type checks and runs without having to first run `npx convex dev`. It's particularly useful when non-backend developers are writing frontend code and want to ensure their code type checks against currently deployed backend code. Once this is done you can use your new server function in your frontend: src/App.tsx import { useQuery } from "convex/react";import { api } from "../convex/_generated/api";export function App() { const data = useQuery(api.tasks.getTaskList); return data ?? "Loading...";} If you have other configuration like [crons](https://docs.convex.dev/scheduling/cron-jobs) or [auth](https://docs.convex.dev/auth) in your `convex/` folder, Convex ensures that they are applied and enforced on your backend. Convex dashboard[​](https://docs.convex.dev/understanding/workflow#convex-dashboard "Direct link to Convex dashboard") ----------------------------------------------------------------------------------------------------------------------- The [Convex dashboard](https://docs.convex.dev/dashboard/deployments/) will be a trusty helper throughout your dev, debug and deploy workflow in Convex. `npx convex dashboard` will open a link to the dashboard for your deployment. ### Logs[​](https://docs.convex.dev/understanding/workflow#logs "Direct link to Logs") Since Convex functions are TypeScript functions you can always use the standard `console.log` and `console.time` functions to debug your apps. Logs from your functions show up [in your dashboard](https://docs.convex.dev/dashboard/deployments/logs) . ![Logs Dashboard Page](https://docs.convex.dev/assets/images/logs-ed208103a42edfb005e9089a8edad58e.png) ### Health, Data, Functions and more[​](https://docs.convex.dev/understanding/workflow#health-data-functions-and-more "Direct link to Health, Data, Functions and more") * [Health](https://docs.convex.dev/dashboard/deployments/health) - provides invaluable information on how your app is performing in production, with deep insights on how your Convex queries are doing. * [Data](https://docs.convex.dev/dashboard/deployments/data) - gives you a complete data browser to spot check your data. * [Functions](https://docs.convex.dev/dashboard/deployments/functions) - gives you stats and run functions to debug them. There is a lot more to to the dashboard. Be sure to click around or [check out the docs](https://docs.convex.dev/dashboard) . Deploying your app[​](https://docs.convex.dev/understanding/workflow#deploying-your-app "Direct link to Deploying your app") ----------------------------------------------------------------------------------------------------------------------------- So far you've been working on your app against your personal dev deployment. All Convex projects have one production deployment running in the cloud. It has separate data and has a separate push process from personal dev deployments, which allows you and your teammates to work on new features using personal dev deployments without disrupting your app running in production. If you have not created a Convex account yet, you will need to do so with `npx convex login`. This will automatically link any projects you've started with your new account, and enable using your production deployment. To push your code to your production deployment for your project you run the deploy command: npx convex deploy info If you're running this command for the first time, it will automatically provision the prod deployment for your project. ### Setting up your deployment pipeline[​](https://docs.convex.dev/understanding/workflow#setting-up-your-deployment-pipeline "Direct link to Setting up your deployment pipeline") It's rare to run `npx convex deploy` directly. Most production applications run an automated workflow that runs tests and deploys your backend and frontend together. You can see detailed deployment and frontend configuration instructions in the [Hosting and Deployment](https://docs.convex.dev/production/hosting/) doc. For most React meta-frameworks Convex [automatically sets the correct environment variable](https://docs.convex.dev/production/hosting/vercel#how-it-works) to connect to the production deployment. Up next[​](https://docs.convex.dev/understanding/workflow#up-next "Direct link to Up next") -------------------------------------------------------------------------------------------- You now know the basics of how Convex works and fits in your app. Go head and explore the docs further to learn more about the specific features you want to use. Whenever you're ready be sure the read the [Best Practices](https://docs.convex.dev/understanding/best-practices/) , and then the [Zen of Convex](https://docs.convex.dev/understanding/zen) once you are ready to "think in Convex." * [Installing and running Convex](https://docs.convex.dev/understanding/workflow#installing-and-running-convex) * [Developing without an account](https://docs.convex.dev/understanding/workflow#developing-without-an-account) * [Developing with an account](https://docs.convex.dev/understanding/workflow#developing-with-an-account) * [Running the dev loop](https://docs.convex.dev/understanding/workflow#running-the-dev-loop) * [Convex dashboard](https://docs.convex.dev/understanding/workflow#convex-dashboard) * [Logs](https://docs.convex.dev/understanding/workflow#logs) * [Health, Data, Functions and more](https://docs.convex.dev/understanding/workflow#health-data-functions-and-more) * [Deploying your app](https://docs.convex.dev/understanding/workflow#deploying-your-app) * [Setting up your deployment pipeline](https://docs.convex.dev/understanding/workflow#setting-up-your-deployment-pipeline) * [Up next](https://docs.convex.dev/understanding/workflow#up-next) --- # The Zen of Convex | Convex Developer Hub [Skip to main content](https://docs.convex.dev/understanding/zen#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex is an opinionated framework, with every element designed to pull developers into [the pit of success](https://blog.codinghorror.com/falling-into-the-pit-of-success/) . The Zen of Convex is a set of guidelines & best practices developers have discovered that keep their projects falling into this wonderful pit. Performance ----------- ### Double down on the [sync engine](https://docs.convex.dev/tutorial#how-convex-works) There's a reason why a deterministic, reactive database is the beating heart of Convex: the more you center your apps around its properties, the better your projects will fare over time. Your projects will be easier to understand and refactor. Your app's performance will stay screaming fast. You won't have any consistency or state management problems. Use a query for nearly every app read Queries are the reactive, automatically cacheable, consistent and resilient way to propagate data to your application and its jobs. With very few exceptions, every read operation in your app should happen via a query function. Keep sync engine functions light & fast In general, your mutations and queries should be working with less than a few hundred records and should aim to finish in less than 100ms. It's nearly impossible to maintain a snappy, responsive app if your synchronous transactions involve a lot more work than this. Use actions sparingly and incrementally Actions are wonderful for batch jobs and/or integrating with outside services. They're very powerful, but they're slower, more expensive, and Convex provides a lot fewer guarantees about their behavior. So never use an action if a query or mutation will get the job done. ### Don't over-complicate client-side state management Convex builds in a ton of its own caching and consistency controls into the app's client library. Rather than reinvent the wheel, let your client-side code take advantage of these built-in performance boosts. Let Convex handle caching & consistency You might be tempted to quickly build your own local cache or state aggregation layer in Convex to sit between your components and your Convex functions. With Convex, most of the time, you won't end up needing this. More often than not, you can bind your components to Convex functions in pretty simple ways and things will Just Work and be plenty fast. Be thoughtful about the return values of mutations Mutation return values can be useful to trigger state changes in your app, but it's rarely a good idea to use them to set in-app state to update the UI. Let queries and the sync engine do that. Architecture // --------------- ### Create server-side frameworks using "just code" Convex's built-in primitives are pretty low level! They're just functions. What about authentication frameworks? What about object-relational mappings? Do you need to wait until Convex ships some in-built feature to get those? Nope. In general, you should solve composition and encapsulation problems in your server-side Convex code using the same methods you use for the rest of your TypeScript code bases. After all, this is why Convex is "just code!" [Stack](https://stack.convex.dev/) always has [great](https://stack.convex.dev/functional-relationships-helpers) [examples](https://stack.convex.dev/wrappers-as-middleware-authentication) of ways to tackle [these needs](https://stack.convex.dev/row-level-security) . ### Don't misuse actions Actions are powerful, but it's important to be intentional in how they fit into your app's data flow. Don't invoke actions directly from your app In general, it's an anti-pattern to call actions from the browser. Usually, actions are running on some dependent record that should be living in a Convex table. So it's best trigger actions by invoking a mutation that both _writes_ that dependent record and _schedules_ the subsequent action to run in the background. Don't think 'background jobs', think 'workflow' When actions are involved, it's useful to write chains of effects and mutations, such as: action code → mutation → more action code → mutation. Then apps or other jobs can follow along with queries. Record progress one step at a time While actions _could_ work with thousands of records and call dozens of APIs, it's normally best to do smaller batches of work and/or to perform individual transformations with outside services. Then record your progress with a mutation, of course. Using this pattern makes it easy to debug issues, resume partial jobs, and report incremental progress in your app's UI. Development workflow -------------------- ### Keep the dashboard by your side Working on your Convex project without using the dashboard is like driving a car with your eyes closed. The dashboard lets you view logs, give mutations/queries/actions a test run, make sure your configuration and codebase are as you expect, inspect your tables, generate schemas, etc. It's an invaluable part of your rapid development cycle. ### Don't go it alone Between these [docs](https://docs.convex.dev/) , [Stack](https://stack.convex.dev/) , and [our community](https://convex.dev/community) , someone has _probably_ encountered the design or architectural issue you're facing. So why try to figure things out the hard way, when you can take advantage of a whole community's experience? Leverage Convex developer search With so many great resources from the Convex team & community, it can be hard to know where to look first. If you want a quick way to search across all of these, [we have a portal for that](https://search.convex.dev/) ! Join the Convex community Whether you're stuck on a tricky use case, you have a question or feature request for the Convex team, or you're excited to share the amazing app(s) you've built and help others learn, the Convex community is there for you! Join the party on [Discord](https://convex.dev/community) . --- # Understanding Components | Convex Developer Hub [Skip to main content](https://docs.convex.dev/components/understanding#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex Components are self-contained backend modules that bundle functions, schemas, and data together. They let you add complex functionality to your app—like authentication, rate limiting, or document collaboration—without implementing everything from scratch. If you've worked with modern web development, you've likely encountered similar ideas in different forms. Components draw inspiration from frontend components, third-party APIs, and service-oriented architectures. The key difference is that Convex Components run within your backend, giving you composability combined with the persistence and reliability of backend services. The following diagram shows how data and function access works in the component ecosystem. Arrows from one element to another represent that an element has access to the functions or data of the other element. ![Screenshot of the component dropdown](https://docs.convex.dev/img/components-diagram.png) ### Data[​](https://docs.convex.dev/components/understanding#data "Direct link to Data") Similar to frontend components, Convex Components encapsulate state and behavior and allow exposing a clean interface. However, instead of storing state in memory, these can have internal state machines that can persist between user sessions, span users, and change in response to external inputs, such as webhooks. Components can store data in a few ways: * Database tables with their own schema validation definitions. Since Convex is realtime by default, data reads are automatically reactive, and writes commit transactionally. * File storage, independent of the main app's file storage. * Durable functions via the built-in function scheduler. Components can schedule functions to run in the future and pass along state. Typically, libraries require configuring a third party service to add stateful off-the-shelf functionality, which lack the transactional guarantees that come from storing state in the same database. ### Isolation[​](https://docs.convex.dev/components/understanding#isolation "Direct link to Isolation") Similar to regular npm libraries, Convex Components include functions, type safety, and are called from your code. However, they also provide extra guarantees. * Similar to an external service, code inside a component can't read data that is not explicitly provided to it. This includes database tables, file storage, environment variables, scheduled functions, etc. Conversely, the component's data cannot be directly mutated by the main app, allowing full separation of concerns. * Similar to a service-oriented architecture, functions in components are run in an isolated environment, so writes to global variables and patches to system behavior are not shared between components. * Similar to a monolithic architecture, data changes commit transactionally across calls to components, without having to reason about complicated distributed commit protocols or data inconsistencies. You'll never have a component commit data but have the calling code roll back. * In addition, each mutation call to a component is a sub-transaction isolated from other calls, allowing you to safely catch errors thrown by components. This also allows component authors to easily reason about state changes without races, and trust that a thrown exception will always roll back the component's sub-transaction. [Read more](https://docs.convex.dev/components/using#transactions) . ### Encapsulation[​](https://docs.convex.dev/components/understanding#encapsulation "Direct link to Encapsulation") Being able to reason about your code is essential to scaling a codebase. Components allow you to reason about API boundaries and abstractions. * The transactional guarantees discussed above allows authors and users of components to reason locally about data changes. * Components expose an explicit API, not direct database table access. Data invariants can be enforced in code, within the abstraction boundary. For example, the [aggregate component](https://convex.dev/components/aggregate) can internally denormalize data, the [rate limiter](https://convex.dev/components/rate-limiter) component can shard its data, and the [push notification](https://convex.dev/components/push-notifications) component can internally batch API requests, while maintaining simple interfaces. * Runtime validation ensures all data that cross a component boundary are validated: both arguments and return values. As with normal Convex functions, the validators also specify the TypeScript types, providing end-to-end typing with runtime guarantees. * [Data](https://docs.convex.dev/components/understanding#data) * [Isolation](https://docs.convex.dev/components/understanding#isolation) * [Encapsulation](https://docs.convex.dev/components/understanding#encapsulation) --- # Convex Auth | Convex Developer Hub [Skip to main content](https://docs.convex.dev/auth/convex-auth#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [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](https://docs.convex.dev/production/state/#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[​](https://docs.convex.dev/auth/convex-auth#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[​](https://docs.convex.dev/auth/convex-auth#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) . * [Get Started](https://docs.convex.dev/auth/convex-auth#get-started) * [Overview](https://docs.convex.dev/auth/convex-auth#overview) --- # Cron Jobs | Convex Developer Hub [Skip to main content](https://docs.convex.dev/scheduling/cron-jobs#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page 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[​](https://docs.convex.dev/scheduling/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 syntaxcrons.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](https://docs.convex.dev/scheduling/cron-jobs#supported-schedules) below. The third argument is the name of the public function or [internal function](https://docs.convex.dev/functions/internal-functions) , either a [mutation](https://docs.convex.dev/functions/mutation-functions) or an [action](https://docs.convex.dev/functions/actions) . Supported schedules[​](https://docs.convex.dev/scheduling/cron-jobs#supported-schedules "Direct link to Supported schedules") ------------------------------------------------------------------------------------------------------------------------------ * [`crons.interval()`](https://docs.convex.dev/api/classes/server.Crons#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()`](https://docs.convex.dev/api/classes/server.Crons#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()`](https://docs.convex.dev/api/classes/server.Crons#cron) , [`crons.daily()`](https://docs.convex.dev/api/classes/server.Crons#daily) , [`crons.weekly()`](https://docs.convex.dev/api/classes/server.Crons#weekly) , [`crons.monthly()`](https://docs.convex.dev/api/classes/server.Crons#monthly) provide an alternative syntax for common cron schedules with explicitly named arguments. Viewing your cron jobs[​](https://docs.convex.dev/scheduling/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](https://docs.convex.dev/dashboard/deployments/schedules#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[​](https://docs.convex.dev/scheduling/cron-jobs#error-handling "Direct link to Error handling") --------------------------------------------------------------------------------------------------------------- Mutations and actions have the same guarantees that are described in [Error handling](https://docs.convex.dev/scheduling/scheduled-functions#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. * [Defining your cron jobs](https://docs.convex.dev/scheduling/cron-jobs#defining-your-cron-jobs) * [Supported schedules](https://docs.convex.dev/scheduling/cron-jobs#supported-schedules) * [Viewing your cron jobs](https://docs.convex.dev/scheduling/cron-jobs#viewing-your-cron-jobs) * [Error handling](https://docs.convex.dev/scheduling/cron-jobs#error-handling) --- # Using Cursor with Convex | Convex Developer Hub [Skip to main content](https://docs.convex.dev/ai/using-cursor#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [Cursor](https://cursor.com/) , the AI code editor, makes it easy to write and maintain apps built with Convex. Let's walk through how to setup Cursor for the best possible results with Convex. Add Convex `.cursor/rules`[​](https://docs.convex.dev/ai/using-cursor#add-convex-cursorrules "Direct link to add-convex-cursorrules") -------------------------------------------------------------------------------------------------------------------------------------- To get the best results from Cursor put the model specific `.mdc` files in your project's `.cursor/rules` directory. * [Convex Cursor Rules](https://convex.link/convex_rules.mdc) 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) . Setup the Convex MCP Server[​](https://docs.convex.dev/ai/using-cursor#setup-the-convex-mcp-server "Direct link to Setup the Convex MCP Server") ------------------------------------------------------------------------------------------------------------------------------------------------- The Convex CLI comes with a [Convex Model Context Protocol](https://docs.convex.dev/ai/convex-mcp-server) (MCP) server built in. The Convex MCP server gives your AI coding agent access to the your Convex deployment to query and optimize your project. ### Quick Install[​](https://docs.convex.dev/ai/using-cursor#quick-install "Direct link to Quick Install") You can click this handy deep-link below: [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=convex&config=eyJjb21tYW5kIjoibnB4IC15IGNvbnZleEBsYXRlc3QgbWNwIHN0YXJ0In0%3D) ### Manual Install[​](https://docs.convex.dev/ai/using-cursor#manual-install "Direct link to Manual Install") To get started with Cursor, open "Cursor Settings > Tools & Integrations", click on "New MCP Server", and add a "convex" section to "mcpServers" in the `mcp.json` file that's opened. { "mcpServers": { "convex": { "command": "npx", "args": ["-y", "convex@latest", "mcp", "start"] } }} You can also install the Convex MCP [for just one project](https://docs.cursor.com/en/context/mcp#configuration-locations) . After adding the server, ensure the "convex" server is enabled and lit up green (it make take a minute the first time while the NPM package downloads). Now start asking it questions like: * Evaluate my convex schema and suggest improvements * What are this app's public endpoints? * Run the `my_convex_function` query Tips and tricks[​](https://docs.convex.dev/ai/using-cursor#tips-and-tricks "Direct link to Tips and tricks") ------------------------------------------------------------------------------------------------------------- ### Install and run Convex yourself[​](https://docs.convex.dev/ai/using-cursor#install-and-run-convex-yourself "Direct link to Install and run Convex yourself") Keeping Convex running is crucial because [it automatically generates](https://docs.convex.dev/cli#run-the-convex-dev-server) the client-side types. Without this, the agent can get stuck in a linting loop since it can't access the types for the queries and mutations it created. We recommended that you install (`npm install convex`) and run convex (`npx convex dev`) yourself in a terminal window. ### Keep your requests small[​](https://docs.convex.dev/ai/using-cursor#keep-your-requests-small "Direct link to Keep your requests small") The best results when using agentic LLMs can be found when keeping the amount of changes you want to make small and git commit frequently. This lets you be more specific around the context you provide the agent and it means the agent doesn't need to do a lot of searching for context. After each successful prompt or series of prompts it is a good idea to commit your changes so that its simple to rollback to that point should the next prompt cause issues. ### Update and reference your `README.md`[​](https://docs.convex.dev/ai/using-cursor#update-and-reference-your-readmemd "Direct link to update-and-reference-your-readmemd") The agent needs context about the specific business goals for your project. While it can infer some details from the files it reads, this becomes more challenging as your project grows. Providing general information about your project gives the agent a helpful head start. Rather than including this information in each prompt, it's better to write a comprehensive README.md file in your project root and reference it. [Some people](https://youtu.be/2PjmPU07KNs?t=145) advocate for crafting a Product Requirements Document (PRD), this may be a good idea for more complex projects. ### Add Convex docs[​](https://docs.convex.dev/ai/using-cursor#add-convex-docs "Direct link to Add Convex docs") Adding Convex docs can let you specifically refer to Convex features when building your app. From **`Cursor Settings`** > **`Indexing & Docs`** > **`Docs`** add new doc, use the URL "[https://docs.convex.dev/home](https://docs.convex.dev/home) " ![Chat UI]() Cursor will then index all of the Convex docs for the LLM to use. ![Chat UI](https://docs.convex.dev/assets/images/indexed_docs-90bb59330756c00540015c53da6a484c.webp) You can then reference those docs in your prompt with the `@Convex` symbol. ![Chat UI](https://docs.convex.dev/assets/images/reference_convex_docs-c791c41ddbd7663244fda1c4c59a43d9.webp) Add more Convex knowledge You can perform the above steps for [https://stack.convex.dev/](https://stack.convex.dev/) too if you would like to provide even more context to the agent. * [Add Convex `.cursor/rules`](https://docs.convex.dev/ai/using-cursor#add-convex-cursorrules) * [Setup the Convex MCP Server](https://docs.convex.dev/ai/using-cursor#setup-the-convex-mcp-server) * [Quick Install](https://docs.convex.dev/ai/using-cursor#quick-install) * [Manual Install](https://docs.convex.dev/ai/using-cursor#manual-install) * [Tips and tricks](https://docs.convex.dev/ai/using-cursor#tips-and-tricks) * [Install and run Convex yourself](https://docs.convex.dev/ai/using-cursor#install-and-run-convex-yourself) * [Keep your requests small](https://docs.convex.dev/ai/using-cursor#keep-your-requests-small) * [Update and reference your `README.md`](https://docs.convex.dev/ai/using-cursor#update-and-reference-your-readmemd) * [Add Convex docs](https://docs.convex.dev/ai/using-cursor#add-convex-docs) --- # Scheduled Functions | Convex Developer Hub [Skip to main content](https://docs.convex.dev/scheduling/scheduled-functions#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex allows you to schedule functions to run in the future. This allows you to build powerful durable workflows without the need to set up and maintain queues or other infrastructure. Scheduled functions are stored in the database. This means you can schedule functions minutes, days, and even months in the future. Scheduling is resilient against unexpected downtime or system restarts. **Example:** [Scheduling](https://github.com/get-convex/convex-demos/tree/main/scheduling) Scheduling functions[​](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-functions "Direct link to Scheduling functions") ------------------------------------------------------------------------------------------------------------------------------------------- You can schedule public functions and [internal functions](https://docs.convex.dev/functions/internal-functions) from mutations and actions via the [scheduler](https://docs.convex.dev/api/interfaces/server.Scheduler) provided in the respective function context. * [runAfter](https://docs.convex.dev/api/interfaces/server.Scheduler#runafter) schedules a function to run after a delay (measured in milliseconds). * [runAt](https://docs.convex.dev/api/interfaces/server.Scheduler#runat) schedules a function run at a date or timestamp (measured in milliseconds elapsed since the epoch). The rest of the arguments are the path to the function and its arguments, similar to invoking a function from the client. For example, here is how to send a message that self-destructs in five seconds. convex/messages.ts TS import { mutation, internalMutation } from "./_generated/server";import { internal } from "./_generated/api";import { v } from "convex/values";export const sendExpiringMessage = mutation({ args: { body: v.string(), author: v.string() }, handler: async (ctx, args) => { const { body, author } = args; const id = await ctx.db.insert("messages", { body, author }); await ctx.scheduler.runAfter(5000, internal.messages.destruct, { messageId: id, }); },});export const destruct = internalMutation({ args: { messageId: v.id("messages"), }, handler: async (ctx, args) => { await ctx.db.delete("messages", args.messageId); },}); A single function can schedule up to 1000 functions with total argument size of 8MB. ### Scheduling from mutations[​](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-from-mutations "Direct link to Scheduling from mutations") Scheduling functions from [mutations](https://docs.convex.dev/functions/mutation-functions#transactions) is atomic with the rest of the mutation. This means that if the mutation succeeds, the scheduled function is guaranteed to be scheduled. On the other hand, if the mutations fails, no function will be scheduled, even if the function fails after the scheduling call. ### Scheduling from actions[​](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-from-actions "Direct link to Scheduling from actions") Unlike mutations, [actions](https://docs.convex.dev/functions/actions) don't execute as a single database transaction and can have side effects. Thus, scheduling from actions does not depend on the outcome of the function. This means that an action might succeed to schedule some functions and later fail due to transient error or a timeout. The scheduled functions will still be executed. ### Scheduling immediately[​](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-immediately "Direct link to Scheduling immediately") Using `runAfter()` with delay set to 0 is used to immediately add a function to the event queue. This usage may be familiar to you if you're used to calling `setTimeout(fn, 0)`. As noted above, actions are not atomic and are meant to cause side effects. Scheduling immediately becomes useful when you specifically want to trigger an action from a mutation that is conditional on the mutation succeeding. [This post](https://stack.convex.dev/pinecone-and-embeddings#kick-off-a-background-action) goes over a direct example of this in action, where the application depends on an external service to fill in information to the database. Retrieving scheduled function status[​](https://docs.convex.dev/scheduling/scheduled-functions#retrieving-scheduled-function-status "Direct link to Retrieving scheduled function status") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Every scheduled function is reflected as a document in the `"_scheduled_functions"` system table. `runAfter()` and `runAt()` return the id of scheduled function. You can read data from system tables using the `db.system.get` and `db.system.query` methods, which work the same as the standard `db.get` and `db.query` methods. convex/messages.ts TS export const listScheduledMessages = query({ args: {}, handler: async (ctx, args) => { return await ctx.db.system.query("_scheduled_functions").collect(); },});export const getScheduledMessage = query({ args: { id: v.id("_scheduled_functions"), }, handler: async (ctx, args) => { return await ctx.db.system.get("_scheduled_functions", args.id); },}); This is an example of the returned document: { "_creationTime": 1699931054642.111, "_id": "3ep33196167235462543626ss0scq09aj4gqn9kdxrdr", "args": [{}], "completedTime": 1699931054690.366, "name": "messages.js:destruct", "scheduledTime": 1699931054657, "state": { "kind": "success" }} The returned document has the following fields: * `name`: the path of the scheduled function * `args`: the arguments passed to the scheduled function * `scheduledTime`: the timestamp of when the function is scheduled to run (measured in milliseconds elapsed since the epoch) * `completedTime`: the timestamp of when the function finished running, if it has completed (measured in milliseconds elapsed since the epoch) * `state`: the status of the scheduled function. Here are the possible states a scheduled function can be in: * `Pending`: the function has not been started yet * `InProgress`: the function has started running is not completed yet (only applies to actions) * `Success`: the function finished running successfully with no errors * `Failed`: the function hit an error while running, which can either be a user error or an internal server error * `Canceled`: the function was canceled via the dashboard, `ctx.scheduler.cancel`, or recursively by a parent scheduled function that was canceled while in progress Scheduled function results are available for 7 days after they have completed. Canceling scheduled functions[​](https://docs.convex.dev/scheduling/scheduled-functions#canceling-scheduled-functions "Direct link to Canceling scheduled functions") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can cancel a previously scheduled function with [`cancel`](https://docs.convex.dev/api/interfaces/server.Scheduler#cancel) via the [scheduler](https://docs.convex.dev/api/interfaces/server.Scheduler) provided in the respective function context. convex/messages.ts TS export const cancelMessage = mutation({ args: { id: v.id("_scheduled_functions"), }, handler: async (ctx, args) => { await ctx.scheduler.cancel(args.id); },}); What `cancel` does depends on the state of the scheduled function: * If it hasn't started running, it won't run. * If it already started, it will continue to run, but any functions it schedules will not run. Debugging[​](https://docs.convex.dev/scheduling/scheduled-functions#debugging "Direct link to Debugging") ---------------------------------------------------------------------------------------------------------- You can view logs from previously executed scheduled functions in the Convex dashboard [Logs view](https://docs.convex.dev/dashboard/deployments/logs) . You can view and cancel yet to be executed functions in the [Functions view](https://docs.convex.dev/dashboard/deployments/functions) . Error handling[​](https://docs.convex.dev/scheduling/scheduled-functions#error-handling "Direct link to Error handling") ------------------------------------------------------------------------------------------------------------------------- Once scheduled, mutations are guaranteed to be executed exactly once. Convex will automatically retry any internal Convex errors, and only fail on developer errors. See [Error Handling](https://docs.convex.dev/functions/error-handling/) for more details on different error types. Since actions may have side effects, they are not automatically retried by Convex. Thus, actions will be executed at most once, and permanently fail if there are transient errors while executing them. Developers can retry those manually by scheduling a mutation that checks if the desired outcome has been achieved and if not schedule the action again. Auth[​](https://docs.convex.dev/scheduling/scheduled-functions#auth "Direct link to Auth") ------------------------------------------------------------------------------------------- The auth is not propagated from the scheduling to the scheduled function. If you want to authenticate or check authorization, you'll have to pass the requisite user information in as a parameter. * [Scheduling functions](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-functions) * [Scheduling from mutations](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-from-mutations) * [Scheduling from actions](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-from-actions) * [Scheduling immediately](https://docs.convex.dev/scheduling/scheduled-functions#scheduling-immediately) * [Retrieving scheduled function status](https://docs.convex.dev/scheduling/scheduled-functions#retrieving-scheduled-function-status) * [Canceling scheduled functions](https://docs.convex.dev/scheduling/scheduled-functions#canceling-scheduled-functions) * [Debugging](https://docs.convex.dev/scheduling/scheduled-functions#debugging) * [Error handling](https://docs.convex.dev/scheduling/scheduled-functions#error-handling) * [Auth](https://docs.convex.dev/scheduling/scheduled-functions#auth) --- # Best Practices | Convex Developer Hub [Skip to main content](https://docs.convex.dev/understanding/best-practices/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page 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[​](https://docs.convex.dev/understanding/best-practices/#await-all-promises "Direct link to Await all Promises") ------------------------------------------------------------------------------------------------------------------------------------ ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#avoid-filter-on-database-queries "Direct link to avoid-filter-on-database-queries") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/database/reading-data/indexes/indexes-and-query-perf) for an overview of how to define indexes and how they work. ### Examples[​](https://docs.convex.dev/understanding/best-practices/#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 indexconst tomsMessages = await ctx.db .query("messages") .withIndex("by_author", (q) => q.eq("author", "Tom")) .collect();// Option 2: Filter in codeconst allMessages = await ctx.db.query("messages").collect();const tomsMessages = allMessages.filter((m) => m.author === "Tom"); ### How?[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#only-use-collect-with-a-small-number-of-results "Direct link to only-use-collect-with-a-small-number-of-results") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#example "Direct link to Example") **Using an index:** convex/movies.ts TS // ❌ -- potentially unboundedconst allMovies = await ctx.db.query("movies").collect();const moviesByDirector = allMovies.filter( (m) => m.director === "Steven Spielberg",);// ✅ -- small number of results, so `collect` is fineconst moviesByDirector = await ctx.db .query("movies") .withIndex("by_director", (q) => q.eq("director", "Steven Spielberg")) .collect(); **Using pagination:** convex/movies.ts TS // ❌ -- potentially unboundedconst watchedMovies = await ctx.db .query("watchedMovies") .withIndex("by_user", (q) => q.eq("user", "Tom")) .collect();// ✅ -- using pagination, showing recently watched movies firstconst 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 unboundedconst 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 documentsconst 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 tableconst watchedMoviesCount = await ctx.db .query("watchedMoviesCount") .withIndex("by_user", (q) => q.eq("user", "Tom")) .unique(); ### How?[​](https://docs.convex.dev/understanding/best-practices/#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. You can also check automatically that `.collect()` is avoided by enabling the [`@convex-dev/no-query-collect` ESLint rule](https://docs.convex.dev/eslint#no-query-collect) . 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[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#check-for-redundant-indexes "Direct link to Check for redundant indexes") --------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#use-argument-validators-for-all-public-functions "Direct link to Use argument validators for all public functions") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ### Why?[​](https://docs.convex.dev/understanding/best-practices/#why-4 "Direct link to Why?") Public functions can be called by anyone, including potentially malicious attackers trying to break your app. [Argument validators](https://docs.convex.dev/functions/validation) (as well as return value validators) help ensure you're getting the traffic you expect. ### Example[​](https://docs.convex.dev/understanding/best-practices/#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` fieldsexport 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?[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/eslint#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[​](https://docs.convex.dev/understanding/best-practices/#use-some-form-of-access-control-for-all-public-functions "Direct link to Use some form of access control for all public functions") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#example-2 "Direct link to Example") convex/teams.ts TS // ❌ -- no checks! anyone can update any team if they get the IDexport 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 spoofedexport 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 spoofedexport 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 controlexport 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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#only-schedule-and-ctxrun-internal-functions "Direct link to only-schedule-and-ctxrun-internal-functions") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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.tscrons.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.tscrons.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[​](https://docs.convex.dev/understanding/best-practices/#use-helper-functions-to-write-shared-code "Direct link to Use helper functions to write shared code") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/understanding/best-practices/typescript) for useful types. ### Example[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#use-runaction-only-when-using-a-different-runtime "Direct link to use-runaction-only-when-using-a-different-runtime") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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 functionexport 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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#avoid-sequential-ctxrunmutation--ctxrunquery-calls-from-actions "Direct link to avoid-sequential-ctxrunmutation--ctxrunquery-calls-from-actions") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#example-queries "Direct link to Example: Queries") convex/teams.ts TS // ❌ -- this assertion could fail if the team changed between running the two queriesconst 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[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#use-ctxrunquery-and-ctxrunmutation-sparingly-in-queries-and-mutations "Direct link to use-ctxrunquery-and-ctxrunmutation-sparingly-in-queries-and-mutations") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#always-include-the-table-name-when-calling-ctxdb-functions "Direct link to always-include-the-table-name-when-calling-ctxdb-functions") --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#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[​](https://docs.convex.dev/understanding/best-practices/#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);// ✅ vvvvvvvvawait 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?[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/eslint#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[​](https://docs.convex.dev/understanding/best-practices/#date-in-queries "Direct link to date-in-queries") --------------------------------------------------------------------------------------------------------------------------------------------- ### Why?[​](https://docs.convex.dev/understanding/best-practices/#why-12 "Direct link to Why?") When you subscribe to a query, Convex [will automatically run it again](https://docs.convex.dev/realtime) 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[​](https://docs.convex.dev/understanding/best-practices/#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?[​](https://docs.convex.dev/understanding/best-practices/#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](https://docs.convex.dev/scheduling/scheduled-functions) (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. * [Await all Promises](https://docs.convex.dev/understanding/best-practices/#await-all-promises) * [Avoid `.filter` on database queries](https://docs.convex.dev/understanding/best-practices/#avoid-filter-on-database-queries) * [Only use `.collect` with a small number of results](https://docs.convex.dev/understanding/best-practices/#only-use-collect-with-a-small-number-of-results) * [Check for redundant indexes](https://docs.convex.dev/understanding/best-practices/#check-for-redundant-indexes) * [Use argument validators for all public functions](https://docs.convex.dev/understanding/best-practices/#use-argument-validators-for-all-public-functions) * [Use some form of access control for all public functions](https://docs.convex.dev/understanding/best-practices/#use-some-form-of-access-control-for-all-public-functions) * [Only schedule and `ctx.run*` internal functions](https://docs.convex.dev/understanding/best-practices/#only-schedule-and-ctxrun-internal-functions) * [Use helper functions to write shared code](https://docs.convex.dev/understanding/best-practices/#use-helper-functions-to-write-shared-code) * [Use `runAction` only when using a different runtime](https://docs.convex.dev/understanding/best-practices/#use-runaction-only-when-using-a-different-runtime) * [Avoid sequential `ctx.runMutation` / `ctx.runQuery` calls from actions](https://docs.convex.dev/understanding/best-practices/#avoid-sequential-ctxrunmutation--ctxrunquery-calls-from-actions) * [Use `ctx.runQuery` and `ctx.runMutation` sparingly in queries and mutations](https://docs.convex.dev/understanding/best-practices/#use-ctxrunquery-and-ctxrunmutation-sparingly-in-queries-and-mutations) * [Always include the table name when calling `ctx.db` functions](https://docs.convex.dev/understanding/best-practices/#always-include-the-table-name-when-calling-ctxdb-functions) * [Don’t use `Date.now()` in queries](https://docs.convex.dev/understanding/best-practices/#date-in-queries) --- # Storing Generated Files | Convex Developer Hub [Skip to main content](https://docs.convex.dev/file-storage/store-files#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Files can be uploaded to Convex from a client and stored directly, see [Upload](https://docs.convex.dev/file-storage/upload-files) . Alternatively files can also be stored after they've been fetched or generated in [actions](https://docs.convex.dev/functions/actions) and [HTTP actions](https://docs.convex.dev/functions/http-actions) . For example you might call a third-party API to generate an image based on a user prompt and then store that image in Convex. **Example:** [Dall-E Storage & Action](https://github.com/get-convex/convex-demos/tree/main/dall-e-storage-action) Storing files in actions[​](https://docs.convex.dev/file-storage/store-files#storing-files-in-actions "Direct link to Storing files in actions") ------------------------------------------------------------------------------------------------------------------------------------------------- Storing files in actions is similar to [uploading a file via an HTTP action](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-an-http-action) . The action takes these steps: 1. Fetch or generate an image. 2. Store the image using [`storage.store()`](https://docs.convex.dev/api/interfaces/server.StorageActionWriter#store) and receive a storage ID. 3. Save the storage ID into your data model via a mutation. Storage IDs correspond to documents in the `"_storage"` system table (see [Metadata](https://docs.convex.dev/file-storage/file-metadata) ), so they can be validated using the `v.id("_storage")` validator and typed as `Id<"_storage">` in TypeScript. convex/images.ts TS import { action, internalMutation, query } from "./_generated/server";import { internal } from "./_generated/api";import { v } from "convex/values";import { Id } from "./_generated/dataModel";export const generateAndStore = action({ args: { prompt: v.string() }, handler: async (ctx, args) => { // Not shown: generate imageUrl from `prompt` const imageUrl = "https://...."; // Download the image const response = await fetch(imageUrl); const image = await response.blob(); // Store the image in Convex const storageId: Id<"_storage"> = await ctx.storage.store(image); // Write `storageId` to a document await ctx.runMutation(internal.images.storeResult, { storageId, prompt: args.prompt, }); },});export const storeResult = internalMutation({ args: { storageId: v.id("_storage"), prompt: v.string(), }, handler: async (ctx, args) => { const { storageId, prompt } = args; await ctx.db.insert("images", { storageId, prompt }); },}); * [Storing files in actions](https://docs.convex.dev/file-storage/store-files#storing-files-in-actions) --- # Streaming Import | Convex Developer Hub [Skip to main content](https://docs.convex.dev/streaming-import-api#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex supports streaming import. Convex provides a connector implementation for [Airbyte](https://docs.convex.dev/production/integrations/streaming-import-export) . Those connectors use the following APIs. Streaming import support is automatically enabled for all Convex projects. Streaming import requests require deployment admin authorization via the HTTP header `Authorization`. The value is `Convex ` where the access key comes from "Deploy key" on the Convex dashboard and gives full read and write access to your Convex data. ### Headers[​](https://docs.convex.dev/streaming-import-api#headers "Direct link to Headers") Streaming import endpoints accept a `Convex-Client: streaming-import-` header, where the version follows [Semver](https://semver.org/) guidelines. If this header is not specified, Convex will default to the latest version. We recommend using the header to ensure the consumer of this API does not break as the API changes. ### GET `/api/streaming_import/primary_key_indexes_ready`[​](https://docs.convex.dev/streaming-import-api#get-apistreaming_importprimary_key_indexes_ready "Direct link to get-apistreaming_importprimary_key_indexes_ready") The `primary_key_indexes_ready` endpoint takes a list of table names and returns true if the primary key indexes (created by `add_primary_key_indexes`) on all those tables are ready. If the tables are newly created, the indexes should be ready immediately; however if there are existing documents in the tables, it may take some time to backfill the primary key indexes. The response looks like: { "indexesReady": true} ### PUT `/api/streaming_import/add_primary_key_indexes`[​](https://docs.convex.dev/streaming-import-api#put-apistreaming_importadd_primary_key_indexes "Direct link to put-apistreaming_importadd_primary_key_indexes") The `add_primary_key_indexes` endpoint takes a JSON body containing the primary keys for tables and creates indexes on the primary keys to be backfilled. Note that they are not immediately ready to query - the `primary_key_indexes_ready` endpoint needs to be polled until it returns True before calling `import_airbyte_records` with records that require primary key indexes. Also note that Convex queries will not have access to these added indexes. These are solely for use in `import_airbyte_records`. The body takes the form of a map of index names to list of field paths to index. Each field path is represented by a list of fields that can represent nested field paths. { "indexes": { "": [[""], ["", ""]] }} Expected API Usage: 1. Add indexes for primary keys by making a request to `add_primary_key_indexes`. 2. Poll `primary_key_indexes_ready` until the response is true. 3. Query using the added indexes. ### PUT `api/streaming_import/clear_tables`[​](https://docs.convex.dev/streaming-import-api#put-apistreaming_importclear_tables "Direct link to put-apistreaming_importclear_tables") The `clear_tables` endpoint deletes all documents from the specified tables. Note that this may require multiple transactions. If there is an intermediate error only some documents may be deleted. The JSON body to use this API request contains a list of table names: { "tableNames": ["", ""]} ### POST `api/streaming_import/replace_tables`[​](https://docs.convex.dev/streaming-import-api#post-apistreaming_importreplace_tables "Direct link to post-apistreaming_importreplace_tables") This endpoint is no longer supported. Use `api/streaming_import/clear_tables` instead. The `replace_tables` endpoint renames tables with temporary names to their final names, deleting any existing tables with the final names. The JSON body to use this API request contains a list of table names: { "tableNames": { "": "", "": "" }} ### POST `api/streaming_import/import_airbyte_records`[​](https://docs.convex.dev/streaming-import-api#post-apistreaming_importimport_airbyte_records "Direct link to post-apistreaming_importimport_airbyte_records") The `import_airbyte_records` endpoint enables streaming ingress into a Convex deployment and is designed to be called from an Airbyte destination connector. It takes a map of streams and a list of messages in the JSON body. Each stream has a name and JSON schema that will correspond to a Convex table. Streams where records should be deduplicated include a primary key as well, which is represented as a list of lists of strings that are field paths. Records for streams without a primary key are appended to tables; records for streams with a primary key replace an existing record where the primary key value matches or are appended if there is no match. If you are using primary keys, you must call the `add_primary_key_indexes` endpoint first and wait for them to backfill by polling `primary_key_indexes_ready`. Each message contains a stream name and a JSON document that will be inserted (or replaced, in the case of deduplicated sync) into the table with the corresponding stream name. Table names are same as the stream names. Airbyte records become Convex documents. { "tables": { "": { "primaryKey": [[""], ["", ""]], "jsonSchema": // see https://json-schema.org/ for examples } }, "messages": [{ "tableName": "", "data": {} // JSON object conforming to the `json_schema` for that stream }]} Similar to `clear_tables`, it is possible to execute a partial import using `import_airbyte_records` if there is a failure after a transaction has committed. Expected API Usage: 1. \[Optional\] Add any indexes if using primary keys and [deduplicated sync](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history/) (see `add_primary_key_indexes` above). 2. \[Optional\] Delete all documents in specified tables using `clear_tables` if using [overwrite sync](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-overwrite) . 3. Make a request to `import_airbyte_records` with new records to sync and stream information. * [Headers](https://docs.convex.dev/streaming-import-api#headers) * [GET `/api/streaming_import/primary_key_indexes_ready`](https://docs.convex.dev/streaming-import-api#get-apistreaming_importprimary_key_indexes_ready) * [PUT `/api/streaming_import/add_primary_key_indexes`](https://docs.convex.dev/streaming-import-api#put-apistreaming_importadd_primary_key_indexes) * [PUT `api/streaming_import/clear_tables`](https://docs.convex.dev/streaming-import-api#put-apistreaming_importclear_tables) * [POST `api/streaming_import/replace_tables`](https://docs.convex.dev/streaming-import-api#post-apistreaming_importreplace_tables) * [POST `api/streaming_import/import_airbyte_records`](https://docs.convex.dev/streaming-import-api#post-apistreaming_importimport_airbyte_records) --- # Full Text Search | Convex Developer Hub [Skip to main content](https://docs.convex.dev/search/text-search#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Full text search allows you to find Convex documents that approximately match a search query. Unlike normal [document queries](https://docs.convex.dev/database/reading-data/#querying-documents) , search queries look _within_ a string field to find the keywords. Search queries are useful for building features like searching for messages that contain certain words. Search queries are automatically reactive, consistent, transactional, and work seamlessly with pagination. They even include new documents created with a mutation! **Example:** [Search App](https://github.com/get-convex/convex-demos/tree/main/search) To use full text search you need to: 1. Define a search index. 2. Run a search query. Search indexes are built and queried using Convex's multi-segment search algorithm on top of [Tantivy](https://github.com/quickwit-oss/tantivy) , a powerful, open-source, full-text search library written in Rust. Defining search indexes[​](https://docs.convex.dev/search/text-search#defining-search-indexes "Direct link to Defining search indexes") ---------------------------------------------------------------------------------------------------------------------------------------- Like [database indexes](https://docs.convex.dev/database/reading-data/indexes/) , search indexes are a data structure that is built in advance to enable efficient querying. Search indexes are defined as part of your Convex [schema](https://docs.convex.dev/database/schemas) . Every search index definition consists of: 1. A name. * Must be unique per table. 2. A `searchField` * This is the field which will be indexed for full text search. * It must be of type `string`. 3. \[Optional\] A list of `filterField`s * These are additional fields that are indexed for fast equality filtering within your search index. 4. \[Optional\] A boolean `staged` flag * If set to `true`, the index will be backfilled asynchronously from the deploy similar to [staged database indexes](https://docs.convex.dev/database/reading-data/indexes#staged-indexes) . This is useful for large tables where the index backfill time is significant. Defaults to `false`. To add a search index onto a table, use the [`searchIndex`](https://docs.convex.dev/api/classes/server.TableDefinition#searchindex) method on your table's schema. For example, if you want an index which can search for messages matching a keyword in a channel, your schema could look like: convex/schema.ts import { defineSchema, defineTable } from "convex/server";import { v } from "convex/values";export default defineSchema({ messages: defineTable({ body: v.string(), channel: v.string(), }).searchIndex("search_body", { searchField: "body", filterFields: ["channel"], staged: false, }),}); You can specify search and filter fields on nested documents by using a dot-separated path like `properties.name`. Running search queries[​](https://docs.convex.dev/search/text-search#running-search-queries "Direct link to Running search queries") ------------------------------------------------------------------------------------------------------------------------------------- A query for "10 messages in channel '#general' that best match the query 'hello hi' in their body" would look like: const messages = await ctx.db .query("messages") .withSearchIndex("search_body", (q) => q.search("body", "hello hi").eq("channel", "#general"), ) .take(10); This is just a normal [database read](https://docs.convex.dev/database/reading-data/) that begins by querying the search index! The [`.withSearchIndex`](https://docs.convex.dev/api/interfaces/server.QueryInitializer#withsearchindex) method defines which search index to query and how Convex will use that search index to select documents. The first argument is the name of the index and the second is a _search filter expression_. A search filter expression is a description of which documents Convex should consider when running the query. A search filter expression is always a chained list of: 1. 1 search expression against the index's search field defined with [`.search`](https://docs.convex.dev/api/interfaces/server.SearchFilterBuilder#search) . 2. 0 or more equality expressions against the index's filter fields defined with [`.eq`](https://docs.convex.dev/api/interfaces/server.SearchFilterFinalizer#eq) . ### Search expressions[​](https://docs.convex.dev/search/text-search#search-expressions "Direct link to Search expressions") Search expressions are issued against a search index, filtering and ranking documents by their relevance to the search expression's query. Internally, Convex will break up the query into separate words (called _terms_) and approximately rank documents matching these terms. In the example above, the expression `search("body", "hello hi")` would internally be split into `"hi"` and `"hello"` and matched against words in your document (ignoring case and punctuation). The behavior of search incorporates [prefix matching rules](https://docs.convex.dev/search/text-search#search-behavior) . ### Equality expressions[​](https://docs.convex.dev/search/text-search#equality-expressions "Direct link to Equality expressions") Unlike search expressions, equality expressions will filter to only documents that have an exact match in the given field. In the example above, `eq("channel", "#general")` will only match documents that have exactly `"#general"` in their `channel` field. Equality expressions support fields of any type (not just text). To filter to documents that are missing a field, use `q.eq("fieldName", undefined)`. ### Other filtering[​](https://docs.convex.dev/search/text-search#other-filtering "Direct link to Other filtering") Because search queries are normal database queries, you can also [filter results](https://docs.convex.dev/database/reading-data/filters) using the [`.filter` method](https://docs.convex.dev/api/interfaces/server.Query#filter) ! Here's a query for "messages containing 'hi' sent in the last 10 minutes": const messages = await ctx.db .query("messages") .withSearchIndex("search_body", (q) => q.search("body", "hi")) .filter((q) => q.gt(q.field("_creationTime", Date.now() - 10 * 60000))) .take(10); **For performance, always put as many of your filters as possible into `.withSearchIndex`.** Every search query is executed by: 1. First, querying the search index using the search filter expression in `withSearchIndex`. 2. Then, filtering the results one-by-one using any additional `filter` expressions. Having a very specific search filter expression will make your query faster and less likely to hit Convex's limits because Convex will use the search index to efficiently cut down on the number of results to consider. ### Retrieving results and paginating[​](https://docs.convex.dev/search/text-search#retrieving-results-and-paginating "Direct link to Retrieving results and paginating") Just like ordinary database queries, you can [retrieve the results](https://docs.convex.dev/database/reading-data/#retrieving-results) using [`.collect()`](https://docs.convex.dev/api/interfaces/server.Query#collect) , [`.take(n)`](https://docs.convex.dev/api/interfaces/server.Query#take) , [`.first()`](https://docs.convex.dev/api/interfaces/server.Query#first) , and [`.unique()`](https://docs.convex.dev/api/interfaces/server.Query#unique) . Additionally, search results can be [paginated](https://docs.convex.dev/database/pagination) using [`.paginate(paginationOpts)`](https://docs.convex.dev/api/interfaces/server.OrderedQuery#paginate) . Note that `collect()` will throw an exception if it attempts to collect more than the limit of 1024 documents. It is often better to pick a smaller limit and use `take(n)` or paginate the results. ### Ordering[​](https://docs.convex.dev/search/text-search#ordering "Direct link to Ordering") Search queries always return results in [relevance order](https://docs.convex.dev/search/text-search#relevance-order) based on how well the document matches the search query. Different ordering of results are not supported. Search Behavior[​](https://docs.convex.dev/search/text-search#search-behavior "Direct link to Search Behavior") ---------------------------------------------------------------------------------------------------------------- ### Typeahead Search[​](https://docs.convex.dev/search/text-search#typeahead-search "Direct link to Typeahead Search") Convex full-text search is designed to power as-you-type search experiences. In your search queries, the final search term has _prefix search_ enabled, matching any term that is a prefix of the original term. For example, the expression `search("body", "r")` would match the documents: * `"rabbit"` * `"send request"` Fuzzy search matches are deprecated. After January 15, 2025, search results will not include `"snake"` for a typo like `"stake"`. ### Relevance order[​](https://docs.convex.dev/search/text-search#relevance-order "Direct link to Relevance order") **Relevance order is subject to change.** The relevance of search results and the exact rules Convex applies is subject to change to improve the quality of search results. Search queries return results in relevance order. Internally, Convex ranks the relevance of a document based on a combination of its [BM25 score](https://en.wikipedia.org/wiki/Okapi_BM25) and several other criteria such as the proximity of matches, the number of exact matches, and more. The BM25 score takes into account: * How many words in the search query appear in the field? * How many times do they appear? * How long is the text field? If multiple documents have the same score, the newest documents are returned first. Limits[​](https://docs.convex.dev/search/text-search#limits "Direct link to Limits") ------------------------------------------------------------------------------------- Search indexes work best with English or other Latin-script languages. Text is tokenized using Tantivy's [`SimpleTokenizer`](https://docs.rs/tantivy/latest/tantivy/tokenizer/struct.SimpleTokenizer.html) , which splits on whitespace and punctuation. We also limit terms to 32 characters in length and lowercase them. Search indexes must have: * Exactly 1 search field. * Up to 16 filter fields. Search indexes count against the [limit of 32 indexes per table](https://docs.convex.dev/database/reading-data/indexes/#limits) . Search queries can have: * Up to 16 terms (words) in the search expression. * Up to 8 filter expressions. Additionally, search queries can scan up to 1024 results from the search index. The source of truth for these limits is our [source code](https://github.com/get-convex/convex-backend/blob/main/crates/search/src/constants.rs) . For information on other limits, see [here](https://docs.convex.dev/production/state/limits) . * [Defining search indexes](https://docs.convex.dev/search/text-search#defining-search-indexes) * [Running search queries](https://docs.convex.dev/search/text-search#running-search-queries) * [Search expressions](https://docs.convex.dev/search/text-search#search-expressions) * [Equality expressions](https://docs.convex.dev/search/text-search#equality-expressions) * [Other filtering](https://docs.convex.dev/search/text-search#other-filtering) * [Retrieving results and paginating](https://docs.convex.dev/search/text-search#retrieving-results-and-paginating) * [Ordering](https://docs.convex.dev/search/text-search#ordering) * [Search Behavior](https://docs.convex.dev/search/text-search#search-behavior) * [Typeahead Search](https://docs.convex.dev/search/text-search#typeahead-search) * [Relevance order](https://docs.convex.dev/search/text-search#relevance-order) * [Limits](https://docs.convex.dev/search/text-search#limits) --- # Convex & Clerk | Convex Developer Hub [Skip to main content](https://docs.convex.dev/auth/clerk#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [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[​](https://docs.convex.dev/auth/clerk#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](https://docs.convex.dev/auth/clerk#react) - Use this as a starting point if your SDK is not listed * [Next.js](https://docs.convex.dev/auth/clerk#nextjs) * [TanStack Start](https://docs.convex.dev/auth/clerk#tanstack-start) ### React[​](https://docs.convex.dev/auth/clerk#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](https://docs.convex.dev/quickstart/react) 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](https://docs.convex.dev/screenshots/clerk-signup.png) 2. Create an application in Clerk Choose how you want your users to sign in. ![Create a Clerk application](https://docs.convex.dev/screenshots/clerk-createapp.png) 3. Activate the Convex integration in Clerk In the Clerk Dashboard, activate the [Convex integration](https://dashboard.clerk.com/apps/setup/convex) . ![Activate the Convex integration in Clerk](https://docs.convex.dev/screenshots/clerk-convex-integration.png) Copy your Clerk app's _Frontend API URL_. In development, its format will be `https://verb-noun-00.clerk.accounts.dev`. In production, its format will be `https://clerk..com`. 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 Clerk Frontend API URL // 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. 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. tip If you choose to build your own auth-integrated components without using the helpers, it's important to use the [`useConvexAuth()`](https://docs.convex.dev/api/modules/react#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. 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[​](https://docs.convex.dev/auth/clerk#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](https://docs.convex.dev/quickstart/nextjs) 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](https://docs.convex.dev/screenshots/clerk-signup.png) 2. Create an application in Clerk Choose how you want your users to sign in. ![Create a Clerk application](https://docs.convex.dev/screenshots/clerk-createapp.png) 3. Activate the Convex integration in Clerk In the Clerk Dashboard, activate the [Convex integration](https://dashboard.clerk.com/apps/setup/convex) . ![Activate the Convex integration in Clerk](https://docs.convex.dev/screenshots/clerk-convex-integration.png) Copy your Clerk app's _Frontend API URL_. In development, its format will be `https://verb-noun-00.clerk.accounts.dev`. In production, its format will be `https://clerk..com`. 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 Clerk Frontend API URL // 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_KEYCLERK_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.[https://clerk.com/docs/references/nextjs/clerk-middleware](https://clerk.com/docs/references/nextjs/clerk-middleware) ) 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. 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. tip If you choose to build your own auth-integrated components without using the helpers, it's important to use the [`useConvexAuth()`](https://docs.convex.dev/api/modules/react#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. 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[​](https://docs.convex.dev/auth/clerk#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](https://docs.convex.dev/client/tanstack/tanstack-start/clerk) for more information. Next steps[​](https://docs.convex.dev/auth/clerk#next-steps "Direct link to Next steps") ----------------------------------------------------------------------------------------- ### Accessing user information in functions[​](https://docs.convex.dev/auth/clerk#accessing-user-information-in-functions "Direct link to Accessing user information in functions") See [Auth in Functions](https://docs.convex.dev/auth/functions-auth) to learn about how to access information about the authenticated user in your queries, mutations and actions. See [Storing Users in the Convex Database](https://docs.convex.dev/auth/database-auth) to learn about how to store user information in the Convex database. ### Accessing user information client-side[​](https://docs.convex.dev/auth/clerk#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[​](https://docs.convex.dev/auth/clerk#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[​](https://docs.convex.dev/auth/clerk#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 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](https://docs.convex.dev/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[​](https://docs.convex.dev/auth/clerk#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[​](https://docs.convex.dev/auth/clerk#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](https://docs.convex.dev/auth/debug) . Under the hood[​](https://docs.convex.dev/auth/clerk#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. * [Get started](https://docs.convex.dev/auth/clerk#get-started) * [React](https://docs.convex.dev/auth/clerk#react) * [Next.js](https://docs.convex.dev/auth/clerk#nextjs) * [TanStack Start](https://docs.convex.dev/auth/clerk#tanstack-start) * [Next steps](https://docs.convex.dev/auth/clerk#next-steps) * [Accessing user information in functions](https://docs.convex.dev/auth/clerk#accessing-user-information-in-functions) * [Accessing user information client-side](https://docs.convex.dev/auth/clerk#accessing-user-information-client-side) * [Configuring dev and prod instances](https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances) * [Configuring the backend](https://docs.convex.dev/auth/clerk#configuring-the-backend) * [Configuring Clerk's API keys](https://docs.convex.dev/auth/clerk#configuring-clerks-api-keys) * [Debugging authentication](https://docs.convex.dev/auth/clerk#debugging-authentication) * [Under the hood](https://docs.convex.dev/auth/clerk#under-the-hood) --- # Module: nextjs | Convex Developer Hub [Skip to main content](https://docs.convex.dev/api/modules/nextjs#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Helpers for integrating Convex into Next.js applications using server rendering. This module contains: 1. [preloadQuery](https://docs.convex.dev/api/modules/nextjs#preloadquery) , for preloading data for reactive client components. 2. [fetchQuery](https://docs.convex.dev/api/modules/nextjs#fetchquery) , [fetchMutation](https://docs.convex.dev/api/modules/nextjs#fetchmutation) and [fetchAction](https://docs.convex.dev/api/modules/nextjs#fetchaction) for loading and mutating Convex data from Next.js Server Components, Server Actions and Route Handlers. Usage[​](https://docs.convex.dev/api/modules/nextjs#usage "Direct link to Usage") ---------------------------------------------------------------------------------- All exported functions assume that a Convex deployment URL is set in the `NEXT_PUBLIC_CONVEX_URL` environment variable. `npx convex dev` will automatically set it during local development. ### Preloading data[​](https://docs.convex.dev/api/modules/nextjs#preloading-data "Direct link to Preloading data") Preload data inside a Server Component: import { preloadQuery } from "convex/nextjs";import { api } from "@/convex/_generated/api";import ClientComponent from "./ClientComponent";export async function ServerComponent() { const preloaded = await preloadQuery(api.foo.baz); return ;} And pass it to a Client Component: import { Preloaded, usePreloadedQuery } from "convex/react";import { api } from "@/convex/_generated/api";export function ClientComponent(props: { preloaded: Preloaded;}) { const data = usePreloadedQuery(props.preloaded); // render `data`...} Type Aliases[​](https://docs.convex.dev/api/modules/nextjs#type-aliases "Direct link to Type Aliases") ------------------------------------------------------------------------------------------------------- ### NextjsOptions[​](https://docs.convex.dev/api/modules/nextjs#nextjsoptions "Direct link to NextjsOptions") Ƭ **NextjsOptions**: `Object` Options to [preloadQuery](https://docs.convex.dev/api/modules/nextjs#preloadquery) , [fetchQuery](https://docs.convex.dev/api/modules/nextjs#fetchquery) , [fetchMutation](https://docs.convex.dev/api/modules/nextjs#fetchmutation) and [fetchAction](https://docs.convex.dev/api/modules/nextjs#fetchaction) . #### Type declaration[​](https://docs.convex.dev/api/modules/nextjs#type-declaration "Direct link to Type declaration") | Name | Type | Description | | --- | --- | --- | | `token?` | `string` | The JWT-encoded OpenID Connect authentication token to use for the function call. | | `url?` | `string` | The URL of the Convex deployment to use for the function call. Defaults to `process.env.NEXT_PUBLIC_CONVEX_URL` if not provided. Explicitly passing undefined here (such as from missing ENV variables) will throw an error in the future. | | `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[​](https://docs.convex.dev/api/modules/nextjs#defined-in "Direct link to Defined in") [nextjs/index.ts:60](https://github.com/get-convex/convex-js/blob/main/src/nextjs/index.ts#L60) Functions[​](https://docs.convex.dev/api/modules/nextjs#functions "Direct link to Functions") ---------------------------------------------------------------------------------------------- ### preloadQuery[​](https://docs.convex.dev/api/modules/nextjs#preloadquery "Direct link to preloadQuery") ▸ **preloadQuery**<`Query`\>(`query`, `...args`): `Promise`<[`Preloaded`](https://docs.convex.dev/api/modules/react#preloaded) <`Query`\>> Execute a Convex query function and return a `Preloaded` payload which can be passed to [usePreloadedQuery](https://docs.convex.dev/api/modules/react#usepreloadedquery) in a Client Component. #### Type parameters[​](https://docs.convex.dev/api/modules/nextjs#type-parameters "Direct link to Type parameters") | Name | Type | | --- | --- | | `Query` | extends [`FunctionReference`](https://docs.convex.dev/api/modules/server#functionreference)
<`"query"`\> | #### Parameters[​](https://docs.convex.dev/api/modules/nextjs#parameters "Direct link to Parameters") | Name | Type | Description | | --- | --- | --- | | `query` | `Query` | a [FunctionReference](https://docs.convex.dev/api/modules/server#functionreference)
for the public query to run like `api.dir1.dir2.filename.func`. | | `...args` | [`ArgsAndOptions`](https://docs.convex.dev/api/modules/server#argsandoptions)
<`Query`, [`NextjsOptions`](https://docs.convex.dev/api/modules/nextjs#nextjsoptions)
\> | The arguments object for the query. If this is omitted, the arguments will be `{}`. | #### Returns[​](https://docs.convex.dev/api/modules/nextjs#returns "Direct link to Returns") `Promise`<[`Preloaded`](https://docs.convex.dev/api/modules/react#preloaded) <`Query`\>> A promise of the `Preloaded` payload. #### Defined in[​](https://docs.convex.dev/api/modules/nextjs#defined-in-1 "Direct link to Defined in") [nextjs/index.ts:101](https://github.com/get-convex/convex-js/blob/main/src/nextjs/index.ts#L101) * * * ### preloadedQueryResult[​](https://docs.convex.dev/api/modules/nextjs#preloadedqueryresult "Direct link to preloadedQueryResult") ▸ **preloadedQueryResult**<`Query`\>(`preloaded`): [`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Query`\> Returns the result of executing a query via [preloadQuery](https://docs.convex.dev/api/modules/nextjs#preloadquery) . #### Type parameters[​](https://docs.convex.dev/api/modules/nextjs#type-parameters-1 "Direct link to Type parameters") | Name | Type | | --- | --- | | `Query` | extends [`FunctionReference`](https://docs.convex.dev/api/modules/server#functionreference)
<`"query"`\> | #### Parameters[​](https://docs.convex.dev/api/modules/nextjs#parameters-1 "Direct link to Parameters") | Name | Type | Description | | --- | --- | --- | | `preloaded` | [`Preloaded`](https://docs.convex.dev/api/modules/react#preloaded)
<`Query`\> | The `Preloaded` payload returned by [preloadQuery](https://docs.convex.dev/api/modules/nextjs#preloadquery)
. | #### Returns[​](https://docs.convex.dev/api/modules/nextjs#returns-1 "Direct link to Returns") [`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Query`\> The query result. #### Defined in[​](https://docs.convex.dev/api/modules/nextjs#defined-in-2 "Direct link to Defined in") [nextjs/index.ts:120](https://github.com/get-convex/convex-js/blob/main/src/nextjs/index.ts#L120) * * * ### fetchQuery[​](https://docs.convex.dev/api/modules/nextjs#fetchquery "Direct link to fetchQuery") ▸ **fetchQuery**<`Query`\>(`query`, `...args`): `Promise`<[`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Query`\>> Execute a Convex query function. #### Type parameters[​](https://docs.convex.dev/api/modules/nextjs#type-parameters-2 "Direct link to Type parameters") | Name | Type | | --- | --- | | `Query` | extends [`FunctionReference`](https://docs.convex.dev/api/modules/server#functionreference)
<`"query"`\> | #### Parameters[​](https://docs.convex.dev/api/modules/nextjs#parameters-2 "Direct link to Parameters") | Name | Type | Description | | --- | --- | --- | | `query` | `Query` | a [FunctionReference](https://docs.convex.dev/api/modules/server#functionreference)
for the public query to run like `api.dir1.dir2.filename.func`. | | `...args` | [`ArgsAndOptions`](https://docs.convex.dev/api/modules/server#argsandoptions)
<`Query`, [`NextjsOptions`](https://docs.convex.dev/api/modules/nextjs#nextjsoptions)
\> | The arguments object for the query. If this is omitted, the arguments will be `{}`. | #### Returns[​](https://docs.convex.dev/api/modules/nextjs#returns-2 "Direct link to Returns") `Promise`<[`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Query`\>> A promise of the query's result. #### Defined in[​](https://docs.convex.dev/api/modules/nextjs#defined-in-3 "Direct link to Defined in") [nextjs/index.ts:136](https://github.com/get-convex/convex-js/blob/main/src/nextjs/index.ts#L136) * * * ### fetchMutation[​](https://docs.convex.dev/api/modules/nextjs#fetchmutation "Direct link to fetchMutation") ▸ **fetchMutation**<`Mutation`\>(`mutation`, `...args`): `Promise`<[`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Mutation`\>> Execute a Convex mutation function. #### Type parameters[​](https://docs.convex.dev/api/modules/nextjs#type-parameters-3 "Direct link to Type parameters") | Name | Type | | --- | --- | | `Mutation` | extends [`FunctionReference`](https://docs.convex.dev/api/modules/server#functionreference)
<`"mutation"`\> | #### Parameters[​](https://docs.convex.dev/api/modules/nextjs#parameters-3 "Direct link to Parameters") | Name | Type | Description | | --- | --- | --- | | `mutation` | `Mutation` | A [FunctionReference](https://docs.convex.dev/api/modules/server#functionreference)
for the public mutation to run like `api.dir1.dir2.filename.func`. | | `...args` | [`ArgsAndOptions`](https://docs.convex.dev/api/modules/server#argsandoptions)
<`Mutation`, [`NextjsOptions`](https://docs.convex.dev/api/modules/nextjs#nextjsoptions)
\> | The arguments object for the mutation. If this is omitted, the arguments will be `{}`. | #### Returns[​](https://docs.convex.dev/api/modules/nextjs#returns-3 "Direct link to Returns") `Promise`<[`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Mutation`\>> A promise of the mutation's result. #### Defined in[​](https://docs.convex.dev/api/modules/nextjs#defined-in-4 "Direct link to Defined in") [nextjs/index.ts:155](https://github.com/get-convex/convex-js/blob/main/src/nextjs/index.ts#L155) * * * ### fetchAction[​](https://docs.convex.dev/api/modules/nextjs#fetchaction "Direct link to fetchAction") ▸ **fetchAction**<`Action`\>(`action`, `...args`): `Promise`<[`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Action`\>> Execute a Convex action function. #### Type parameters[​](https://docs.convex.dev/api/modules/nextjs#type-parameters-4 "Direct link to Type parameters") | Name | Type | | --- | --- | | `Action` | extends [`FunctionReference`](https://docs.convex.dev/api/modules/server#functionreference)
<`"action"`\> | #### Parameters[​](https://docs.convex.dev/api/modules/nextjs#parameters-4 "Direct link to Parameters") | Name | Type | Description | | --- | --- | --- | | `action` | `Action` | A [FunctionReference](https://docs.convex.dev/api/modules/server#functionreference)
for the public action to run like `api.dir1.dir2.filename.func`. | | `...args` | [`ArgsAndOptions`](https://docs.convex.dev/api/modules/server#argsandoptions)
<`Action`, [`NextjsOptions`](https://docs.convex.dev/api/modules/nextjs#nextjsoptions)
\> | The arguments object for the action. If this is omitted, the arguments will be `{}`. | #### Returns[​](https://docs.convex.dev/api/modules/nextjs#returns-4 "Direct link to Returns") `Promise`<[`FunctionReturnType`](https://docs.convex.dev/api/modules/server#functionreturntype) <`Action`\>> A promise of the action's result. #### Defined in[​](https://docs.convex.dev/api/modules/nextjs#defined-in-5 "Direct link to Defined in") [nextjs/index.ts:176](https://github.com/get-convex/convex-js/blob/main/src/nextjs/index.ts#L176) * [Usage](https://docs.convex.dev/api/modules/nextjs#usage) * [Preloading data](https://docs.convex.dev/api/modules/nextjs#preloading-data) * [Type Aliases](https://docs.convex.dev/api/modules/nextjs#type-aliases) * [NextjsOptions](https://docs.convex.dev/api/modules/nextjs#nextjsoptions) * [Functions](https://docs.convex.dev/api/modules/nextjs#functions) * [preloadQuery](https://docs.convex.dev/api/modules/nextjs#preloadquery) * [preloadedQueryResult](https://docs.convex.dev/api/modules/nextjs#preloadedqueryresult) * [fetchQuery](https://docs.convex.dev/api/modules/nextjs#fetchquery) * [fetchMutation](https://docs.convex.dev/api/modules/nextjs#fetchmutation) * [fetchAction](https://docs.convex.dev/api/modules/nextjs#fetchaction) --- # Vector Search | Convex Developer Hub [Skip to main content](https://docs.convex.dev/search/vector-search#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Vector search allows you to find Convex documents similar to a provided vector. Typically, vectors will be embeddings which are numerical representations of text, images, or audio. Embeddings and vector search enable you to provide useful context to LLMs for AI powered applications, recommendations for similar content and more. Vector search is consistent and fully up-to-date. You can write a vector and immediately read it from a vector search. Unlike [full text search](https://docs.convex.dev/search) , however, vector search is only available in [Convex actions](https://docs.convex.dev/functions/actions) . **Example:** [Vector Search App](https://github.com/get-convex/convex-demos/tree/main/vector-search) To use vector search you need to: 1. Define a vector index. 2. Run a vector search from within an [action](https://docs.convex.dev/functions/actions) . Defining vector indexes[​](https://docs.convex.dev/search/vector-search#defining-vector-indexes "Direct link to Defining vector indexes") ------------------------------------------------------------------------------------------------------------------------------------------ Like [database indexes](https://docs.convex.dev/database/reading-data/indexes/) , vector indexes are a data structure that is built in advance to enable efficient querying. Vector indexes are defined as part of your Convex [schema](https://docs.convex.dev/database/schemas) . To add a vector index onto a table, use the [`vectorIndex`](https://docs.convex.dev/api/classes/server.TableDefinition#vectorindex) method on your table's schema. Every vector index has a unique name and a definition with: 1. `vectorField` string * The name of the field indexed for vector search. 2. `dimensions` number * The fixed size of the vectors index. If you're using embeddings, this dimension should match the size of your embeddings (e.g. `1536` for OpenAI). 3. \[Optional\] `filterFields` array * The names of additional fields that are indexed for fast filtering within your vector index. 4. \[Optional\] `staged` boolean * If set to `true`, the index will be backfilled asynchronously from the deploy similar to [staged database indexes](https://docs.convex.dev/database/reading-data/indexes#staged-indexes) . This is useful for large tables where the index backfill time is significant. Defaults to `false`. For example, if you want an index that can search for similar foods within a given cuisine, your table definition could look like: convex/schema.ts foods: defineTable({ description: v.string(), cuisine: v.string(), embedding: v.array(v.float64()),}).vectorIndex("by_embedding", { vectorField: "embedding", dimensions: 1536, filterFields: ["cuisine"],}), You can specify vector and filter fields on nested documents by using a dot-separated path like `properties.name`. Running vector searches[​](https://docs.convex.dev/search/vector-search#running-vector-searches "Direct link to Running vector searches") ------------------------------------------------------------------------------------------------------------------------------------------ Unlike database queries or full text search, vector searches can only be performed in a [Convex action](https://docs.convex.dev/functions/actions) . They generally involve three steps: 1. Generate a vector from provided input (e.g. using OpenAI) 2. Use [`ctx.vectorSearch`](https://docs.convex.dev/api/interfaces/server.GenericActionCtx#vectorsearch) to fetch the IDs of similar documents 3. Load the desired information for the documents Here's an example of the first two steps for searching for similar French foods based on a description: convex/foods.ts TS import { v } from "convex/values";import { action } from "./_generated/server";export const similarFoods = action({ args: { descriptionQuery: v.string(), }, handler: async (ctx, args) => { // 1. Generate an embedding from your favorite third party API: const embedding = await embed(args.descriptionQuery); // 2. Then search for similar foods! const results = await ctx.vectorSearch("foods", "by_embedding", { vector: embedding, limit: 16, filter: (q) => q.eq("cuisine", "French"), }); // ... },}); An example of the first step can be found [here](https://github.com/get-convex/convex-demos/blob/main/vector-search/convex/foods.ts#L18) in the vector search demo app. Focusing on the second step, the `vectorSearch` API takes in the table name, the index name, and finally a [`VectorSearchQuery`](https://docs.convex.dev/api/interfaces/server.VectorSearchQuery) object describing the search. This object has the following fields: 1. `vector` array * An array of numbers (e.g. embedding) to use in the search. * The search will return the document IDs of the documents with the most similar stored vectors. * It must have the same length as the `dimensions` of the index. 2. \[Optional\] `limit` number * The number of results to get back. If specified, this value must be between 1 and 256. 3. \[Optional\] `filter` * An expression that restricts the set of results based on the `filterFields` in the `vectorIndex` in your schema. See [Filter expressions](https://docs.convex.dev/search/vector-search#filter-expressions) for details. It returns an `Array` of objects containing exactly two fields: 1. `_id` * The [Document ID](https://docs.convex.dev/database/document-ids) for the matching document in the table 2. `_score` * An indicator of how similar the result is to the vector you were searching for, ranging from -1 (least similar) to 1 (most similar) Neither the underlying document nor the vector are included in `results`, so once you have the list of results, you will want to load the desired information about the results. There are a few strategies for loading this information documented in the [Advanced Patterns](https://docs.convex.dev/search/vector-search#advanced-patterns) section. For now, let's load the documents and return them from the action. To do so, we'll pass the list of results to a Convex query and run it inside of our action, returning the result: convex/foods.ts TS export const fetchResults = internalQuery({ args: { ids: v.array(v.id("foods")) }, handler: async (ctx, args) => { const results = []; for (const id of args.ids) { const doc = await ctx.db.get("foods", id); if (doc === null) { continue; } results.push(doc); } return results; },}); convex/foods.ts TS export const similarFoods = action({ args: { descriptionQuery: v.string(), }, handler: async (ctx, args) => { // 1. Generate an embedding from your favorite third party API: const embedding = await embed(args.descriptionQuery); // 2. Then search for similar foods! const results = await ctx.vectorSearch("foods", "by_embedding", { vector: embedding, limit: 16, filter: (q) => q.eq("cuisine", "French"), }); // 3. Fetch the results const foods: Array> = await ctx.runQuery( internal.foods.fetchResults, { ids: results.map((result) => result._id) }, ); return foods; },}); ### Filter expressions[​](https://docs.convex.dev/search/vector-search#filter-expressions "Direct link to Filter expressions") As mentioned above, vector searches support efficiently filtering results by additional fields on your document using either exact equality on a single field, or an `OR` of expressions. For example, here's a filter for foods with cuisine exactly equal to "French": filter: (q) => q.eq("cuisine", "French"), You can also filter documents by a single field that contains several different values using an `or` expression. Here's a filter for French or Indonesian dishes: filter: (q) => q.or(q.eq("cuisine", "French"), q.eq("cuisine", "Indonesian")), For indexes with multiple filter fields, you can also use `.or()` filters on different fields. Here's a filter for dishes whose cuisine is French or whose main ingredient is butter: filter: (q) => q.or(q.eq("cuisine", "French"), q.eq("mainIngredient", "butter")), **Both `cuisine` and `mainIngredient` would need to be included in the `filterFields` in the `.vectorIndex` definition.** ### Other filtering[​](https://docs.convex.dev/search/vector-search#other-filtering "Direct link to Other filtering") Results can be filtered based on how similar they are to the provided vector using the `_score` field in your action: const results = await ctx.vectorSearch("foods", "by_embedding", { vector: embedding,});const filteredResults = results.filter((result) => result._score >= 0.9); Additional filtering can always be done by passing the vector search results to a query or mutation function that loads the documents and performs filtering using any of the fields on the document. **For performance, always put as many of your filters as possible into `.vectorSearch`.** ### Ordering[​](https://docs.convex.dev/search/vector-search#ordering "Direct link to Ordering") Vector queries always return results in relevance order. Currently Convex searches vectors using an [approximate nearest neighbor search](https://en.wikipedia.org/wiki/Nearest_neighbor_search#Approximate_nearest_neighbor) based on [cosine similarity](https://en.wikipedia.org/wiki/Cosine_similarity) . Support for more similarity metrics [will come in the future](https://docs.convex.dev/search/vector-search#future-development) . If multiple documents have the same score, ties are broken by the document ID. Advanced patterns[​](https://docs.convex.dev/search/vector-search#advanced-patterns "Direct link to Advanced patterns") ------------------------------------------------------------------------------------------------------------------------ ### Using a separate table to store vectors[​](https://docs.convex.dev/search/vector-search#using-a-separate-table-to-store-vectors "Direct link to Using a separate table to store vectors") There are two main options for setting up a vector index: 1. Storing vectors in the same table as other metadata 2. Storing vectors in a separate table, with a reference The examples above show the first option, which is simpler and works well for reading small amounts of documents. The second option is more complex, but better supports reading or returning large amounts of documents. Since vectors are typically large and not useful beyond performing vector searches, it's nice to avoid loading them from the database when reading other data (e.g. `db.get()`) or returning them from functions by storing them in a separate table. A table definition for movies, and a vector index supporting search for similar movies filtering by genre would look like this: convex/schema.ts movieEmbeddings: defineTable({ embedding: v.array(v.float64()), genre: v.string(),}).vectorIndex("by_embedding", { vectorField: "embedding", dimensions: 1536, filterFields: ["genre"],}),movies: defineTable({ title: v.string(), genre: v.string(), description: v.string(), votes: v.number(), embeddingId: v.optional(v.id("movieEmbeddings")),}).index("by_embedding", ["embeddingId"]), Generating an embedding and running a vector search are the same as using a single table. Loading the relevant documents given the vector search result is different since we have an ID for `movieEmbeddings` but want to load a `movies` document. We can do this using the `by_embedding` database index on the `movies` table: convex/movies.ts TS export const fetchMovies = query({ args: { ids: v.array(v.id("movieEmbeddings")), }, handler: async (ctx, args) => { const results = []; for (const id of args.ids) { const doc = await ctx.db .query("movies") .withIndex("by_embedding", (q) => q.eq("embeddingId", id)) .unique(); if (doc === null) { continue; } results.push(doc); } return results; },}); ### Fetching results and adding new documents[​](https://docs.convex.dev/search/vector-search#fetching-results-and-adding-new-documents "Direct link to Fetching results and adding new documents") Returning information from a vector search involves an action (since vector search is only available in actions) and a query or mutation to load the data. The example above used a query to load data and return it from an action. Since this is an action, the data returned is not reactive. An alternative would be to return the results of the vector search in the action, and have a separate query that reactively loads the data. The search results will not update reactively, but the data about each result would be reactive. The [Vector Search Demo App](https://github.com/get-convex/convex-demos/tree/main/vector-search) uses this strategy to show similar movies with a reactive "Votes" count. Limits[​](https://docs.convex.dev/search/vector-search#limits "Direct link to Limits") --------------------------------------------------------------------------------------- Convex supports millions of vectors today. This is an ongoing project and we will continue to scale this offering out with the rest of Convex. Vector indexes must have: * Exactly 1 vector index field. * The field must be of type `v.array(v.float64())` (or a union in which one of the possible types is `v.array(v.float64())`) * Exactly 1 dimension field with a value between 2 and 4096. * Up to 16 filter fields. Vector indexes count towards the [limit of 32 indexes per table](https://docs.convex.dev/database/reading-data/indexes/#limits) . In addition you can have up to 4 vector indexes per table. Vector searches can have: * Exactly 1 vector to search by in the `vector` field * Up to 64 filter expressions * Up to 256 requested results (defaulting to 10). If your action performs a vector search then passes the results to a query or mutation function, you may find that one or more results from the vector search have been deleted or mutated. Because vector search is only available in actions, you cannot perform additional transactional queries or mutations based on the results. If this is important for your use case, please [let us know on Discord](https://convex.dev/community) ! Only documents that contain a vector of the size and in the field specified by a vector index will be included in the index and returned by the vector search. For information on limits, see [here](https://docs.convex.dev/production/state/limits) . Future development[​](https://docs.convex.dev/search/vector-search#future-development "Direct link to Future development") --------------------------------------------------------------------------------------------------------------------------- We're always open to customer feedback and requests. Some ideas we've considered for improving vector search in Convex include: * More sophisticated filters and filter syntax * Filtering by score in the `vectorSearch` API * Better support for generating embeddings If any of these features is important for your app, [let us know on Discord](https://convex.dev/community) ! * [Defining vector indexes](https://docs.convex.dev/search/vector-search#defining-vector-indexes) * [Running vector searches](https://docs.convex.dev/search/vector-search#running-vector-searches) * [Filter expressions](https://docs.convex.dev/search/vector-search#filter-expressions) * [Other filtering](https://docs.convex.dev/search/vector-search#other-filtering) * [Ordering](https://docs.convex.dev/search/vector-search#ordering) * [Advanced patterns](https://docs.convex.dev/search/vector-search#advanced-patterns) * [Using a separate table to store vectors](https://docs.convex.dev/search/vector-search#using-a-separate-table-to-store-vectors) * [Fetching results and adding new documents](https://docs.convex.dev/search/vector-search#fetching-results-and-adding-new-documents) * [Limits](https://docs.convex.dev/search/vector-search#limits) * [Future development](https://docs.convex.dev/search/vector-search#future-development) --- # OAuth Applications | Convex Developer Hub [Skip to main content](https://docs.convex.dev/platform-apis/oauth-applications#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Convex allows third-party app developers to manage a user's projects on their behalf through **Application Tokens**. Application tokens are obtained through the OAuth authorization code flow, which will be described in detail below. There are two types of OAuth tokens: * Team-scoped tokens that are authorized to create new projects, create new deployments within projects, and have read and write access to data and functions to every project on the team. * Project-scoped tokens that can create new deployments and access data and functions within a specific project. note All application tokens are also scoped to the permissions of the member that authorized usage. If the member is removed from the team, or their permissions changed, the permissions of the token will also change. Creating an application[​](https://docs.convex.dev/platform-apis/oauth-applications#creating-an-application "Direct link to Creating an application") ------------------------------------------------------------------------------------------------------------------------------------------------------ To obtain application tokens, you'll first have to register an OAuth application with Convex, which can be done in your [Team Settings](https://dashboard.convex.dev/team/settings/applications/oauth-apps) . To register an application, you'll need to provide a name for the application and a set of redirect URIs. Redirect URIs are used to return users to your application once they have authorized you to access their Convex team or project. You may add up to 20 redirect URIs, including ones pointing to [localhost](http://localhost/) for testing. Once you've created your application, it will be in the "Unverified" state. In the Unverified state, you'll be able to obtain application tokens for your own team, but not for other teams. We recommend testing your application in the Unverified state before requesting verification. You may request verification by clicking the ⋮ button next to your application, and clicking "Request Verification". ### Verification requirements[​](https://docs.convex.dev/platform-apis/oauth-applications#verification-requirements "Direct link to Verification requirements") To have your application be verified and be accessible for all Convex users, it must meet the following criteria: * The application description has an explanation of the capabilities and planned future capabilities of your application. * The application name, redirect URIs, and content of the redirect URIs do not attempt to misrepresent another organization, business, or entity. * The listed redirect URIs belong to your organization. The Convex team will respond to your verification request by email if more information is required. Implementing OAuth[​](https://docs.convex.dev/platform-apis/oauth-applications#implementing-oauth "Direct link to Implementing OAuth") --------------------------------------------------------------------------------------------------------------------------------------- Convex implements [OAuth 2.0](https://oauth.net/2/) (RFC 6749)'s [Authorization Code Grant](https://oauth.net/2/grant-types/authorization-code/) flow. Convex also optionally supports the [PKCE extension](https://oauth.net/2/pkce/) (RFC 7636) to improve security. * Convex provides two _authorization endpoint_ URLs, depending on whether you are generating team-scoped or project-scoped tokens: * `https://dashboard.convex.dev/oauth/authorize/team` * `https://dashboard.convex.dev/oauth/authorize/project` * Convex's **token endpoint** is `https://api.convex.dev/oauth/token`. We'll walk through the authorization flow step by step. However, we recommend using an OAuth 2.0 client library to help construct the required URLs and API calls. Step 1: Redirect the user to Convex's authorization endpoint[​](https://docs.convex.dev/platform-apis/oauth-applications#step-1-redirect-the-user-to-convexs-authorization-endpoint "Direct link to Step 1: Redirect the user to Convex's authorization endpoint") ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- In your application, navigate the user to: `https://dashboard.convex.dev/oauth/authorize[TOKEN_SCOPE]?client_id=[CLIENT_ID]&redirect_uri=[REDIRECT_URI]&response_type=code&state=[STATE]` * `[TOKEN_SCOPE]` should be replaced with "team" or "project" * `[CLIENT_ID]` should be replaced with your app's client ID as assigned by Convex. * `[REDIRECT_URI]` is a URL on your application's domain. The user will be redirected back to that URL after authorizing. * **Important note**: You'll need to provide us with all the redirect URIs that your application might use (likely just one). This is required to prevent a malicious application from masquerading as yours, but redirecting to a different callback. * `[STATE]` is an optional arbitrary string. It's up to you how to encode it, but your application will use this to decide what to do with the auth token after it receives it. **Remember to URI-encode all the parameters!** This brings the user to a page that looks like this: ![OAuth authorization page](https://docs.convex.dev/assets/images/oauth-page-a895fb5b2d41e9fd16dcc4003f1ca6a1.png) From here, the user can select which team they'd like to authorize access to. If using the project flow, the user will also be able to select an existing project or create a new project. After they click "Authorize", the page will redirect to your redirect URL. Step 2: Receive the callback[​](https://docs.convex.dev/platform-apis/oauth-applications#step-2-receive-the-callback "Direct link to Step 2: Receive the callback") -------------------------------------------------------------------------------------------------------------------------------------------------------------------- The authorization endpoint brings the user to your redirect URI with the `code` and `state` query parameters populated. If your `redirect_uri` was `https://yourapp.example.com/cb`, the result would look like: `https://yourapp.example.com/cb?code=[CODE]&state=[STATE]` where `state` is the same value you provided earlier, and `code` is a randomly generated string like `895c59eb98504a5bbaa7ad2e49cf4817`. This code **is not** the final auth token - you'll need to exchange it for one within 10 minutes. Step 3: Exchange the authorization code for a project token[​](https://docs.convex.dev/platform-apis/oauth-applications#step-3-exchange-the-authorization-code-for-a-project-token "Direct link to Step 3: Exchange the authorization code for a project token") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Your application should make an HTTP POST request to the following endpoint: `https://api.convex.dev/oauth/token` The body should have content type `application/x-www-form-urlencoded` and look like the following: `client_id=[CLIENT_ID]&client_secret=[CLIENT_SECRET]&grant_type=authorization_code&redirect_uri=[REDIRECT_URI]&code=[CODE]` * `client_id` is the same one used to construct the authorization endpoint URL. * `client_secret` is your app's secret assigned by Convex. * `grant_type` is always `authorization_code`. * `redirect_uri` is the same one used to construct the authorization endpoint URL. * `code` is the authorization code provided to your callback. * Note that a `code` can only be exchanged _once_ for an access token. If all provided values are correct, Convex's API returns a JSON response containing: { "access_token": "team:my-team|AAAAAA==", "token_type": "bearer"} The `access_token` is the application token! Using PKCE (RFC 7636) ===================== Convex supports this extension to the Authorization Code grant type. Only the `S256` method is allowed. PKCE protects a leaked authorization code from being used by an attacker even if your client secret is not private (e.g. if it has to be embedded in a client-side application). OAuth client libraries typically support PKCE already, but to implement it manually: * Each time you request user authorization, before redirecting the user, construct a random string called the `code_verifier`. It's recommended that you generate a random 32-byte value and base64url-encode it. * Calculate `code_challenge = base64url(sha256(code_verifier))`. This will be a 43-character string. * Redirect the user to authorization endpoint as before (`/oauth/authorize/[TOKEN_SCOPE]`), but additionally provide the parameters `code_challenge=[CODE_CHALLENGE]&code_challenge_method=S256`. * When exchanging the authorization code for a token, additionally provide the parameter `code_verifier=[CODE_VERIFIER]`. * [Creating an application](https://docs.convex.dev/platform-apis/oauth-applications#creating-an-application) * [Verification requirements](https://docs.convex.dev/platform-apis/oauth-applications#verification-requirements) * [Implementing OAuth](https://docs.convex.dev/platform-apis/oauth-applications#implementing-oauth) * [Step 1: Redirect the user to Convex's authorization endpoint](https://docs.convex.dev/platform-apis/oauth-applications#step-1-redirect-the-user-to-convexs-authorization-endpoint) * [Step 2: Receive the callback](https://docs.convex.dev/platform-apis/oauth-applications#step-2-receive-the-callback) * [Step 3: Exchange the authorization code for a project token](https://docs.convex.dev/platform-apis/oauth-applications#step-3-exchange-the-authorization-code-for-a-project-token) --- # Get token details | Convex Developer Hub [Skip to main content](https://docs.convex.dev/management-api/get-token-details#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Get token details ================= GET /token\_details --------------- Returns the team ID for team tokens. Especially useful after receiving a team token from an OAuth flow since most endpoints require team ID. Responses[​](https://docs.convex.dev/management-api/get-token-details#responses "Direct link to Responses") ------------------------------------------------------------------------------------------------------------ * 200 --- # List deployments for team | Convex Developer Hub [Skip to main content](https://docs.convex.dev/management-api/list-deployments-for-team#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! List deployments for team ========================= GET /teams/:team\_id/list\_deployments ---------------------------------- Lists deployments for a team with pagination, sorting, and filtering. Request[​](https://docs.convex.dev/management-api/list-deployments-for-team#request "Direct link to Request") -------------------------------------------------------------------------------------------------------------- Responses[​](https://docs.convex.dev/management-api/list-deployments-for-team#responses "Direct link to Responses") -------------------------------------------------------------------------------------------------------------------- * 200 --- # Nuxt | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/vue/nuxt#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! [Nuxt](https://nuxt.com/) is a powerful web framework powered by Vue. The community-maintained [`convex-nuxt` npm package](https://www.npmjs.com/package/convex-nuxt) provides deep integration of Convex with the Nuxt 3 ecosystem. It uses [convex-vue](https://docs.convex.dev/client/vue) under the hood. See the [Nuxt Quickstart](https://docs.convex.dev/quickstart/nuxt) to get started or the [convex-nuxt GitHub page](https://github.com/chris-visser/convex-nuxt) for more documentation. info The [`convex-nuxt` library](https://github.com/chris-visser/convex-nuxt/tree/main) is community-maintained. Thank you to the maintainer [Chris Visser](https://github.com/chris-visser) for his work on this project! You're welcome to ask questions about the library on the [Convex Discord](https://convex.dev/community) but opening a [convex-nuxt GitHub](https://github.com/chris-visser/convex-nuxt) issue is a better way to request a new feature or report a bug. --- # Uploading and Storing Files | Convex Developer Hub [Skip to main content](https://docs.convex.dev/file-storage/upload-files#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Upload files to Convex by [generated upload urls](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-upload-urls) , or via an [custom HTTP Action](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-an-http-action) . Uploading files via upload URLs[​](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-upload-urls "Direct link to Uploading files via upload URLs") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- Arbitrarily large files can be uploaded directly to your backend using a generated upload URL. This requires the client to make 3 requests: 1. Generate an upload URL using a mutation that calls [`storage.generateUploadUrl()`](https://docs.convex.dev/api/interfaces/server.StorageWriter#generateuploadurl) . 2. Send a POST request with the file contents to the upload URL and receive a storage ID. 3. Save the storage ID into your data model via another mutation. In the first mutation that generates the upload URL you can control who can upload files to your Convex storage. **Example**: [File Storage with Queries and Mutations](https://github.com/get-convex/convex-demos/tree/main/file-storage) ### Calling the upload APIs from a web page[​](https://docs.convex.dev/file-storage/upload-files#calling-the-upload-apis-from-a-web-page "Direct link to Calling the upload APIs from a web page") Here's an example of uploading an image via a form submission handler to an upload URL generated by a mutation: src/App.tsx TS import { FormEvent, useRef, useState } from "react";import { useMutation } from "convex/react";import { api } from "../convex/_generated/api";export default function App() { const generateUploadUrl = useMutation(api.messages.generateUploadUrl); const sendImage = useMutation(api.messages.sendImage); const imageInput = useRef(null); const [selectedImage, setSelectedImage] = useState(null); const [name] = useState(() => "User " + Math.floor(Math.random() * 10000)); async function handleSendImage(event: FormEvent) { event.preventDefault(); // Step 1: Get a short-lived upload URL const postUrl = await generateUploadUrl(); // Step 2: POST the file to the URL const result = await fetch(postUrl, { method: "POST", headers: { "Content-Type": selectedImage!.type }, body: selectedImage, }); const { storageId } = await result.json(); // Step 3: Save the newly allocated storage id to the database await sendImage({ storageId, author: name }); setSelectedImage(null); imageInput.current!.value = ""; } return (
setSelectedImage(event.target.files![0])} disabled={selectedImage !== null} /> );} ### Generating the upload URL[​](https://docs.convex.dev/file-storage/upload-files#generating-the-upload-url "Direct link to Generating the upload URL") An upload URL can be generated by the [`storage.generateUploadUrl`](https://docs.convex.dev/api/interfaces/server.StorageWriter#generateuploadurl) function of the [`MutationCtx`](https://docs.convex.dev/api/interfaces/server.GenericMutationCtx) object: convex/messages.ts TS import { mutation } from "./_generated/server";export const generateUploadUrl = mutation({ args: {}, handler: async (ctx) => { return await ctx.storage.generateUploadUrl(); },}); This mutation can control who is allowed to upload files. The upload URL expires in 1 hour and so should be fetched shortly before the upload is made. ### Writing the new storage ID to the database[​](https://docs.convex.dev/file-storage/upload-files#writing-the-new-storage-id-to-the-database "Direct link to Writing the new storage ID to the database") Since the storage ID is returned to the client it is likely you will want to persist it in the database via another mutation: convex/messages.ts TS import { mutation } from "./_generated/server";export const sendImage = mutation({ args: { storageId: v.id("_storage"), author: v.string() }, handler: async (ctx, args) => { await ctx.db.insert("messages", { body: args.storageId, author: args.author, format: "image", }); },}); ### Limits[​](https://docs.convex.dev/file-storage/upload-files#limits "Direct link to Limits") The file size is not limited, but upload POST request has a 2 minute timeout. Uploading files via an HTTP action[​](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-an-http-action "Direct link to Uploading files via an HTTP action") -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- The file upload process can be more tightly controlled by leveraging [HTTP action](https://docs.convex.dev/functions/http-actions) s, performing the whole upload flow using a single request, but requiring correct CORS headers configuration. The custom upload HTTP action can control who can upload files to your Convex storage. But note that the HTTP action request size is [currently limited](https://docs.convex.dev/functions/http-actions#limits) to 20MB. For larger files you need to use upload URLs as described [above](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-upload-urls) . **Example:** [File Storage with HTTP Actions](https://github.com/get-convex/convex-demos/tree/main/file-storage-with-http) ### Calling the upload HTTP action from a web page[​](https://docs.convex.dev/file-storage/upload-files#calling-the-upload-http-action-from-a-web-page "Direct link to Calling the upload HTTP action from a web page") Here's an example of uploading an image via a form submission handler to the `sendImage` HTTP action defined next. The highlighted lines make the actual request to the HTTP action: src/App.tsx TS import { FormEvent, useRef, useState } from "react";const convexSiteUrl = import.meta.env.VITE_CONVEX_SITE_URL;export default function App() { const imageInput = useRef(null); const [selectedImage, setSelectedImage] = useState(null); async function handleSendImage(event: FormEvent) { event.preventDefault(); // e.g. https://happy-animal-123.convex.site/sendImage?author=User+123 const sendImageUrl = new URL(`${convexSiteUrl}/sendImage`); sendImageUrl.searchParams.set("author", "Jack Smith"); await fetch(sendImageUrl, { method: "POST", headers: { "Content-Type": selectedImage!.type }, body: selectedImage, }); setSelectedImage(null); imageInput.current!.value = ""; } return (
setSelectedImage(event.target.files![0])} disabled={selectedImage !== null} /> );} ### Defining the upload HTTP action[​](https://docs.convex.dev/file-storage/upload-files#defining-the-upload-http-action "Direct link to Defining the upload HTTP action") A file sent in the HTTP request body can be stored using the [`storage.store`](https://docs.convex.dev/api/interfaces/server.StorageActionWriter#store) function of the [`ActionCtx`](https://docs.convex.dev/api/interfaces/server.GenericActionCtx) object. This function returns an `Id<"_storage">` of the stored file. From the HTTP action you can call a mutation to write the storage ID to a document in your database. To confirm success back to your hosted website, you will need to set the right [CORS headers](https://docs.convex.dev/functions/http-actions#cors) : convex/http.ts TS import { httpRouter } from "convex/server";import { httpAction } from "./_generated/server";import { api } from "./_generated/api";import { Id } from "./_generated/dataModel";const http = httpRouter();http.route({ path: "/sendImage", method: "POST", handler: httpAction(async (ctx, request) => { // Step 1: Store the file const blob = await request.blob(); const storageId = await ctx.storage.store(blob); // Step 2: Save the storage ID to the database via a mutation const author = new URL(request.url).searchParams.get("author"); if (author === null) { return new Response("Author is required", { status: 400, }); } await ctx.runMutation(api.messages.sendImage, { storageId, author }); // Step 3: Return a response with the correct CORS headers return new Response(null, { status: 200, // CORS headers headers: new Headers({ // e.g. https://mywebsite.com, configured on your Convex dashboard "Access-Control-Allow-Origin": process.env.CLIENT_ORIGIN!, Vary: "origin", }), }); }),}); You also need to handle the pre-flight `OPTIONS` request: convex/http.ts TS // Pre-flight request for /sendImagehttp.route({ path: "/sendImage", method: "OPTIONS", handler: httpAction(async (_, request) => { // Make sure the necessary headers are present // for this to be a valid pre-flight request const headers = request.headers; if ( headers.get("Origin") !== null && headers.get("Access-Control-Request-Method") !== null && headers.get("Access-Control-Request-Headers") !== null ) { return new Response(null, { headers: new Headers({ // e.g. https://mywebsite.com, configured on your Convex dashboard "Access-Control-Allow-Origin": process.env.CLIENT_ORIGIN!, "Access-Control-Allow-Methods": "POST", "Access-Control-Allow-Headers": "Content-Type, Digest", "Access-Control-Max-Age": "86400", }), }); } else { return new Response(); } }),}); * [Uploading files via upload URLs](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-upload-urls) * [Calling the upload APIs from a web page](https://docs.convex.dev/file-storage/upload-files#calling-the-upload-apis-from-a-web-page) * [Generating the upload URL](https://docs.convex.dev/file-storage/upload-files#generating-the-upload-url) * [Writing the new storage ID to the database](https://docs.convex.dev/file-storage/upload-files#writing-the-new-storage-id-to-the-database) * [Limits](https://docs.convex.dev/file-storage/upload-files#limits) * [Uploading files via an HTTP action](https://docs.convex.dev/file-storage/upload-files#uploading-files-via-an-http-action) * [Calling the upload HTTP action from a web page](https://docs.convex.dev/file-storage/upload-files#calling-the-upload-http-action-from-a-web-page) * [Defining the upload HTTP action](https://docs.convex.dev/file-storage/upload-files#defining-the-upload-http-action) --- # Swift and Convex type conversion | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/swift/data-types#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Custom data types[​](https://docs.convex.dev/client/swift/data-types#custom-data-types "Direct link to Custom data types") --------------------------------------------------------------------------------------------------------------------------- Convex lets you easily express your data in the backend as TypeScript objects, and can return those objects from queries, mutations and actions. To handle objects on the Swift side, create `struct` definitions that conform to the `Decodable` protocol. Usually that’s fairly trivial to do, as any `struct` with all `Decodable` members can automatically conform. 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 Swift using: struct BaseballTeam: Decodable { let name: String let uniformColors: [String] @ConvexInt var wins: Int @ConvexInt var losses: Int} Then you can pass that type as the yielding argument in your subscribe call: convex.subscribe(to: "mlb:first_place_team", with: ["division": "AL Central"], yielding: BaseballTeam.self) The data from the remote function will be deserialized to your custom struct. Often your use of the type can be inferred from the calling context, and you can skip the yielding argument. Numerical types[​](https://docs.convex.dev/client/swift/data-types#numerical-types "Direct link to Numerical types") --------------------------------------------------------------------------------------------------------------------- Numeric types like `Int` and `Double` are encoded in a special format to ensure proper interoperation with your TypeScript backend functions. To safely use them on the Swift side, ensure that you use one of the following property wrappers. | Type | Wrapper | | --- | --- | | `Float` or `Double` | `@ConvexFloat` | | `Float?` or `Double?` | `@OptionalConvexFloat` | | `Int` or `Int32` or `Int64` | `@ConvexInt` | | `Int?` or `Int32?` or `Int64?` | `@OptionalConvexInt` | Note that `struct` properties with wrappers must be declared as `var`. Field name conversion[​](https://docs.convex.dev/client/swift/data-types#field-name-conversion "Direct link to Field name conversion") --------------------------------------------------------------------------------------------------------------------------------------- If your code receives objects with names that you need to or want to translate to different names, you can use a `CodingKeys` `enum` to specify a mapping of remote names to names on your struct. For example, imagine a backend function or API that returns log entries like the following representing when someone came in and went out: {name: "Bob", in: "2024-10-03 08:00:00", out: "2024-10-03 11:00:00"} That data can’t decode directly into a `struct` because `in` is a keyword in Swift. We can use `CodingKeys` to give it an alternate name while still ingesting the data from the original name. struct Log: Decodable { let name: String let inTime: String let outTime: String enum CodingKeys: String, CodingKey { case name case inTime = "in" case outTime = "out" }} Putting it all together[​](https://docs.convex.dev/client/swift/data-types#putting-it-all-together "Direct link to Putting it all together") --------------------------------------------------------------------------------------------------------------------------------------------- In the custom data type example above, JavaScript's `BigInt` type is used in the backend data by adding a trailing `n` to the `wins` and `losses` values which lets the Swift code use `Int`. If instead the code used regular JavaScript `number` types, on the Swift side those would be received as floating point values and deserialization to `Int` would fail. If you have a situation like that where `number` is used but by convention it only contains integer values, you can handle that in your `struct` by using field name conversion and custom properties to hide the floating point representation. struct BaseballTeam: Decodable { let name: String let uniformColors: [String] @ConvexFloat private var internalWins: Double @ConvexFloat private var internalLosses: Double enum CodingKeys: String, CodingKey { case name case uniformColors case internalWins = "wins" case internalLosses = "losses" } // Expose the Double values as Ints var wins: Int { Int(internalWins) } var losses: Int { Int(internalLosses) }} The pattern is to store the `Double` values privately and with different names than the value from the backend. Then add custom properties to provide the `Int` values. * [Custom data types](https://docs.convex.dev/client/swift/data-types#custom-data-types) * [Numerical types](https://docs.convex.dev/client/swift/data-types#numerical-types) * [Field name conversion](https://docs.convex.dev/client/swift/data-types#field-name-conversion) * [Putting it all together](https://docs.convex.dev/client/swift/data-types#putting-it-all-together) --- # Testing Local Backend | Convex Developer Hub [Skip to main content](https://docs.convex.dev/testing/convex-backend#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Alternatively to [`convex-test`](https://docs.convex.dev/testing/convex-test) you can test your functions using the [open-source version of the Convex backend](https://github.com/get-convex/convex-backend) . Getting Started[​](https://docs.convex.dev/testing/convex-backend#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[​](https://docs.convex.dev/testing/convex-backend#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](https://docs.convex.dev/testing/convex-test) . CI[​](https://docs.convex.dev/testing/convex-backend#ci "Direct link to CI") ----------------------------------------------------------------------------- See [Continuous Integration](https://docs.convex.dev/testing/ci) to run your tests on a shared remote machine. * [Getting Started](https://docs.convex.dev/testing/convex-backend#getting-started) * [Limitations](https://docs.convex.dev/testing/convex-backend#limitations) * [CI](https://docs.convex.dev/testing/convex-backend#ci) --- # Continuous Integration | Convex Developer Hub [Skip to main content](https://docs.convex.dev/testing/ci#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page 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[​](https://docs.convex.dev/testing/ci#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 Testson: [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. * [Testing in GitHub Actions](https://docs.convex.dev/testing/ci#testing-in-github-actions) --- # Kotlin and Convex type conversion | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/android/data-types#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Custom data types[​](https://docs.convex.dev/client/android/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: @Serializabledata 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[​](https://docs.convex.dev/client/android/data-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.convexappimport 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. @Serializabledata 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[​](https://docs.convex.dev/client/android/data-types#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: @Serializabledata class ConvexDocument(@SerialName("_id") val id: String) * [Custom data types](https://docs.convex.dev/client/android/data-types#custom-data-types) * [Numerical types](https://docs.convex.dev/client/android/data-types#numerical-types) * [Field name conversion](https://docs.convex.dev/client/android/data-types#field-name-conversion) --- # Multiple Repositories | Convex Developer Hub [Skip to main content](https://docs.convex.dev/production/multiple-repos#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Your TypeScript clients can call Convex functions in a type-safe way outside of the repository where your Convex functions are defined. By following the steps below, you can generate a file similar to `convex/_generated/api.d.ts` that you can check in and use in a separate repository. TypeScript API generation is in beta TypeScript API generation is currently a [beta feature](https://docs.convex.dev/production/state/#beta-features) . If you have feedback or feature requests, [let us know on Discord](https://convex.dev/community) ! 1. Install the Convex Helpers npm package Install the `convex-helpers` package, which contains a CLI command to generate an api file. npm install convex-helpers 2. Run a command to generate a TypeScript API file Running this command will call into your configured Convex deployment and generate an `api.ts` file based on it. You can see additional flags by passing `--help` to the command. npx convex-helpers ts-api-spec Example[​](https://docs.convex.dev/production/multiple-repos#example "Direct link to Example") ----------------------------------------------------------------------------------------------- Below are code snippets of what this workflow looks like in action. These snippets include three different files: * `convex/messages.ts` - contains Convex function definitions * `api.ts` - a generated file from running the command above * `src/App.tsx` - frontend code in a separate repository where `api.ts` is checked in convex/messages.ts TS import { v } from "convex/values";import { mutation } from "./_generated/server";export const send = mutation({ args: { body: v.string(), author: v.string() }, returns: v.null(), handler: async (ctx, { body, author }) => { const message = { body, author }; await ctx.db.insert("messages", message); },}); api.ts TS import { FunctionReference, anyApi } from "convex/server";export const api: PublicApiType = anyApi as unknown as PublicApiType;export type PublicApiType = { messages: { send: FunctionReference< "mutation", "public", { author: string; body: string }, null >; };}; src/App.tsx TS import { FormEvent, useState } from "react";import { useMutation } from "convex/react";// Note: This file is importing from the file we generated,`api`,// and not from `../convex/_generated/api`import { api } from "../api";export default function App() { const [newMessageText, setNewMessageText] = useState(""); const sendMessage = useMutation(api.messages.send); const [name] = useState(() => "User " + Math.floor(Math.random() * 10000)); async function handleSendMessage(event: FormEvent) { event.preventDefault(); await sendMessage({ body: newMessageText, author: name }); setNewMessageText(""); } return (

Send Messages

setNewMessageText(event.target.value)} placeholder="Write a message…" />
);} Limits[​](https://docs.convex.dev/production/multiple-repos#limits "Direct link to Limits") -------------------------------------------------------------------------------------------- * Argument and return value validators are not required, but they will enrich the types of your TypeScript API. Where validators aren't defined, we default to `v.any()` as the validator. * You cannot call internal functions from outside of your Convex deployment. * [Example](https://docs.convex.dev/production/multiple-repos#example) * [Limits](https://docs.convex.dev/production/multiple-repos#limits) --- # Using GitHub Copilot with Convex | Convex Developer Hub [Skip to main content](https://docs.convex.dev/ai/using-github-copilot#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [GitHub Copilot](https://github.com/features/copilot) , the AI built into VS Code, makes it easy to write and maintain apps built with Convex. Let's walk through how to setup GitHub Copilot for the best possible results with Convex. Add Convex Instructions[​](https://docs.convex.dev/ai/using-github-copilot#add-convex-instructions "Direct link to Add Convex Instructions") --------------------------------------------------------------------------------------------------------------------------------------------- Add the following [instructions](https://code.visualstudio.com/docs/copilot/copilot-customization#_instruction-files) file to your `.github/instructions` directory in your project and it will automatically be included when working with TypeScript or JavaScript files: * [convex.instructions.md](https://convex.link/convex_github_copilot_instructions) ![Showing Where to Put GitHub Copilot Instructions](https://docs.convex.dev/assets/images/showing-where-to-put-convex-instructions-1d22c1b802b42443b4808e0dd27f0746.png) If you would rather that the instructions file is NOT automatically pulled into context then open the file in your editor and alter the `applyTo` field at the top. Read more about instructions files here: [https://code.visualstudio.com/docs/copilot/copilot-customization#\_use-instructionsmd-files](https://code.visualstudio.com/docs/copilot/copilot-customization#_use-instructionsmd-files) 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) . Setup the Convex MCP Server[​](https://docs.convex.dev/ai/using-github-copilot#setup-the-convex-mcp-server "Direct link to Setup the Convex MCP Server") --------------------------------------------------------------------------------------------------------------------------------------------------------- The Convex CLI comes with a [Convex Model Context Protocol](https://docs.convex.dev/ai/convex-mcp-server) (MCP) server built in. The Convex MCP server gives your AI coding agent access to the your Convex deployment to query and optimize your project. To get started with [MCP in VS Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers) then create a file in `.vscode/mcp.json` and add the following: { "servers": { "convex-mcp": { "type": "stdio", "command": "npx", "args": ["-y", "convex@latest", "mcp", "start"] } }} Once this is done it will take a few seconds to start up the MCP server and then you should see the Convex tool listed in the codelens: ![Convex Tool in Codelens](https://docs.convex.dev/assets/images/convex-tool-in-codelens-0cf36ed79938643797e93dd08ef3565c.png) and in the selection of tools that the model has access to in chat: ![Convex Tool in Chat](https://docs.convex.dev/assets/images/convex-tools-in-chat-eef97848e328479e7e1b06452b7934ea.png) Now start asking it questions like: * Evaluate and convex schema and suggest improvements * What are this app's public endpoints? * Run the `my_convex_function` query If you want to use the MCP server globally for all your projects then you can add it to your user settings, please see these docs for more information: [https://code.visualstudio.com/docs/copilot/chat/mcp-servers#\_add-an-mcp-server-to-your-user-settings](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server-to-your-user-settings) * [Add Convex Instructions](https://docs.convex.dev/ai/using-github-copilot#add-convex-instructions) * [Setup the Convex MCP Server](https://docs.convex.dev/ai/using-github-copilot#setup-the-convex-mcp-server) --- # List local deployments | Convex Developer Hub [Skip to main content](https://docs.convex.dev/management-api/list-local-deployments-for-team#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! List local deployments ====================== GET /teams/:team\_id/list\_local\_deployments ----------------------------------------- Lists the local deployments for a team. Request[​](https://docs.convex.dev/management-api/list-local-deployments-for-team#request "Direct link to Request") -------------------------------------------------------------------------------------------------------------------- Responses[​](https://docs.convex.dev/management-api/list-local-deployments-for-team#responses "Direct link to Responses") -------------------------------------------------------------------------------------------------------------------------- * 200 --- # TanStack Start | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/tanstack/tanstack-start/#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [TanStack Start](https://tanstack.com/start/latest) is a new React web framework with best-in-class typesafe routing. When used with Convex, TanStack Start provides * Live-updating queries with React Query (the React client for TanStack Query) * Subscription session resumption, from SSR to live on the client * Loader-based preloading and prefetching * Consistent logical query timestamp during SSR * Opt-in component-local SSR and more! This page describes the recommended way to use Convex with TanStack Start, via React Query. The standard Convex React hooks work also with TanStack Start without React Query, as do the [React Query hooks](https://docs.convex.dev/client/tanstack/tanstack-query/) without TanStack Start! But using all three is a sweet spot. TanStack Start is in Beta TanStack Start is a new React framework currently in beta. You can use it today but there may be breaking changes made to it before a stable release. Getting started[​](https://docs.convex.dev/client/tanstack/tanstack-start/#getting-started "Direct link to Getting started") ----------------------------------------------------------------------------------------------------------------------------- Follow the [TanStack Start Quickstart](https://docs.convex.dev/quickstart/tanstack-start) to add Convex to a new TanStack Start project. Using Convex with React Query[​](https://docs.convex.dev/client/tanstack/tanstack-start/#using-convex-with-react-query "Direct link to Using Convex with React Query") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- You can read more about [React Query hooks](https://docs.convex.dev/client/tanstack/tanstack-query/) , but a few highlights relevant to TanStack Start. ### Staying subscribed to queries[​](https://docs.convex.dev/client/tanstack/tanstack-start/#staying-subscribed-to-queries "Direct link to Staying subscribed to queries") Convex queries in React Query continue to receive updates after the last component subscribed to the query unmounts. The default for this behavior is 5 minutes and this value is configured with [`gcTime`](https://tanstack.com/query/latest/docs/framework/react/guides/caching) . This is useful to know when debugging why a query result is already loaded: for client side navigations, whether a subscription is already active can depend on what pages were previously visited in a session. ### Using Convex React hooks[​](https://docs.convex.dev/client/tanstack/tanstack-start/#using-convex-react-hooks "Direct link to Using Convex React hooks") [Convex React](https://docs.convex.dev/client/react) hooks like [`usePaginatedQuery`](https://docs.convex.dev/api/modules/react#usepaginatedquery) can be used alongside TanStack hooks. These hooks reference the same Convex Client so there's still just one set of consistent query results in your app when these are combined. Server-side Rendering[​](https://docs.convex.dev/client/tanstack/tanstack-start/#server-side-rendering "Direct link to Server-side Rendering") ----------------------------------------------------------------------------------------------------------------------------------------------- Using TanStack Start and Query with Convex makes it particularly easy to live-update Convex queries on the client while also [server-rendering](https://tanstack.com/query/v5/docs/framework/react/guides/ssr) them. [`useSuspenseQuery()`](https://tanstack.com/query/latest/docs/framework/react/reference/useSuspenseQuery) is the simplest way to do this: const { data } = useSuspenseQuery(convexQuery(api.messages.list, {})); ### Consistent client views[​](https://docs.convex.dev/client/tanstack/tanstack-start/#consistent-client-views "Direct link to Consistent client views") In the browser all Convex query subscriptions present a consistent, at-the-same-logical-timestamp view of the database: if one query result reflects a given mutation transaction, every other query result will too. Server-side rendering is usually a special case: instead of a stateful WebSocket session, on the server it's simpler to fetch query results ad-hoc. This can lead to inconsistencies analogous to one REST endpoint returning results before a mutation ran and another endpoint returning results after that change. In TanStack Start, this issue is avoided by sending in a timestamp along with each query: Convex uses the same timestamp for all queries. ### Loaders[​](https://docs.convex.dev/client/tanstack/tanstack-start/#loaders "Direct link to Loaders") To make client-side navigations faster you can add a [loader](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#using-loaders-to-ensure-data-is-loaded) to a route. By default, loaders will run when mousing over a link to that page. export const Route = createFileRoute('/posts')({ loader: async (opts) => { await opts.context.queryClient.ensureQueryData( convexQuery(api.messages.list, {}), ); }; component: () => { const { data } = useSuspenseQuery(convexQuery(api.messages.list, {})); return (
{data.map((message) => ( ))}
); },}) Authentication[​](https://docs.convex.dev/client/tanstack/tanstack-start/#authentication "Direct link to Authentication") -------------------------------------------------------------------------------------------------------------------------- Client-side authentication in Start works the way [client-side authentication with Convex](https://docs.convex.dev/auth) generally works in React because TanStack Start works well as a client-side framework. To use Clerk auth to make authenticated Convex calls on the server as well see the [TanStack Start + Clerk guide](https://docs.convex.dev/client/tanstack/tanstack-start/clerk) . Clerk is an official partner of TanStack, see our setup guide. * [Getting started](https://docs.convex.dev/client/tanstack/tanstack-start/#getting-started) * [Using Convex with React Query](https://docs.convex.dev/client/tanstack/tanstack-start/#using-convex-with-react-query) * [Staying subscribed to queries](https://docs.convex.dev/client/tanstack/tanstack-start/#staying-subscribed-to-queries) * [Using Convex React hooks](https://docs.convex.dev/client/tanstack/tanstack-start/#using-convex-react-hooks) * [Server-side Rendering](https://docs.convex.dev/client/tanstack/tanstack-start/#server-side-rendering) * [Consistent client views](https://docs.convex.dev/client/tanstack/tanstack-start/#consistent-client-views) * [Loaders](https://docs.convex.dev/client/tanstack/tanstack-start/#loaders) * [Authentication](https://docs.convex.dev/client/tanstack/tanstack-start/#authentication) --- # Configuring Deployment URL | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/react/deployment-urls#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page When [connecting to your backend](https://docs.convex.dev/client/react#connecting-to-a-backend) it's important to correctly configure the deployment URL. ### Create a Convex project[​](https://docs.convex.dev/client/react/deployment-urls#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](https://docs.convex.dev/dashboard/deployments/deployment-settings) on your Convex [dashboard](https://dashboard.convex.dev/) . ### Configure the client[​](https://docs.convex.dev/client/react/deployment-urls#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[​](https://docs.convex.dev/client/react/deployment-urls#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 frontendNEXT_PUBLIC_SENTRY_DSN=https://123abc@o123.ingest.sentry.io/1234NEXT_PUBLIC_LAUNCHDARKLY_SDK_CLIENT_SIDE_ID=01234567890abcdef Your backend functions can use [environment variables](https://docs.convex.dev/production/environment-variables) configured on your dashboard. They do not source values from `.env` files. * [Create a Convex project](https://docs.convex.dev/client/react/deployment-urls#create-a-convex-project) * [Configure the client](https://docs.convex.dev/client/react/deployment-urls#configure-the-client) * [Choosing environment variable names](https://docs.convex.dev/client/react/deployment-urls#choosing-environment-variable-names) --- # Data Export | Convex Developer Hub [Skip to main content](https://docs.convex.dev/database/import-export/export#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! You can export your data from Convex by [taking a backup](https://docs.convex.dev/database/backup-restore) and downloading it as a zip file. Alternatively, you can export the same data with the [command line](https://docs.convex.dev/cli#export-data-to-a-file) : npx convex export --path ~/Downloads --- # Agent Mode | Convex Developer Hub [Skip to main content](https://docs.convex.dev/cli/agent-mode#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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](https://docs.convex.dev/cli/local-deployments) 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](https://docs.convex.dev/production/state/#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. --- # Optimistic Updates | Convex Developer Hub [Skip to main content](https://docs.convex.dev/client/react/optimistic-updates#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page Even though Convex queries are completely reactive, sometimes you'll want to update your UI before the mutation changes propagate back to the client. To accomplish this, you can configure an _optimistic update_ to execute as part of your mutation. Optimistic updates are temporary, local changes to your query results which are used to make your app more responsive. These updates are made by functions registered on a mutation invocation with the [`.withOptimisticUpdate`](https://docs.convex.dev/api/interfaces/react.ReactMutation#withoptimisticupdate) configuration option. Optimistic updates are run when a mutation is initiated, rerun if the local query results change, and rolled back when a mutation completes. Simple example[​](https://docs.convex.dev/client/react/optimistic-updates#simple-example "Direct link to Simple example") -------------------------------------------------------------------------------------------------------------------------- Here is how an optimistic update could be added to an `increment` mutation in a simple counter app: src/IncrementCounter.tsx TS import { api } from "../convex/_generated/api";import { useMutation } from "convex/react";export function IncrementCounter() { const increment = useMutation(api.counter.increment).withOptimisticUpdate( (localStore, args) => { const { increment } = args; const currentValue = localStore.getQuery(api.counter.get); if (currentValue !== undefined) { localStore.setQuery(api.counter.get, {}, currentValue + increment); } }, ); const incrementCounter = () => { increment({ increment: 1 }); }; return ;} Optimistic updates receive a [`localStore`](https://docs.convex.dev/api/interfaces/browser.OptimisticLocalStore) , a view of the Convex client's internal state, followed by the arguments to the mutation. This optimistic update updates the `api.counter.get` query to be `increment` higher if it's loaded. Complex example[​](https://docs.convex.dev/client/react/optimistic-updates#complex-example "Direct link to Complex example") ----------------------------------------------------------------------------------------------------------------------------- If we want to add an optimistic update to a multi-channel chat app, that might look like: src/MessageSender.tsx TS import { api } from "../convex/_generated/api";import { useMutation } from "convex/react";import { Id } from "../convex/_generated/dataModel";export function MessageSender(props: { channel: Id<"channels"> }) { const sendMessage = useMutation(api.messages.send).withOptimisticUpdate( (localStore, args) => { const { channel, body } = args; const existingMessages = localStore.getQuery(api.messages.list, { channel, }); // If we've loaded the api.messages.list query, push an optimistic message // onto the list. if (existingMessages !== undefined) { const now = Date.now(); const newMessage = { _id: crypto.randomUUID() as Id<"messages">, _creationTime: now, channel, body, }; localStore.setQuery(api.messages.list, { channel }, [ ...existingMessages, newMessage, ]); } }, ); async function handleSendMessage( channelId: Id<"channels">, newMessageText: string, ) { await sendMessage({ channel: channelId, body: newMessageText }); } return ( );} This optimistic update changes the `api.messages.list` query for the current channel to include a new message. The newly created message object should match the structure of the real messages generated by the `api.messages.list` query on the server. Because this message includes the client's current time (not the server's), it will inevitably not match the `api.messages.list` query after the mutation runs. That's okay! The Convex client will handle rolling back this update after the mutation completes and the queries are updated. If there are small mistakes in optimistic updates, the UI will always eventually render the correct values. Similarly, the update creates a temporary `Id` with `new Id("messages", crypto.randomUUID())`. This will also be rolled back and replaced with the true ID once the server assigns it. Lastly, note that this update creates a new array of messages instead of using `existingMessages.push(newMessage)`. This is important! Mutating objects inside of optimistic updates will corrupt the client's internal state and lead to surprising results. Always create new objects inside of optimistic updates. Learning more[​](https://docs.convex.dev/client/react/optimistic-updates#learning-more "Direct link to Learning more") ----------------------------------------------------------------------------------------------------------------------- To learn more, check out our API documentation: * [`.withOptimisticUpdate`](https://docs.convex.dev/api/interfaces/react.ReactMutation#withoptimisticupdate) * [`OptimisticUpdate`](https://docs.convex.dev/api/modules/browser#optimisticupdate) * [`OptimisticLocalStore`](https://docs.convex.dev/api/interfaces/browser.OptimisticLocalStore) If you'd like some hands on experience, try adding optimistic updates to the [tutorial app](https://github.com/get-convex/convex-tutorial) ! If you do, you should notice the app feels snappier — just a little, Convex is pretty fast already! — but otherwise works the same. To explore even further, try inserting a mistake into this update! You should see a flicker as the optimistic update is applied and then rolled back. * [Simple example](https://docs.convex.dev/client/react/optimistic-updates#simple-example) * [Complex example](https://docs.convex.dev/client/react/optimistic-updates#complex-example) * [Learning more](https://docs.convex.dev/client/react/optimistic-updates#learning-more) --- # api.js | Convex Developer Hub [Skip to main content](https://docs.convex.dev/generated-api/api#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page This code is generated These exports are not directly available in the `convex` package! Instead you need to run `npx convex dev` to create `convex/_generated/api.js` and `convex/_generated/api.d.ts`. These types require running code generation because they are specific to the Convex functions you define for your app. If you aren't using code generation, you can use [`makeFunctionReference`](https://docs.convex.dev/api/modules/server#makefunctionreference) instead. ### api[​](https://docs.convex.dev/generated-api/api#api "Direct link to api") An object of type `API` describing your app's public Convex API. Its `API` type includes information about the arguments and return types of your app's Convex functions. The api object is used by client-side React hooks and Convex functions that run or schedule other functions. src/App.jsx import { api } from "../convex/_generated/api";import { useQuery } from "convex/react";const data = useQuery(api.messages.list); ### internal[​](https://docs.convex.dev/generated-api/api#internal "Direct link to internal") Another object of type `API` describing your app's internal Convex API. convex/upgrade.js import { action } from "../_generated/server";import { internal } from "../_generated/api";export default action({ handler: async ({ runMutation }, { planId, ... }) => { // Call out to payment provider (e.g. Stripe) to charge customer const response = await fetch(...); if (response.ok) { // Mark the plan as "professional" in the Convex DB await runMutation(internal.plans.markPlanAsProfessional, { planId }); } },}); * [api](https://docs.convex.dev/generated-api/api#api) * [internal](https://docs.convex.dev/generated-api/api#internal) --- # Using Windsurf with Convex | Convex Developer Hub [Skip to main content](https://docs.convex.dev/ai/using-windsurf#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! On this page [Windsurf](https://codeium.com/windsurf) , the AI code editor, makes it easy to write and maintain apps built with Convex. Let's walk through how to setup Windsurf for the best possible results with Convex. Add Convex Rules[​](https://docs.convex.dev/ai/using-windsurf#add-convex-rules "Direct link to Add Convex Rules") ------------------------------------------------------------------------------------------------------------------ Add the following rules file to your project and refer to it directly when prompting for changes: * [Convex Rules](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) . Setup the Convex MCP Server[​](https://docs.convex.dev/ai/using-windsurf#setup-the-convex-mcp-server "Direct link to Setup the Convex MCP Server") --------------------------------------------------------------------------------------------------------------------------------------------------- The Convex CLI comes with a [Convex Model Context Protocol](https://docs.convex.dev/ai/convex-mcp-server) (MCP) server built in. The Convex MCP server gives your AI coding agent access to the your Convex deployment to query and optimize your project. To get started with Windsurf, open "Windsurf Settings > Cascade > Model Context Protocol (MCP) Servers", click on "Add Server", click "Add custom server", and add the following configuration for Convex. { "mcpServers": { "convex": { "command": "npx", "args": ["-y", "convex@latest", "mcp", "start"] } }} After adding the server return to the "Windsurf Settings > Cascade > Model Context Protocol (MCP) Servers" screen an click "Refresh" button for Windsurf to pick up the new server. Once this is done you should see the Convex tool listed in the servers: ![Chat UI](https://docs.convex.dev/assets/images/windsurf_convex_mcp-ed91858fc5df64ae0b900f95b69ae2ad.png) Now start asking it questions like: * Evaluate and convex schema and suggest improvements * What are this app's public endpoints? * Run the `my_convex_function` query * [Add Convex Rules](https://docs.convex.dev/ai/using-windsurf#add-convex-rules) * [Setup the Convex MCP Server](https://docs.convex.dev/ai/using-windsurf#setup-the-convex-mcp-server) --- # Using Components | Convex Developer Hub [Skip to main content](https://docs.convex.dev/components/using#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! Convex Components add new features to your backend in their own sandbox with their own functions, schema and data, scheduled functions and all other fundamental Convex features. You can see the full list of components in the [directory](https://convex.dev/components) . Installation[​](https://docs.convex.dev/components/using#installation "Direct link to Installation") ----------------------------------------------------------------------------------------------------- We'll use the [Agent](https://www.npmjs.com/package/@convex-dev/agent) component as an example. 1. Install from \`npm\` npm i @convex-dev/agent 2. Add the component to your app Create or update the `convex.config.ts` file in your app's `convex/` folder and install the component by calling `use`. Multiple instances of the same component can be installed by calling `use` multiple times with different names. Each will have their own tables and functions. convex/convex.config.ts TS import { defineApp } from "convex/server";import agent from "@convex-dev/agent/convex.config.js";const app = defineApp();app.use(agent);app.use(agent, { name: "agent2" });//... Add other components hereexport default app; 3. Run convex dev The `convex dev` CLI command will generate code necessary for using the component. npx convex dev 4. Access the component through its API Each instance of a component has its API listed under the `components` object by its name. Some components wrap this API with classes or functions. Check out each component's documentation for more details on its usage. import { components } from "./_generated/api.js";const agent = new Agent(components.agent, { ... }); Using the component's API directly[​](https://docs.convex.dev/components/using#using-the-components-api-directly "Direct link to Using the component's API directly") ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- Though components may expose higher level TypeScript APIs, under the hood they are called via normal Convex functions over the component sandbox boundary. Queries, mutations, and action rules still apply - queries can only call component queries, mutations can also call component mutations, and actions can also call component actions. As a result, queries into components are reactive by default, and mutations have the same transaction guarantees. Component functions can be called from your application using the following syntax: import { internalAction } from "./_generated/server";import { components } from "./_generated/api";export const myAction = internalAction({ args: { threadId: v.string() }, handler: async (ctx, args) => { // Call the component's API to get the thread status. const { status } = await ctx.runQuery(components.agent.threads.getThread, { threadId: args.threadId, }); //... },}); Some components abstract away the component's API. For instance, the `Agent` class from `@convex-dev/agent` is initialized with `components.agent`, and its methods take in `ctx` so they can call the component's API internally. [Learn more about the Agent Component here](https://docs.convex.dev/agents) . Transactions[​](https://docs.convex.dev/components/using#transactions "Direct link to Transactions") ----------------------------------------------------------------------------------------------------- Remember that mutation functions in Convex are [transactions](https://docs.convex.dev/functions/mutation-functions#transactions) . Either all the changes in the mutation get written at once or none are written at all. All writes for a top-level mutation call, including writes performed by calls into other components' mutations, are committed at the same time. If the top-level mutation throws an error, all of the writes are rolled back, and the mutation doesn't change the database at all. However, if a component mutation call throws an exception, only its writes are rolled back. Then, if the caller catches the exception, it can continue, perform more writes, and return successfully. If the caller doesn't catch the exception, then it's treated as failed and all the writes associated with the caller mutation are rolled back. This means your code can choose a different code path depending on the semantics of your component. As an example, take the [Rate Limiter](https://www.npmjs.com/package/@convex-dev/ratelimiter) component. One API of the Rate Limiter throws an error if a rate limit is hit: // Automatically throw an error if the rate limit is hit.await rateLimiter.limit(ctx, "failedLogins", { key: userId, throws: true }); If the call to `rateLimiter.limit` throws an exception, we're over the rate limit. Then, if the calling mutation doesn't catch this exception, the whole transaction is rolled back. The calling mutation, on the other hand, could also decide to ignore the rate limit by catching the exception and proceeding. For example, an app may want to ignore rate limits if there is a development environment override. In this case, only the component mutation will be rolled back, and the rest of the mutation will continue. Dashboard[​](https://docs.convex.dev/components/using#dashboard "Direct link to Dashboard") -------------------------------------------------------------------------------------------- You can see your component’s data, functions, files, logs, and other info using the dropdown in the Dashboard. You can also use the dropdown to exclude info from certain components. ![Screenshot of the component dropdown](https://docs.convex.dev/screenshots/component_dropdown.png) Testing components[​](https://docs.convex.dev/components/using#testing-components "Direct link to Testing components") ----------------------------------------------------------------------------------------------------------------------- When writing tests with [`convex-test`](https://docs.convex.dev/testing/convex-test) , that use components, you must register the component with the test instance. This tells it what schema to validate and where to find the component source code. Most components export convenient helper functions on `/test` to make this easy: convex/some.test.ts TS import agentTest from "@convex-dev/agent/test";import { expect, test } from "vitest";import { convexTest } from "convex-test";import { components } from "./_generated/api";import { createThread } from "@convex-dev/agent";// Define this once, often in a shared test helper file.export function initConvexTest() { const t = convexTest(); agentTest.register(t); return t;}test("Agent createThread", async () => { const t = initConvexTest(); const threadId = await t.run(async (ctx) => { // Calling functions that use ctx and components.agent return await createThread(ctx, components.agent, { title: "Hello, world!", }); }); // Calling functions directly on the component's API const thread = await t.query(components.agent.threads.getThread, { threadId, }); expect(thread).toMatchObject({ title: "Hello, world!", });}); If you need to register the component yourself, you can do so by passing the component's schema and modules to the test instance. convex/manual.test.ts TS /// import { test } from "vitest";import { convexTest } from "convex-test";import schema from "./path/to/component/schema.ts";const modules = import.meta.glob("./path/to/component/**/*.ts");test("Test something with a local component", async () => { const t = convexTest(); t.registerComponent("componentName", schema, modules); await t.run(async (ctx) => { await ctx.runQuery(components.componentName.someQuery, { arg: "value", }); });}); Log Streams[​](https://docs.convex.dev/components/using#log-streams "Direct link to Log Streams") -------------------------------------------------------------------------------------------------- You can use the `data.function.component_path` field in [log streams](https://docs.convex.dev/production/integrations/log-streams) to separate log lines based on the component they came from. --- # Embedding the dashboard | Convex Developer Hub [Skip to main content](https://docs.convex.dev/platform-apis/embedded-dashboard#__docusaurus_skipToContent_fallback) Copy as Markdown Copied! 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 `