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:
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:
- The context object for the type of function (query, mutation, or action).
- 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 }) => {
//...
},
});