Skip to content

toomuchdesign/openapi-ts-json-schema

Repository files navigation

openapi-ts-json-schema

Build Status Npm version Coveralls

Generate TypeScript JSON Schema modules (.ts files with as const) directly from your OpenAPI spec — one source of truth for runtime validation and static type inference.

Why?

Your OpenAPI spec, runtime schemas, and TypeScript types tend to drift when maintained separately. This library generates them all from the spec — one source of truth.

The output is plain JSON Schema as const TypeScript modules, so they work with any JSON Schema-aware tool (Ajv, TypeBox, Hyperjump, Fastify, …) and can be passed directly to json-schema-to-ts for static type inference.

These schemas:

  • ✅ are 100% JSON Schema–compatible (usable with Ajv, Fastify, etc.)
  • ✅ are TypeScript-native (as const objects you can import)
  • ✅ can be used for type inference via json-schema-to-ts
  • ✅ are generated automatically from your OpenAPI spec

In short: OpenAPI spec becomes the single source of truth for both runtime validation and TypeScript typing.

Important

Why a code generator and not just a JSON import?

The whole reason this library exists is that TypeScript cannot import a .json file as const (microsoft/TypeScript#32063). Imported JSON is widened to its base types (string, number, …), which destroys the literal information json-schema-to-ts needs to infer types from a schema.

The workaround is to emit .ts modules with an as const assertion — exactly what openapi-ts-json-schema does. If TypeScript ever supports import schema from './schema.json' as const, most use cases for this library go away and you can consume your OpenAPI-derived JSON Schemas directly.

Example

From this OpenAPI definition:

components:
  schemas:
    User:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
      required: [id, name]

openapi-ts-json-schema generates:

// components/schemas/User.ts
export default {
  type: 'object',
  properties: {
    id: { type: 'string' },
    name: { type: 'string' },
  },
  required: ['id', 'name'],
} as const;

Now you can use it for both runtime validation and type inference:

import Ajv from 'ajv';
import type { FromSchema } from 'json-schema-to-ts';

import userSchema from './components/schemas/User';

const ajv = new Ajv();
const validate = ajv.compile<FromSchema<typeof userSchema>>(userSchema);

const data: unknown = {};
if (validate(data)) {
  // data is now typed as { id: string; name: string }
}

How this fits with openapi-typescript

Different tools for different jobs — not competitors:

  • Types only, no runtimeopenapi-typescript
  • Runtime schemas + types from one specopenapi-ts-json-schema + json-schema-to-ts (works with any JSON Schema-compatible tool)
  • Both togetheropenapi-ts-json-schema handles runtime; fall back to openapi-typescript types where json-schema-to-ts inference breaks down

Installation

npm i openapi-ts-json-schema -D

Usage

openapi-ts-json-schema can be invoked through its CLI or used as a library. Both surfaces share the same options.

CLI

The package ships with an openapi-ts-json-schema binary supporting two mutually exclusive modes: passing options as flags, or pointing to a config file.

Flags mode

For simple setups, pass options directly as flags. Flags are a serialized projection of the Options object — see that section for full semantics.

npx openapi-ts-json-schema \
  --input path/to/open-api-specs.yaml \
  --collections paths
Flag Maps to
--input <file> openApiDocument
--collections <paths> targets.collections (comma-separated)
--single <paths> targets.single (comma-separated)
--output <dir> outputPath
--import-extension <ext> importExtension
--ref-handling <strategy> refHandling
--silent silent

Config file mode

For setups that need schemaPatcher, idMapper, or plugins — none of which can be expressed as CLI flags — pass a JS or TS config file via --config. The file must export default an Options object. When --config is used, no other flags are allowed.

npx openapi-ts-json-schema --config ./openapi-ts-json-schema.config.ts
// openapi-ts-json-schema.config.ts
import type { Options } from 'openapi-ts-json-schema';
import { fastifyIntegrationPlugin } from 'openapi-ts-json-schema';

const config: Options = {
  openApiDocument: 'path/to/open-api-specs.yaml',
  targets: {
    collections: ['paths'],
  },
  plugins: [fastifyIntegrationPlugin()],
};

export default config;

Programmatic API

import { openapiToTsJsonSchema } from 'openapi-ts-json-schema';

const { outputPath } = await openapiToTsJsonSchema({
  openApiDocument: 'path/to/open-api-specs.yaml',
  targets: {
    collections: ['paths'],
  },
});

Options

Core options

Property Type                                              Description Default
openApiDocument (required) string Path to an OpenAPI document (JSON or YAML). -
targets (required) {
collections?: string[];
single?: string[];
}
OpenAPI definition paths to generate JSON Schemas from (dot notation).

collections: paths pointing to objects/records of definitions, where each entry will be generated (eg: ["components.schemas"]).

single: paths pointing to individual definitions to generate (eg: ["paths./users/{id}"]).
-
outputPath string Directory where generated schemas will be written. Defaults to /schemas-autogenerated in the same directory of openApiDocument. -
refHandling "import" | "inline" | "keep" "import": generate and import $ref schemas.
"inline": inline $ref schemas.
"keep": keep $ref values.
"import"
importExtension "js" | "ts" | "none" File extension appended to relative import specifiers in generated artifacts. "js"
silent boolean Don't log user messages. false

Advanced options

Property Type Description Default
idMapper (params: { id: string }) => string Map internal schema ids to custom $id and $ref values in the generated output. See idMapper option below. -
schemaPatcher (params: { schema: JSONSchema }) => void Hook called for every generated schema node, allowing programmatic mutation before output. -
plugins ReturnType<Plugin>[] List of plugins to extend or customize the generation process. See plugins docs. -

Full configuration example

import {
  generateSchemaWith,
  openapiToTsJsonSchema,
} from 'openapi-ts-json-schema';

await openapiToTsJsonSchema({
  openApiDocument: './openapi.yaml',
  targets: {
    collections: ['components.schemas'],
    single: ['paths./users/{id}'],
  },
  outputPath: './generated',
  refHandling: 'import',
  importExtension: 'js',
  silent: false,

  idMapper: ({ id }) => id.toUpperCase(),
  schemaPatcher: ({ schema }) => {
    if (schema.properties && !schema.type) {
      schema.type = 'object';
    }
  },
  plugins: [generateSchemaWith$idPlugin()],
});

refHandling option

Three strategies for how $refs are resolved:

refHandling option description
inline Inlines $refss, creating self-contained schemas (no imports, but possible redundancy).
import Replaces$refs with imports of the target definition
keep Leaves $refs untouched — useful if you plan to interpret $refs dynamically or use a plugin

Circular references are supported:

  • inline: circular refs are replaced with {}
  • import: resolves the JSON schema but TypeScript recursion halts (any type, TS error 7022)
  • keep: circular refs left unresolved

See tests for details.

idMapper option

Every schema gets an internal id derived from its path in the OpenAPI document (e.g. /components/schemas/Pet). idMapper lets you rewrite these ids before they are written as $id and $ref values in the generated output.

Strip the /components/schemas/ prefix so Fastify's addSchema / getSchema can look up schemas by a short name:

await openapiToTsJsonSchema({
  openApiDocument: './openapi.yaml',
  targets: { collections: ['components.schemas'] },
  refHandling: 'keep',
  idMapper: ({ id }) => {
    const prefix = '/components/schemas/';
    return id.startsWith(prefix) ? id.slice(prefix.length) : id;
    // '/components/schemas/Pet' → 'Pet'
  },
});

Map to a flat, namespaced identifier across multiple target collections:

idMapper: ({ id }) => id.replaceAll('/', '.').replace(/^\./, ''),
// '/components/schemas/Pet' → 'components.schemas.Pet'
// '/paths/~1users~1{id}/get' → 'paths.~1users~1{id}.get'

Return values

Along with generated schema files, openapi-ts-json-schema returns metadata:

{
  // The path where the schemas are generated
  outputPath: string;
  metaData: {
    // Meta data of the generated schemas
    schemas: Map<
      // Schema internal id. Eg: "/components/schemas/MySchema"
      string,
      {
        id: string;
        // Internal unique schema identifier. Eg `"/components/schemas/MySchema"`
        $id: string;
        // JSON schema Compound Schema Document `$id`. Eg: `"/components/schemas/MySchema"`
        uniqueName: string;
        // Unique JavaScript identifier used as import name. Eg: `"componentsSchemasMySchema"`
        openApiDefinition: OpenApiObject;
        // Original dereferenced openAPI definition
        originalSchema: JSONSchema | string;
        // Original dereferenced JSON schema
        isRef: boolean;
        // True if schemas is used as a `$ref`
        shouldBeGenerated: boolean;
        // Text content of schema file
        fileContent?: string;

        absoluteDirName: string;
        // Absolute path pointing to schema folder (posix or win32). Eg: `"Users/username/output/path/components/schemas"`
        absolutePath: string;
        // Absolute path pointing to schema file (posix or win32). Eg: `"Users/username/output/path/components/schemas/MySchema.ts"`
        absoluteImportPath: string;
        // Absolute import path (posix or win32, without extension). Eg: `"Users/username/output/path/components/schemas/MySchema"`
      }
    >;
  }
}

Plugins

Plugins generate extra artifacts using the same internal metadata. Available plugins:

  • generateSchemaWith$idPlugin — adds a named export with the schema $id field set.
  • fastifyIntegrationPlugin — generates a fastify-integration.ts file with a schemas array for fastify.addSchema and a RefSchemas type for the json-schema-to-ts type provider.

See plugins documentation.

How it works

Given an OpenAPI definition file, openapi-ts-json-schema:

See developer's notes.

Todo

  • Improve external #refs handling (currently being inlined and duplicated)
  • Find a way to merge multiple different OpenApi definitions consistently
  • Consider implementing an option to inline circular $refs with a configurable nesting level

About

OpenAPI ➡️ TypeScript JSON Schema generator.

Topics

Resources

License

Stars

Watchers

Forks

Contributors