OpenAPI feature support
This page says which parts of an OpenAPI 3.x document the shared parser and the four generators understand, and what each one turns them into. Read it when a specification uses a construct and you want to know, before generating, whether it will come out as a type, a validator, a client method or nothing at all.
Specification versions and document shapes
The parser reads OpenAPI 3.0.x and 3.1 documents in YAML or JSON. A document must carry an openapi field; a Swagger 2.0 document (a swagger field) is accepted by the parser only so that its #/definitions/<name> schemas can be referenced from an OpenAPI 3 document, and nothing is generated from a Swagger 2.0 document itself.
Everything under paths and components is read. Of the remaining top-level sections, webhooks produces a warning and no output, and servers, security, callbacks, links and externalDocs are ignored: the clients take their base URL at construction time and a basePath option, and send no authentication of their own. x- extensions are ignored everywhere.
The two columns of the matrix below that matter for a given feature follow from how the generators relate: the Zod and Playwright generators share one schema generator, and the TypeScript and k6 generators share one type generator. A schema feature therefore behaves the same for Zod and Playwright, and the same for TypeScript and k6. Overview describes how the packages build on each other.
Feature matrix
"Yes" means the feature affects the generated code; "JSDoc" means it is carried as a comment only; "Type" means a type is generated but no run-time check exists for it (TypeScript has no counterpart for a constraint); "No" means the keyword is read and ignored.
| Feature | TypeScript | Zod | Playwright | k6 |
|---|---|---|---|---|
| Primitive types, objects, arrays | Yes | Yes | Yes | Yes |
OpenAPI 3.1 type arrays (type: [string, "null"]) | Yes | Yes | Yes | Yes |
nullable: true (3.0) and defaultNullable option | Yes | Yes | Yes | Yes |
enum (string, number, boolean, mixed) | Yes, enumFormat | Yes | Yes | Yes, enumFormat |
const | Yes | Yes | Yes | Yes |
default | JSDoc @default | Yes, .default(value) | Yes | JSDoc |
description | JSDoc | JSDoc, .describe() with useDescribe | JSDoc | JSDoc |
title, example, examples | No | JSDoc | JSDoc | No |
deprecated (schema, property, operation) | JSDoc, filter | JSDoc, filter | JSDoc, filter | JSDoc, filter |
format on strings | JSDoc @format | Yes, see Formats | Yes | JSDoc |
minLength, maxLength, pattern | Type | Yes | Yes | Type |
minimum, maximum, exclusiveMinimum/exclusiveMaximum (3.0 boolean and 3.1 number), multipleOf, integer | Type | Yes | Yes | Type |
required | Yes | Yes | Yes | Yes |
readOnly / writeOnly | Yes, schemaType | Yes, schemaType | Yes, per direction | No |
additionalProperties (false, true, schema) | Yes | Yes | Yes | Yes |
patternProperties, propertyNames | Type | Yes | Yes | Type |
minProperties, maxProperties | Type | Yes | Yes | Type |
minItems, maxItems, uniqueItems | Type | Yes | Yes | Type |
prefixItems (tuples), with items as rest | Yes | Yes | Yes | Yes |
contains, minContains, maxContains | Type | Yes | Yes | Type |
unevaluatedProperties, unevaluatedItems | Type | Yes | Yes | Type |
if / then / else | Type | Yes, for a subset of conditions | Yes | Type |
dependentRequired, dependencies | Type | Yes | Yes | Type |
not | Type | Yes | Yes | Type |
contentEncoding, contentMediaType | Type | Yes | Yes | Type |
allOf | Yes | Yes | Yes | Yes |
oneOf, anyOf | Yes | Yes | Yes | Yes |
discriminator with mapping | Yes | Yes | Yes | Yes |
| Circular references | Yes | Yes, z.lazy() | Yes | Yes |
| Operation filters (tags, paths, methods, operationIds, deprecated) | Yes | Yes | Yes | Yes |
Status code filters (includeStatusCodes, excludeStatusCodes) | No | No | Yes | No |
| Per-operation query and header parameter types | Yes | Yes | Yes | Yes |
Path-level parameters and $ref parameters | Yes | Yes | Yes | Yes |
ignoreHeaders | Yes | Yes | Yes | Yes |
| Cookie parameters | No | No | Yes, as a Cookie header | Yes |
Query style/explode (form, spaceDelimited, pipeDelimited, deepObject) and allowReserved | No | No | Yes | Yes |
Path style/explode (simple, label, matrix), percent-encoding | No | No | Yes | Yes |
| Inline request body and response types per operation | Yes | No, component and parameter schemas only | Yes | Yes |
preferredContentTypes | No | No | Yes | Yes |
| Form, multipart, text and binary request bodies | Type | No | Yes | Yes |
| Response parsing by content type | No | No | Yes, fallbackContentTypeParsing | Yes |
| Several content types on one response or body | Yes, one type each | No | Yes, one method each | First preferred |
$ref to other local files, refMappings for remote URLs | Yes | Yes | Yes | Yes |
webhooks, callbacks, links, security, servers | No | No | No | No |
Schemas
Every schema in components.schemas becomes one declaration, and inline schemas in query and header parameters (and, for the TypeScript, Playwright and k6 generators, in request bodies and responses) become declarations named after the operation (see Naming). The following fixture is the smallest realistic example:
User:
type: object
description: A user in the system
properties:
id:
type: string
format: uuid
name:
type: string
email:
type: string
format: email
age:
type: integer
required: [id, name]
The Zod generator turns it into a strict object by default, with every constraint it can express:
/** A user in the system */
export const userSchema = z.strictObject({
id: z.uuid(),
name: z.string(),
email: z.email().optional(),
age: z.number().int().optional(),
});
export type User = z.infer<typeof userSchema>;
The TypeScript generator produces a type, carrying what it cannot express as JSDoc:
/**
* A user in the system
*/
export type User = {
/** @format uuid */
id: string;
name: string;
/** @format email */
email?: string;
age?: number;
};
Numbers map to z.number() with .int() for integer, .gte()/.lte() for inclusive bounds, .gt()/.lt() for exclusive bounds in both the 3.0 boolean form and the 3.1 numeric form, and .multipleOf(). Strings get .min(), .max() and .regex(); a pattern that is not a valid JavaScript regular expression is dropped with a warning rather than emitted broken. Arrays get .min() and .max(), and uniqueItems, contains, minProperties/maxProperties, patternProperties, propertyNames, unevaluatedProperties, dependentRequired and not become .refine() or .superRefine() checks. prefixItems becomes z.tuple([...]), with .rest() when items is also set, and a TypeScript tuple type.
An if/then/else is honoured when its if can be expressed as checks on a property's type, const, minimum, maximum or required; anything else is skipped with a warning naming the limitation. An absent property satisfies an if vacuously, as JSON Schema requires.
Object strictness is a generator option, not a specification keyword: mode (strict, normal or loose) selects z.strictObject, z.object or z.looseObject, and additionalProperties: false closes an object in every mode. Objects that constrain keys without declaring properties (minProperties, propertyNames) accept the keys they constrain. The default is strict; see Zod options.
Enumerations are strings, numbers, booleans or a mix. The Zod generator emits z.enum() for all-string enums, z.boolean() for an enum of booleans, and a z.union() of z.literal() values for numeric and mixed enums. The TypeScript generator's enumFormat chooses between a union type, a const object with a derived type (the default), or a TypeScript enum; the Zod and Playwright generators forward enumFormat to the types they emit in split mode and accept only union and const-object. Option parity lists where each value is accepted.
default makes a Zod property .default(value) instead of .optional(), so parsing an object without the key returns the default; the TypeScript type is unchanged and records the value as @default. readOnly and writeOnly are applied through schemaType for the TypeScript and Zod generators and automatically per direction by the Playwright generator; see Request and response shapes. nullable: true and a 3.1 type array containing "null" both produce .nullable() and | null; defaultNullable: true treats every property that says nothing as nullable.
Composition
allOf of object schemas becomes .extend() chains in Zod and an intersection type in TypeScript; constraints declared alongside a $ref inside an allOf are applied to the result. allOf over non-objects uses .and(). oneOf and anyOf become z.union() and a union type. When a discriminator is present, oneOf and anyOf become z.discriminatedUnion() on the discriminator property, and a mapping is followed so that mapped schemas are members even when they are not listed inline. A discriminator property must be required in every variant; the check follows allOf and $ref, so the usual allOf: [{ $ref: Base }, { properties: { petType: { const: "dog" } } }] variant qualifies.
Schemas that refer to each other, directly or through a chain, are declared with z.lazy() where the cycle is broken, and the declarations are sorted so that every schema appears after the ones it uses. Only schemas that are actually on a cycle are treated as circular.
Parameters and serialisation
Parameters declared on the path item and on the operation are merged, with the operation's declaration winning, and $refs into components.parameters are resolved. Every generator produces a type (and, for the Zod-based generators, a schema) for an operation's query parameters and header parameters; ignoreHeaders removes headers from both, matching names case-insensitively with glob patterns.
The two clients serialise parameters as the specification declares. Query parameters follow style and explode: the OpenAPI default, form with explode: true, sends tags: ["a", "b"] as ?tags=a&tags=b; explode: false joins with commas, spaceDelimited and pipeDelimited with their delimiters, and deepObject sends an object as filter[name]=x. allowReserved: true keeps RFC 3986 reserved characters literal. Values that are null or undefined are omitted. Path parameters follow simple (the default), label and matrix, and are percent-encoded, so a value containing / or a space cannot address a different endpoint. Cookie parameters reach the Playwright client as a Cookie header and the k6 client as a typed cookies object.
A declared parameter whose name is not a valid identifier is sanitised, and path segments containing characters such as : are folded into valid type and method names.
Request bodies and content types
The Playwright and k6 generators resolve a request body with several content types through preferredContentTypes, which defaults to ["application/json"]; a wildcard such as */* in the specification covers a preferred type, and when nothing matches the first declared type is used. The Playwright service generates one method per request content type, so a body offered as JSON and as multipart yields two methods. The TypeScript generator emits one type per content type, adding a content-type suffix to the name only when an operation declares more than one. The Playwright service types the body by content type, as data for JSON, form for application/x-www-form-urlencoded and multipart for multipart/form-data, and the client passes these options to Playwright's request context unchanged. The k6 client serialises objects to JSON unless the declared content type is form or multipart, in which case the object is handed to k6 to encode, and leaves strings, ArrayBuffers and k6 file data unchanged.
Responses and status codes
In the TypeScript, Playwright and k6 generators every documented response with a body gets a declaration named after the operation: GetUsersResponse for an operation's only success response, a reason-phrase suffix when there are several (GetUsersOkResponse, GetUsersCreatedResponse), and always a suffix for an error response (GetUsersNotFoundResponse). The Playwright service generates one method per success status code and content type, asserts the documented status, reads the body with the method the content type calls for and validates it against the schema. By default only success responses become methods; includeStatusCodes (exact codes or ranges such as 4xx) opts error responses in and excludeStatusCodes removes some. The k6 service uses the first 2xx response that declares a body, treats a response without content as void, and reads the body as JSON, text or raw bytes according to its content type. A 2XX response key is recognised as success.
Content types are classed as JSON (application/json, text/json, *+json), text (text/*, XML, JavaScript) or binary (images, audio, video, fonts, application/octet-stream, PDF, archives); an unknown or wildcard type falls back to text in the Playwright service, or to what fallbackContentTypeParsing says, and is reported as a warning during generation.
References
A $ref may point inside the document (#/components/schemas/User), into another local file (./common.yaml#/components/schemas/Money, relative to the document that contains it) or at an http(s) URL. Local files are read when the specification is loaded and the target is inlined into the specification's own components, so the generated output is self-contained; references inside the inlined target resolve against its own document, references are followed transitively, and a cycle between two documents terminates. A target whose name clashes with one already declared is inlined under a qualified name (CommonMoney) with a warning. Every component type is supported (schemas, responses, parameters, requestBodies, headers, examples, links, callbacks, securitySchemes, pathItems), and #/definitions/<name> in a Swagger 2.0 file is read as a schema.
Remote URLs are never fetched. A refMappings entry points a URL prefix at a local directory; a remote reference with no mapping fails the run with a message that shows the mapping to add. A $ref that resolves to nothing is reported, and a $ref in place of a whole path item (OpenAPI 3.1 components.pathItems) is followed. Specs and references explains the mapping rules.
Formats
String formats become Zod validators in the Zod and Playwright generators and a @format JSDoc tag in the TypeScript and k6 generators. A format not in this table generates a plain z.string().
format | Zod |
|---|---|
date-time | z.iso.datetime(), or a .regex() from customDateTimeFormatRegex |
date, time | z.iso.date(), z.iso.time() |
duration | .refine() checking an ISO 8601 duration |
uuid, guid | z.uuid() or z.guid(), or a versioned z.uuid({ version }) via uuidFormat |
email | z.email() |
uri, url | z.url() |
uri-reference | .refine() rejecting whitespace |
hostname | .refine() checking RFC 1123 labels |
ipv4, ipv6 | z.ipv4(), z.ipv6() |
cidr, cidrv4, cidrv6 | z.cidrv4(), z.cidrv4(), z.cidrv6() |
byte, base64 | z.base64() |
base64url | z.base64url() |
binary | z.string() |
emoji, nanoid, cuid, cuid2, ulid | z.emoji(), z.nanoid(), z.cuid(), z.cuid2(), z.ulid() |
json-pointer, relative-json-pointer | .refine() checking RFC 6901 syntax |
Numeric formats (int32, int64, float, double) do not change the generated code beyond .int() for type: integer. contentEncoding without a format maps base64 and base64url to their validators and records other encodings in a .describe(); contentMediaType is recorded the same way. When both format and contentEncoding are present the format wins and a warning says so. The options behind this table are in Zod options.
Defaults and deprecation
Schema defaults are applied by the Zod-based generators as described above and recorded as JSDoc by the TypeScript-based ones. deprecated: true on a schema or property adds @deprecated to its JSDoc in every generator, and deprecated: true on an operation adds it to the generated method; excludeDeprecated: true in operationFilters drops such operations entirely, as described in Operation filters.