Validation: enforce types at runtime and compile time
Use Elysia.t (TypeBox) or Standard Schema libraries like Zod and Valibot to validate body, params, query, headers, cookies, and responses with one schema.
Use this file to discover all available pages before exploring further.
Elysia validates incoming data and outgoing responses against declarative schemas. A single schema acts as the single source of truth for runtime validation, TypeScript types, data coercion, and OpenAPI documentation — with no duplication.
import { Elysia, t } from 'elysia'new Elysia() .get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) .listen(3000)
t is Elysia’s built-in schema builder, based on TypeBox. It generates JSON Schema-compatible definitions that work at runtime, at compile time, and in OpenAPI output.
import { t } from 'elysia'const MyType = t.Object({ hello: t.Literal('Elysia')})type MyType = typeof MyType.static// { hello: 'Elysia' }
Elysia supports Standard Schema, so you can use any compatible library alongside or instead of t.Supported libraries include Zod, Valibot, ArkType, Effect Schema, Yup, Joi, and more.
import { Elysia } from 'elysia'import { z } from 'zod'import * as v from 'valibot'new Elysia() .get('/id/:id', ({ params: { id }, query: { name } }) => id, { params: z.object({ id: z.coerce.number() }), query: v.object({ name: v.literal('Lilith') }) }) .listen(3000)
You can mix validators from different libraries in the same handler without any issues.
Use t.File for single file uploads and t.Files for multiple. Elysia automatically sets the content-type to multipart/form-data.
import { Elysia, t } from 'elysia'new Elysia() .post('/upload', ({ body }) => body, { body: t.Object({ file: t.File({ format: 'image/*' }), multipleFiles: t.Files() }) }) .listen(3000)
When using Standard Schema for file uploads, use the exported fileType utility to validate file contents by magic number — most validators only check the MIME type string, which can be spoofed.
import { Elysia, fileType } from 'elysia'import { z } from 'zod'new Elysia() .post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((f) => fileType(f, 'image/jpeg')) }) })
Register a named model with .model() and reference it by name in any route. This avoids repeating the same schema in multiple places and keeps OpenAPI output consistent.
import { Elysia, t } from 'elysia'const app = new Elysia() .model({ sign: t.Object({ username: t.String(), password: t.String() }) }) .post('/sign-in', ({ body }) => body, { body: 'sign', response: 'sign' })
Models compose naturally with plugins, letting you co-locate related schemas and import them as a unit.
// auth.model.tsimport { Elysia, t } from 'elysia'export const authModel = new Elysia() .model({ sign: t.Object({ username: t.String(), password: t.String() }) })
// index.tsimport { Elysia } from 'elysia'import { authModel } from './auth.model'const app = new Elysia() .use(authModel) .post('/sign-in', ({ body }) => body, { body: 'sign', response: 'sign' })