Skip to Content
ValidationStandard Schema

Standard Schema Validation

Standard Schema  is a convention supported by modern validation libraries that implement a common interface for defining and validating data structures. It is supported by libraries such as Zod 4 , ArkType , Valibot , and more. See the full list of implementations in the repository .

Vovk.ts provides a simple createStandardValidation function to build a custom validation library using Standard Schema. Its key option is toJSONSchema, which defines how to convert models to JSON Schema.

ArkType Example

Create withArk somewhere in your codebase:

src/lib/withArk.ts
import { createStandardValidation } from 'vovk'; import { type } from 'arktype'; export const withArk = createStandardValidation({ toJSONSchema: (model: type) => model.toJsonSchema({ fallback: { proto: (ctx) => ctx.proto === File ? { type: "string", format: "binary", } : ctx.base, default: (ctx) => ctx.base } }) });

Then use it in your controller:

src/modules/user/UserController.ts
import { prefix, post, operation } from 'vovk'; import { type } from 'arktype'; import { withArk } from '@/lib/withArk'; @prefix('users') export default class UserController { @operation({ summary: 'Update user (Arktype)', description: 'Update user by ID with Arktype validation', }) @post('{id}') static updateUser = withArk({ body: type({ name: type('string').describe('User full name'), age: type('0 < number < 120').describe('User age'), email: type('string.email').describe('User email'), }), params: type({ id: type('string.uuid').describe('User ID'), }), query: type({ notify: type('"email" | "push" | "none"').describe('Notification type'), }), output: type({ success: type('boolean').describe('Success status'), }), async handle(req, { id }) { const { name, age } = await req.json(); const notify = req.nextUrl.searchParams.get('notify'); // do something with the data console.log(`Updating user ${id}:`, { name, age, notify }); return { success: true, }; }, }); }

Valibot Example

Create withValibot somewhere in your codebase:

src/lib/withValibot.ts
import { createStandardValidation } from 'vovk'; import { toJsonSchema } from '@valibot/to-json-schema'; export const withValibot = createStandardValidation({ toJSONSchema: (model) => toJsonSchema(model, { overrideSchema(context) { if (context.valibotSchema.type === 'file') { return { type: 'string', format: 'binary' }; } }, }), });

Then use it in your controller:

src/modules/user/UserController.ts
import { prefix, post, operation } from 'vovk'; import * as v from 'valibot'; import { withValibot } from '@/lib/withValibot'; @prefix('users') export default class UserController { @operation({ summary: 'Update user (Valibot)', description: 'Update user by ID with Valibot validation', }) @post('{id}') static updateUser = withValibot({ body: v.pipe( v.object({ name: v.pipe(v.string(), v.description('User full name')), age: v.pipe(v.number(), v.minValue(0), v.maxValue(120), v.description('User age')), email: v.pipe(v.string(), v.email(), v.description('User email')), }), v.description('User object') ), params: v.object({ id: v.pipe(v.string(), v.uuid(), v.description('User ID')), }), query: v.object({ notify: v.pipe(v.picklist(['email', 'push', 'none']), v.description('Notification type')), }), output: v.pipe( v.object({ success: v.pipe(v.boolean(), v.description('Success status')), }), v.description('Response object') ), async handle(req, { id }) { const { name, age } = await req.json(); const notify = req.nextUrl.searchParams.get('notify'); // do something with the data console.log(`Updating user ${id}:`, { name, age, notify }); return { success: true, }; }, }); }

Other libraries follow the same pattern; only the toJSONSchema implementation differs. Once standard-schema/issues/21  is resolved, using createStandardValidation will likely be unnecessary in most cases.

Last updated on