# openapi-ts-json-schema

[![Build Status][ci-badge]][ci]
[![Npm version][npm-version-badge]][npm]
[![Coveralls][coveralls-badge]][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](https://json-schema.org/) `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](https://github.com/ThomasAribart/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](https://github.com/ThomasAribart/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](https://github.com/microsoft/TypeScript/issues/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:

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

`openapi-ts-json-schema` generates:

```ts
// 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:

```ts
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 runtime** → [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript)
- **Runtime schemas + types from one spec** → `openapi-ts-json-schema` + `json-schema-to-ts` (works with any JSON Schema-compatible tool)
- **Both together** → `openapi-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](#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`](#options) object — see that section for full semantics.

```sh
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.

```sh
npx openapi-ts-json-schema --config ./openapi-ts-json-schema.config.ts
```

```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

```ts
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&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Description                                                                                                                                                                                                                                                                                                                  | Default    |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| **openApiDocument** _(required)_ | `string`                                                                                                                                                                                                                                                                           | Path to an OpenAPI document (JSON or YAML).                                                                                                                                                                                                                                                                                  | -          |
| **targets** _(required)_         | `{`</br>` collections?: string[];`</br>` single?: string[];`</br>`}`                                                                                                                                                                                                               | OpenAPI definition paths to generate JSON Schemas from _(dot notation)_.<br/><br/>`collections`: paths pointing to objects/records of definitions, where each entry will be generated (eg: `["components.schemas"]`).<br/><br/>`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.<br/>`"inline"`: inline `$ref` schemas.<br/>`"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](#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](./docs/plugins.md).                               | -       |

### Full configuration example

```ts
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 `$ref`s are resolved:

| `refHandling` option | description                                                                                    |
| -------------------- | ---------------------------------------------------------------------------------------------- |
| `inline`             | Inlines `$refs`s, creating self-contained schemas (no imports, but possible redundancy).       |
| `import`             | Replaces`$ref`s with imports of the target definition                                          |
| `keep`               | Leaves `$ref`s untouched — useful if you plan to interpret `$ref`s 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](https://github.com/toomuchdesign/openapi-ts-json-schema/blob/master/test/circularReference.test.ts) 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:

```ts
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:

```ts
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:

```ts
{
  // 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](./docs/plugins.md).

## How it works

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

- Resolves and dereferences $refs (using [@apidevtools/json-schema-ref-parser](https://github.com/APIDevTools/json-schema-ref-parser))
- Converts OpenAPI objects to JSON Schema (via [@openapi-contrib/openapi-schema-to-json-schema](https://github.com/APIDevTools/json-schema-ref-parser) & [`openapi-jsonschema-parameters`](https://www.npmjs.com/package/openapi-jsonschema-parameters))
- Generates `.ts` files exporting each schema as `as const`
- Mirrors the original OpenAPI structure in the generated folder
- Runs plugins before and after file generation

See [developer's notes](./docs/developer-notes.md).

## Todo

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

[ci-badge]: https://github.com/toomuchdesign/openapi-ts-json-schema/actions/workflows/ci.yml/badge.svg
[ci]: https://github.com/toomuchdesign/openapi-ts-json-schema/actions/workflows/ci.yml
[coveralls-badge]: https://coveralls.io/repos/github/toomuchdesign/openapi-ts-json-schema/badge.svg?branch=master
[coveralls]: https://coveralls.io/github/toomuchdesign/openapi-ts-json-schema?branch=master
[npm]: https://www.npmjs.com/package/openapi-ts-json-schema
[npm-version-badge]: https://img.shields.io/npm/v/openapi-ts-json-schema.svg
