Skip to main content

Writing Convex Functions

Convex functions are written in JavaScript or TypeScript, and are run on Convex servers. Functions encapsulate the business logic and provide the public API of a Convex application. They can perform complex aggregations, use NPM libraries, and so on.

There are three types of Convex functions - queries, mutations and actions. Queries and mutations are the primary way to interact with Convex, while actions are used in more limited scenarios when non-deterministic code is needed, such as calling third-party services.

Defining Convex Functions

Functions exported from files inside the convex/ folder wrapped in query, mutation or action comprise the public API of a Convex application. Actions needs to be defined inside convex/actions folder.

You can invoke a file's default export by passing its file path relative to the convex/ folder to useQuery. Be sure to leave off the extension. Here's an example:

convex/folder_name/file_name.ts
export default query(...);
App.tsx
// Call "folder_name/file_name.ts"'s default export.
useQuery("folder_name/file_name");

You can call non-default exports too. Add a colon and the exported name to the end of the path.

convex/folder_name/file_name.ts
export const exportedName = query(...);
App.tsx
// Call `exportedName` within "folder_name/file_name.js"
useQuery("folder_name/file_name:exportedName");

Function Signatures

Since Convex functions run on the server, subscribing to a query or running a mutation requires sending arguments and return values over the network.

Convex automatically handles serialization of arguments and return values. Convex supports a superset of JSON: See Types and Schemas for the supported types. In particular, functions, instances of user-defined classes, and undefined aren't supported.

When the server runs your Convex functions, it provides an additional first argument: a QueryCtx for queries, a MutationCtx for mutations or a ActionCtx for actions. These context objects provide capabilities to query the database or check client authentication with the auth property. Queries and mutations can interact with the database directly via the db property, while actions can invoke queries and mutations via the query and mutation properties respectively.

Convex query functions can't evaluate to undefined on the client, since undefined isn't a valid Convex value and is used on the client to signal that a query has not yet returned a value. If a Convex function returns undefined, the server will translate this value to null.

// in convex/getUserByEmail.ts a function can return `undefined`...
export default query(async ({ db }, email: string) => {
const user = db
.query("users")
.filter(q => q.eq(email, q.field("email")))
.first();
if (user) {
return user.name;
}
});
// ...but in a React component this value will be translated to null.
const messages: Message[] | undefined | null = useQuery("getUserByEmail");

Environment

Actions run in Node.js and can use any compatible libraries. Query and mutations, on the other hand, execute in a more limited environment. They have have access to fewer built-in APIs. Nearly all JavaScript global objects are available. But web APIs like fetch and window are absent, as are Node.js APIs like process and fs.

The implementation of some objects differs from stock V8, the JavaScript engine Convex uses to execute these functions, in order to run these functions deterministically.

Using Libraries

When you push Convex functions with npx convex dev or npx convex deploy, the CLI tool bundles your functions to include any libraries they may use. Many libraries just work, but if a library depends on Node.js, it probably won't in query and mutation functions. Libraries that depends on browser-specific capabilities won't work in any type of Convex functions.

If there's a library you'd like to use that isn't working for you, let us know!

Convex functions, once bundled with dependencies, need to be less than 32MB. If you're running into this size limit, you can use npx convex deploy -v to print out each source file's bundled size.

Sharing Code with Your Frontend

Types and functions from your UI code can be imported and used by Convex functions.

import { FeedEntry, formatFeedEntry } from "../src/feedTypes.ts"

export default query(async ({ db }) => {
const entries: FeedEntry[] = [];
...
const formatted = formatEntry(entry)
...
});

Importing modules from your UI could lead you to inadvertently include code that can't run in the Convex environment. If you encounter type errors like Cannot find name 'window' or Cannot redeclare block-scoped variable 'console'. you may need to refactor your UI code to pull code you'd like to share out into separate files that can be imported by both UI and Convex function code.

Avoid importing Convex functions from UI code

Backend code in Convex functions might contain code you'd like to keep private. Importing Convex functions in UI code may cause your bundler to include this code in the frontend bundle, potentially exposing secret logic to users.