Skip to main content

Argument Validation

Argument validators ensure that queries, mutations, and actions are called with the correct types of arguments.

This is important for security! Without argument validation, a malicious user can call your public functions with unexpected arguments and cause surprising results. 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 and optionally validation.

Example: Argument Validation

Adding validators

To add argument validation to your functions, pass an object with args and handler properties to the query, mutation or action constructor:

convex/message.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";

export const send = mutation({
args: {
body: v.string(),
author: v.string(),
},
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 type annotations! The type of your function will be inferred automatically.

The validation will throw if the client supplies arguments not declared in args. This is helpful to prevent bugs caused by mistyped names of arguments.

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

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 object imported from "convex/values".

The database can store the exact same set of data types.

Additionally you can also express type unions, literals, any types, and optional fields.

Convex values

Convex supports the following types of values:

Convex TypeTS/JS Type
Example Usage
Validator for Argument Validation and Schemasjson Format for ExportNotes
Idstringdoc._idv.id(tableName)stringSee Document IDs.
Nullnullnullv.null()nullJavaScript's undefined is not a valid Convex value. Use null instead.
Int64bigint3nv.int64()string (base10)Int64s only support BigInts between -2^63 and 2^63-1. Convex supports bigints in most modern browsers.
Float64number3.1v.number()number / stringConvex supports all IEEE-754 double-precision floating point numbers (such as NaNs). Inf and NaN are JSON serialized as strings.
Booleanbooleantruev.boolean()bool
Stringstring"abc"v.string()stringStrings 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.
BytesArrayBuffernew ArrayBuffer(8)v.bytes()string (base64)Convex supports first class bytestrings, passed in as ArrayBuffers. Bytestrings must be smaller than the 1MB total size limit for Convex types.
ArrayArray[1, 3.2, "abc"]v.array(values)arrayArrays can have at most 8192 values.
ObjectObject{a: "abc"}v.object({property: value})objectConvex only supports "plain old JavaScript objects" (objects that do not have a custom prototype). Convex includes all enumerable properties. Objects can have at most 1024 entries. Field names must be nonempty and not start with "$" or "_".

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: {
stringOrNumber: v.union(v.string(), v.number()),
},
handler: async ({ db }, { stringOrNumber }) => {
//...
},
});

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 ({ db }, { oneTwoOrThree }) => {
//...
},
});

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 ({ db }, { anyValue }) => {
//...
},
});

This corresponds to the any type in TypeScript.

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 ({ db }, { optionalString, optionalNumber }) => {
//...
},
});

This corresponds to marking fields as optional with ? in TypeScript.

Extracting TypeScript types

The 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<typeof nestedObject>;

export default mutation({
args: {
nested: nestedObject,
},
handler: async ({ db }, { nested }) => {
//...
},
});