Compatibility report
openapi-typesafe promises the same output as openapi-typescript 7.13, except where the output is a bug. This page shows how that is checked and what the last run found. CI runs the full corpus every night; this page is refreshed from that run for every release candidate and every major or minor release, and the GitHub release notes link it.
Result (2026-11-04): 50,960 cases, 0 unexpected differences. 99.99% of the outputs are the same byte for byte, and 100% have the same types.
What is compared
- Golden output: openapi-typescript at commit
d46a319a(tagfork-base), the code of the 7.13.0 release. A separate run of 4,600 cases found it gives the same bytes as the publishedopenapi-typescript@7.13.0on every document both can load. - Candidate: the current openapi-typesafe generator.
- Corpus: 1,820 OpenAPI 3.x documents: the APIs.guru directory plus 20 curated ones (the large GitHub, Stripe, OpenAI, Cloudflare, Twilio and Kubernetes APIs and the official OpenAPI examples, including 3.2). Each one is pinned by its SHA-256 hash.
- Options: 28 option sets: the defaults, every boolean flag on its own and the combinations that interact (
--enumwith--enum-values,--root-typeswith its naming flags, and others). 1,820 × 28 = 50,960 cases.
For every case the tool generates the types with both generators and compares them in three steps:
- Bytes. The files are compared as text. The header line that names the generator is ignored.
- Types. If the bytes differ, each exported type is checked with a strict identity check: two types count as equal only if TypeScript sees them as identical, so
readonlyandanyare not ignored. - Compiles. If the golden output itself does not compile, both outputs are type-checked with strict settings and the error counts are compared.
Summary
| Result | Cases | What it means |
|---|---|---|
| Same bytes | 49,794 | The output is the same file. |
| Same types | 4 | --enum-values constants whose golden names start with a digit (not valid JavaScript) get a value prefix; every exported type is equal. |
| Different | 0 | — |
| Golden output does not compile | 994 | openapi-typescript 7.13 writes TypeScript with errors; ours has no more errors, and 766 of these compile without any (see below). |
| Golden generator fails | 168 | openapi-typescript 7.13 stops with an error on 6 documents (see below). |
| Candidate fails | 0 | — |
The rates count only cases where the golden output exists and compiles (49,798 cases): byte identity 99.99%, type identity 100%, and 100% of the golden types are kept.
Documents the golden generator cannot load
These 6 documents fail in every option set (6 × 28 = 168 cases):
- 3 are OpenAPI 3.2 documents. The golden generator rejects 3.2; openapi-typesafe loads them and types the 3.2 features as well.
- 3 point to files or URLs that cannot be downloaded (a relative
$refoutside the document, and a URL that returns HTTP 400). Both generators stop on them.
Golden output that does not compile
These cases are not compared type by type: the golden file has TypeScript errors, so the check is that ours has no more. They come from option combinations whose original output does not compile:
| Option set | Cases | Ours compiles | Cause |
|---|---|---|---|
--path-params-as-types | 791 | 600 | A path template such as `/apps/${string}` also matches another path whose type does not fit; ours writes such templates in an intersection where the error is sure (see the CLI docs). |
--make-paths-enum | 81 | 78 | Paths that map to the same member name. |
--enum (with --enum-values, --dedupe-enums, --conditional-enums) | 108 | 77 | Enum members whose names collide, which ours numbers, and names that start with a digit. |
--exclude-deprecated | 13 | 11 | A kept schema points to a deprecated one, which ours keeps. |
--root-types-keep-casing | 1 | 0 | A schema name that is not a valid identifier. |
Where ours still has errors, it has no more than the golden output. In 2 of the 791 cases (Microsoft Graph and Graph beta with --path-params-as-types, 24 MB each) TypeScript cannot finish the check of either file: it stops with RangeError: Map maximum size exceeded, as a user's tsc does; the default output of the same documents compiles.
Intentional differences
Each change below fixes a bug and has a test and a changelog entry. Before comparing, the tool applies the same change to the golden output, so a case that differs only by these counts as the same. The table lists how many cases each change touched.
| Change | Fixes | Cases |
|---|---|---|
--enum: an empty string value gets the member name "" instead of none | #2807, #2812 | 93 |
--enum-values: FlattenedDeepRequired flattens readonly arrays and nullable parents | #2714, #2737 | 1,527 |
--enum-values: the ReadonlyArray helper accepts readonly arrays | #2749 | 1,527 |
--read-write-markers: Readable / Writable keep primitives, arrays, tuples and null-only keys | #2615, #2620, #2632, #2717, #2765 | 3,628 |
--enum: a name that starts with a digit gets the Value prefix | the golden output does not compile | 12 |
--root-types-keep-casing: characters that cannot be in an identifier become _ | the golden output does not compile | 151 |
--root-types-no-schema-prefix: a schema name that starts with a digit keeps its prefix | the golden output does not compile | 8 |
The migration guide lists every change from openapi-typescript 7.13, including the ones this corpus does not reach.
Real projects
22 open-source projects that use openapi-typescript or openapi-fetch were checked out at a fixed commit and run as they are, then again after switching to openapi-typesafe by package name only. All 22 pass every step they had passed before (install, build, type check, tests, whatever the project has), and all 18 files they generate are the same as before.
| Project | Commit |
|---|---|
| unsplash-js | de61b942e16a |
| swr-openapi | 34d1e0dbb524 |
| openapi-fetch-gen | 1b2ec9ee05d2 |
| vc-bots | 0bfc54accbe4 |
| org-roam-ui-lite | e036f5ee0a6f |
| acai-cli | 6ec9df89dda3 |
| obsidian-media-db | 81ff85560f2a |
| immich-discord-bot | a0369c113426 |
| lichess-broadcaster | e5a309199251 |
| arena-cli | 63441ef27f72 |
| whatsapp-js-sdk | 240843d66159 |
| permit-cli | 745e8a3dc656 |
| miro-code | f2226a240c56 |
| immerle-ui | c715fb9f062c |
| vue-h5-template | a829261c3558 |
| coderabbit-bitbucket | c39a1e9c37e7 |
| vitrina-api | ad69f0078de7 |
| rootly-ts | dece1e2322e8 |
| ai-dial-sdk | 1b23ddf8c449 |
| sendly-js | 49a9c06140d7 |
| forge-dashboard-sdk | e1f00f9d8e9a |
| openapi-axios | 4fc0ddec4eed |
Versioning
From 1.0.0 the packages follow semantic versioning, and all five always have the same version. The full rule is ADR-034.
Stable in 1.x. A minor or patch release does not remove, rename or narrow:
- the package entry points (
import … from "openapi-typesafe"and the same for the other packages), their exported names and documented types, and theopenapi-typesafeandopenapi-typesafe-migratecommands; - the CLI flags, the
x-openapi-tssettings inredocly.yaml, the Node API options and their defaults; - the error codes and exit codes, the warning codes, the deprecation codes and the fix names: a code keeps its meaning;
- the generated types: the same document with the same options gives the same exported types. A change to output that compiles is an opt-in fix; output that does not compile can be fixed in any release.
What a minor release may add. New exports, options, flags, error, warning and deprecation codes, fix names (fix: ["all"] then includes the new fix), IR node kinds, members of exported unions such as ErrorCode, WarningCode, OutputFix, IrType and HttpMethod, and optional fields. So:
- give a
switchover a code or akindadefaultbranch; - return an IR node of a kind you do not know unchanged;
- do not treat
ERROR_CODES,WARNING_CODESorOUTPUT_FIXESas a complete list.
Not covered. These may change in any release:
- deep imports such as
openapi-typesafe/dist/…oropenapi-typesafe/src/…; they keep resolving where openapi-typescript had the same file, but are not part of the API; - the text of messages, warnings and notices (match on the
code), and the CLI's decorated output; - the keys of an error's
details, except those the Node API docs list; - log formats (
DEBUG=openapi-ts:*lines, the migration tool's--logfile) and the generated header comment; - exports, fields and members marked
@internal, and the generator's own state onoptions.ctx; - the shape of the types generated for the OpenAPI 3.2 additions (
queryoperations,additionalOperations,itemSchema,in: querystring,components.mediaTypes), which is experimental in 1.x. A change is named in the changelog.
Node.js and TypeScript. A minor release may raise the lowest supported Node.js version, only to drop a line that has reached its end of life (Node.js 22 on 2027-04-30), and its changelog says so. The lowest TypeScript version for type-checking the output and the clients (5.4) rises only in a major release.
Deprecations. Nothing is removed before the next major version. A deprecated export is marked @deprecated in the types, so editors strike it through, and the docs say what to use instead. Where a deprecation also warns at run time, it is a Node.js DeprecationWarning with an OPENAPI_TYPESAFE_DEP_* code (Deprecations).
Run it yourself
The comparison tool lives in tools/compat-harness. Its README explains how to download the corpus, build both generators and produce this report. CI runs it on a 200-document subset with the default options for every pull request and push to main, and on the full corpus with all 28 option sets every night.