Skip to content

Validation, coercion, and transforms

Control runtime validation depth, input/output boundaries, and generated domain types.

These options apply when --runtime is not none.

Think of these controls as a boundary policy: depth decides how strict a schema is, side decides where it runs, and transforms decide the values application code receives.

LevelChecks
looseobject, array, primitive, enum, and union structure
formatsloose checks plus known formats such as email, UUID, URL, IP, and date/time
strictformats plus string, number, array, and object constraints
Terminal window
pnpm exec typed-openapi openapi.yaml --runtime zod --validation formats

strict is the default for a runtime adapter. Use loose for a structurally reliable but less opinionated boundary.

Every preset resolves to the following policy. A policy object can override any field without losing the rest of its preset:

Fieldlooseformatsstrict
formatsoffonon
stringConstraintsoffoffon
numberConstraintsoffoffon
arrayConstraintsoffoffon
objectConstraintsoffoffon
import { defineConfig } from "typed-openapi";
export default defineConfig({
runtime: "zod",
validation: {
preset: "strict",
stringConstraints: false,
numberConstraints: false,
},
});

Use this for a deliberate compatibility exception. Prefer a named preset when it already matches the API boundary you want.

Generated clients validate both outgoing parameters and successful response data by default.

Terminal window
# Validate untrusted server responses, but skip outgoing checks.
pnpm exec typed-openapi openapi.yaml --runtime valibot --validate-side output
ValueBehavior
nonegenerate schemas but do not call them
inputvalidate path, query, cookie, header, and body parameters
outputvalidate successful response data
bothvalidate input and output (default)

You can override the generated default for one request:

await api.post("/pet", {
body: draft,
validate: "input",
});

For trusted internal calls, validate: "none" can be a useful escape hatch. Keep that exception local and documented; a global validateSide: "none" removes the runtime boundary for every endpoint.

The emitted runtime schema is passed to an optional onValidate hook. This is useful when an application wraps validation with telemetry, localization, or a custom error type.

import { z } from "zod";
api.setOnValidate(async ({ side, schema, value }) => {
console.info(`validating ${side}`);
return (schema as z.ZodType).parse(value);
});

Path, query, cookie, and header values arrive as strings in HTTP. --coerce makes runtime schemas accept string versions of number and boolean values. It is enabled by default when a runtime is selected.

Terminal window
pnpm exec typed-openapi openapi.yaml --runtime zod --coerce
# Keep strict input typing and disable coercion when the transport already normalizes values:
pnpm exec typed-openapi openapi.yaml --runtime zod --no-coerce

Map OpenAPI formats into application-friendly values across TypeScript types and runtime schemas:

Terminal window
pnpm exec typed-openapi openapi.yaml \
--runtime zod \
--transform-dates \
--transform-bigint
  • format: date and date-time become Date.
  • format: int64 becomes bigint.
  • With the types-only runtime, ISO date strings are revived for the generated client path.

OpenAPI default values are emitted in runtime schemas. For inline request objects, readOnly properties are removed from input schemas; for inline response objects, writeOnly properties are removed from output schemas. Named $ref components remain shared so a component’s public shape does not silently differ between endpoints.

For browser apps, start with strict, both, and coercion enabled. If forms or the transport already validate outgoing values, move to output validation rather than disabling runtime checks entirely.