Runtime validation, written like TypeScript

A literal is
already a schema.

No object builder. No method language. Write a value like an interface, infer its type, then validate unknown data.

Read the docs →
runtimetype
const User = {
  name: string.min(1),
  age: number.min(0),
  'email?': string.email(),
}

type User = Infer<typeof User>

parse(User, input)

The model

Three things, kept separate.

valueconst User = { name: string }
typetype User = Infer<typeof User>
checkparse(User, input)

Measured, not claimed

Small enough to disappear.
Fast enough to stop thinking about.

5.00 kB
browser bundle, min + gzip
90.47M
flat valid objects / second
341
TypeScript instantiations
0
runtime dependencies
See contracts and methodology →

Schemas are data

Compose with JavaScript.

Spread is extend. Property access is pick. Rest destructuring is omit. Optional keys remain optional because the language already knows how objects compose.

TypeScript
const User = { name: string, age: number, 'email?': string }
const Timestamps = { createdAt: date, 'deletedAt?': date }

const Post = { title: string, ...Timestamps }
const Public = { name: User.name, 'email?': User['email?'] }
const { age: _, ...NoAge } = User

Less library language

The same job, with the scaffolding removed.

Zod
const User = z.object({
  name: z.string().min(1),
  email: z.string().email().optional(),
})

type User = z.infer<typeof User>
User.parse(input)
litetype
const User = {
  name: string.min(1),
  'email?': string.email(),
}

type User = Infer<typeof User>
parse(User, input)
Move from Zod →

One standard edge

tRPC, Hono and forms
without adapter packages.

Keep schemas bare inside your code. Wrap once with Standard Schema where another tool needs it.

TypeScript
const User = { name: string.min(1) }

t.procedure
  .input(standard(User))
  .query(({ input }) => input)
Integration recipes →

Keep the type.
Drop the ceremony.