@dreampulse/openapi-to-typescript
v1.2.0
Published
CLI tool that generates TypeScript type definitions from OpenAPI 3.x and Swagger 2.0 specs
Maintainers
Readme
openapi-to-typescript
Generates TypeScript type definitions and JSON Schemas from OpenAPI 3.x specs — one file per schema, one file per endpoint. The output is optimized for LLM consumption: small, self-contained files with all metadata preserved as JSDoc comments.
Why?
When working with LLMs on codebases that consume APIs, you want the AI to understand your API types without needing to parse a massive OpenAPI spec. This tool generates compact, individual .ts files that an LLM can read and reason about independently. Each file contains everything it needs: the type definition, JSDoc comments with descriptions, constraints, formats, and deprecation markers.
Design decisions:
typeinstead ofinterface— Union types, intersections, and mapped types compose better withtypealiases. This matters for discriminated unions andallOf/oneOfschemas.- One file per schema/endpoint — LLMs work best with small, focused context. A single file per type means you only feed the AI what it needs.
- All metadata as JSDoc — Descriptions, constraints (
@minimum,@maxLength,@pattern), formats (@format date-time), and deprecation are preserved as JSDoc comments so they're visible in IDE tooltips and to LLMs reading the code. - Unsupported features throw errors — Instead of silently generating wrong types, unsupported constructs (like
if/then/else) throw descriptive errors so you know what needs attention. - JSON Schema alongside TypeScript — Each schema also generates a
.schema.jsonfile (JSON Schema Draft 2020-12) with all transitive dependencies embedded in$defs, useful for runtime validation.
Installation
pnpm add openapi-to-typescriptCLI Usage
npx openapi-to-ts --input <spec> --output <dir>| Option | Description |
|---|---|
| -i, --input <path> | Path to the OpenAPI spec (YAML or JSON) |
| -o, --output <dir> | Output directory for generated files |
Example:
npx openapi-to-ts -i ./api/openapi.yaml -o ./generatedThis reads the spec, resolves all internal $ref references, and writes the generated files:
generated/
schemas/
Kundenangaben.ts # export type Kundenangaben = { ... }
Kundenangaben.schema.json # JSON Schema with embedded $defs
Anschrift.ts
Anschrift.schema.json
...
endpoints/
getKundenangaben.ts # path params + header params + response types
importKundenangaben.ts # header params + request body + response types
...The output directory is cleaned before each run.
Output
Schema files
Each schema from components/schemas becomes a .ts file with a single export type:
// schemas/Anschrift.ts
export type Anschrift = {
hausnummer?: string;
ort?: string;
/**
* Postleitzahlen, die nicht aus fünf Ziffern bestehen werden ignoriert.
* @pattern \d{5}
* @example "10557"
*/
plz?: string;
strasse?: string;
};Properties from the OpenAPI spec are preserved:
description→ JSDoc commentformat→@formattagdeprecated→@deprecatedtag- Constraints (
minimum,maxLength,pattern,minItems, ...) → corresponding@tag readOnly→readonlymodifierrequiredfields are non-optional, everything else gets?
Discriminated unions are handled automatically. A base schema with a discriminator mapping becomes a union type, and each subtype gets the discriminator property as a string literal:
// schemas/Finanzierungsbaustein.ts (base — union of subtypes)
export type Finanzierungsbaustein =
Annuitaetendarlehen | Bauspardarlehen | Forwarddarlehen | ...;
// schemas/Annuitaetendarlehen.ts (subtype — literal discriminator)
export type Annuitaetendarlehen = {
"@type": "ANNUITAETENDARLEHEN";
} & {
darlehensbetrag?: number;
annuitaetendetails?: Annuitaetendetails;
...
};Endpoint files
Each operation (identified by operationId) becomes a .ts file with types for parameters, request body, and responses:
// endpoints/getKundenangaben.ts
import type { Kundenangaben } from "../schemas/Kundenangaben";
import type { Problem } from "../schemas/Problem";
/**
* Kundenangaben für einen bestehenden Vorgang laden
* @http GET /kundenangaben/{vorgangsnummer}
*/
export type GetKundenangabenPathParams = {
vorgangsnummer: string;
};
export type GetKundenangabenHeaderParams = {
Authorization: string;
"X-TraceId"?: string;
};
export type GetKundenangabenResponse200 = Kundenangaben;
export type GetKundenangabenResponse401 = void;
export type GetKundenangabenResponse404 = Problem;Naming conventions:
- Schema files: PascalCase (matching the schema name from the spec)
- Endpoint files: camelCase (matching the
operationId) - Type names:
{PascalOperationId}PathParams,{PascalOperationId}QueryParams,{PascalOperationId}HeaderParams,{PascalOperationId}RequestBody,{PascalOperationId}Response{StatusCode} - Responses without a body become
void
JSON Schema files
Each schema also generates a .schema.json file following JSON Schema Draft 2020-12. All referenced types are embedded as $defs, making each file self-contained:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"plz": {
"type": "string",
"pattern": "\\d{5}",
"description": "Postleitzahlen, die nicht aus fünf Ziffern bestehen werden ignoriert.",
"examples": ["10557"]
}
}
}This is useful for runtime validation with libraries like Ajv.
Library Usage
You can also use the tool programmatically:
import {
parseSpec,
generateFiles,
extractEndpoints,
schemaToType,
} from "openapi-to-typescript";Generate all files
import { parseSpec, generateFiles } from "openapi-to-typescript";
const spec = await parseSpec("./api/openapi.yaml");
await generateFiles(spec, "./generated");This is equivalent to the CLI — it parses the spec, generates all .ts and .schema.json files, and writes them to the output directory.
Extract endpoint metadata
import { parseSpec, extractEndpoints } from "openapi-to-typescript";
const spec = await parseSpec("./api/openapi.yaml");
const endpoints = extractEndpoints(spec);
for (const endpoint of endpoints) {
console.log(`${endpoint.method.toUpperCase()} ${endpoint.path}`);
console.log(` operationId: ${endpoint.operationId}`);
console.log(` parameters: ${endpoint.parameters.length}`);
console.log(` responses: ${endpoint.responses.map((r) => r.statusCode)}`);
}Each EndpointInfo contains:
operationId,method,path,summary,descriptionparameters— array of{ name, in, required, schema, description }requestBody— the request body schema (ornull)responses— array of{ statusCode, schema, description }
Convert a single schema to TypeScript
import { schemaToType } from "openapi-to-typescript";
schemaToType({ type: "string" });
// → "string"
schemaToType({ type: "object", properties: { name: { type: "string" } }, required: ["name"] });
// → '{\n name: string;\n}'
schemaToType({ enum: ["A", "B", "C"] });
// → '"A" | "B" | "C"'
schemaToType({ $ref: "#/components/schemas/Foo" });
// → "Foo"schemaToType handles: primitives, enums, const, objects (nested, with required/optional), arrays, tuples, allOf/oneOf/anyOf, nullable, readOnly, $ref, additionalProperties, and all JSDoc metadata. Unsupported constructs like if/then/else throw an error.
Supported OpenAPI Features
| Feature | TypeScript Output |
|---|---|
| type: "string" / "number" / "integer" / "boolean" | string / number / number / boolean |
| type: "null" (OAS 3.1) | null |
| nullable: true (OAS 3.0) | Type \| null |
| enum | "A" \| "B" \| "C" |
| const | "value" (literal type) |
| type: "object" with properties | { key: type; ... } |
| type: "array" with items | Type[] |
| prefixItems (tuples) | [Type1, Type2] |
| allOf | A & B (intersection) |
| oneOf / anyOf | A \| B (union) |
| discriminator with mapping | Discriminated union with string literal property |
| $ref | Named type import |
| additionalProperties | Record<string, Type> or intersection with object |
| readOnly | readonly modifier |
| description, deprecated, format, constraints | JSDoc comments |
