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:
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 Type | TS/JS Type | Example Usage | Validator for Argument Validation and Schemas | json Format for Export | Notes |
---|---|---|---|---|---|
Id | string | doc._id | v.id(tableName) | string | See Document IDs. |
Null | null | null | v.null() | null | JavaScript's undefined is not a valid Convex value. Use null instead. |
Int64 | bigint | 3n | v.int64() | string (base10) | Int64s only support BigInts between -2^63 and 2^63-1. Convex supports bigint s in most modern browsers. |
Float64 | number | 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 | true | v.boolean() | bool | |
String | string | "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 | 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 | [1, 3.2, "abc"] | v.array(values) | array | Arrays can have at most 8192 values. |
Object | Object | {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. 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 }) => {
//...
},
});