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

Defining validators

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

convex/sendMessage.js
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export default mutation({
args: {
body: v.string(),
author: v.string(),
},

handler: async ({ db }, { body, author }) => {
const message = { body, author };
await db.insert("messages", message);
},
});

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.

Args

The args property is an object mapping argument names to validators.

Validators are written with the validator builder, v. This is the same v that is used to define schemas. You can read about the supported types below.

Handler

The handler is the implementation function. It always takes two arguments:

  1. The context object for the type of function (query, mutation, or action).
  2. The arguments object. This is guaranteed to match the type defined by args.

Supported types

The validator builder supports all of the Convex types. It also supports unions, literals, any types, and optional fields.

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