CLI tool
astahmer/openapi-zod-client avatar
astahmer/openapi-zod-client

openapi-zod-client: generating a zodios client from an OpenAPI spec

not maintained anymore; use typed-openapi instead. Generate a zodios (typescript http client with zod validation) from an OpenAPI spec (json/yaml)

1,177 stars134 forksTypeScriptLicense varies

At a glance

What is it?
openapi-zod-client reads a JSON or YAML OpenAPI document and writes a TypeScript zodios client with runtime validation, either through its CLI or as a library. The project is deprecated in favour of typed-openapi, and the last push was on 2026-08-05, so it is worth knowing when to start a new integration on it.
Who is it for?
openapi-zod-client still earns its place in an existing pipeline that already commits a generated zodios client and already uses axios, because the generated file is readable and the CLI flags cover the awkward spec cases. For a new integration, typed-openapi is the better starting point, since the same author deprecated this package in favour of it.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 79 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

It turns a spec file into a zodios client with zod validation at runtime

The problem this solves is narrow and real. You are consuming an API you do not control, the only contract you have is an OpenAPI document in JSON or YAML, and you would rather not hand-write a TypeScript client for it. openapi-zod-client reads that document and produces a zodios client: a TypeScript HTTP client where each endpoint carries a zod schema, so responses are validated at runtime instead of only being described by types at compile time.

Two entry points are offered. Programmatically, you get the computed schemas and endpoints back and can do whatever you want with them. As a CLI, you get a single prettier-formatted TypeScript file, with variables deduplicated when the same schema is referenced from more than one place through `$ref`.

The generated client uses axios as its HTTP client, which is a design decision worth weighing early. It also supports turning the output into just schemas or just types, which is the path most teams end up on when the request and response shapes matter but the transport is already fixed elsewhere.

The package is deprecated, and the author points at typed-openapi

The first line of the project documentation is a deprecation notice, and the repository description says the same thing: not maintained anymore, use typed-openapi instead. The comparison section draws the difference explicitly.

openapi-zod-client is a CLI that generates a zodios API client, currently using axios as the HTTP client. typed-openapi is a CLI and library that generates a headless client, meaning you bring your own fetcher, with fetch, axios, ky and similar options, and it can output schemas as plain TypeScript types for instant editor suggestions, or as runtime validation schemas built with zod, typebox, arktype, valibot, io-ts or yup.

That is a real architectural difference, not a version bump. A zodios client brings its own request layer and its own axios dependency. A headless generated client gives you the schema definitions and leaves the transport to you. If you are starting fresh, the deprecation notice is the strongest single signal in the repository, and it points at the author's other project.

The CLI takes a spec path or URL and writes one TypeScript file

The command interface is small in its core and wide in its options. The positional argument is the input, described as a path or URL to an OpenAPI or Swagger document as JSON or YAML, and the output path defaults to the input path with a `.ts` extension.

sh
openapi-zod-client <input>

A subset of the options covers most real work:

sh
  -o, --output <path>               Output path for the zodios api client ts file (defaults to `<input>.client.ts`)
  -t, --template <path>             Template path for the handlebars template that will be used to generate the output
  -p, --prettier <path>             Prettier config path that will be used to format the output client file
  -b, --base-url <url>              Base url for the api
  --export-schemas                  When true, will export all `#/components/schemas`
  --with-deprecated                 when true, will keep deprecated endpoints in the api output
  --group-strategy                  groups endpoints by a given strategy, possible values are: 'none' | 'tag' | 'method' | 'tag-file' | 'method-file'
  --strict-objects                  Use strict validation for objects so we don't allow unknown keys. Defaults to false. (default: false)

Two of these are worth reading twice. `--group-strategy` decides how endpoints are laid out in the file, with `none`, `tag`, `method`, `tag-file` and `method-file` available, and the difference between a single flat file and one file per tag is large enough to change how the generated client is imported. `--implicit-required` flips the default: when set, all properties of an object are required unless an explicit `required` array says otherwise. Specs written in one style or the other will produce a client that fails validation on the first response if the flag is wrong.

Generating a client in two commands, or none at all

Installation is a development dependency, and generation runs as a script:

bash
pnpm i -D openapi-zod-client
pnpm openapi-zod-client "./input/file.json" -o "./output/client.ts"

The first line installs the generator, the second points it at a local JSON spec and writes the client. The YAML equivalent works the same way with a `.yaml` input.

If you would rather not add a dependency, the package is runnable directly:

bash
pnpx openapi-zod-client "./input/file.yaml" -o "./output/client.ts"

The suggested workflow is to run this in CI and import the generated `api` client from application code, so that the client is always regenerated when the spec changes rather than drifting from it. The generated file is meant to be committed and reviewed, which also means that a spec change shows up as a diff in your repository rather than as a runtime failure.

One caveat is visible in the option list: `--no-with-alias` and `-a, --with-alias` both appear, and both are documented as controlling whether aliases are used as api client methods with a default of true. That duplication is a small sign that the flags have accumulated over time, so check the help output of the version you install rather than assuming an older guide matches.

A custom handlebars template can emit schemas instead of a client

The output is produced through a handlebars template, and you can supply your own along with a prettier configuration:

sh
pnpm openapi-zod-client ./example/petstore.yaml -o ./example/petstore-schemas.ts -t ./example/schemas-only.hbs -p ./example/prettier-custom.json --export-schemas

Here the template path points at a schemas-only template and the `--export-schemas` flag adds the `#/components/schemas` definitions to the output, producing a file of types and schemas rather than a configured client. The repository ships that example, with a matching output file, under `examples/schemas-only/`, along with a `petstore.yaml` spec and smaller fixtures in `examples/petstore-minimal.json` and `examples/repro.yaml`.

This is the escape hatch that makes the tool usable in more than one way. A team that only wants the zod schemas for a handful of models, or a team that wants the endpoint list as plain TypeScript types for its own thin fetch wrapper, can take the template route and ignore the zodios client entirely. The catch is that you are then maintaining a template, and the README does not document the template variables, so you would be reading the default template in the source to learn them.

Deciding which response counts as success is an expression you supply

An OpenAPI operation can declare several response statuses, and the generator has to pick which one becomes the endpoint's main response and which ones become errors. Two flags let you decide that, and the documentation binds them to named functions in the source: `--success-expr` to `isMainResponseStatus` and `--error-expr` to `isErrorStatus`.

The expression is evaluated safely, with the whence library, and works like axios's `validateStatus`:

sh
--success-expr "status >= 200 && status < 300"

There is a related option, `--media-type-expr`, to decide which response content should be allowed, and `--default-status` which, when set to `auto-correct`, uses `default` as the fallback for a response when no status code was declared. That last one is the flag to reach for when a spec omits response codes: without it, the generator has nothing to key the success shape on, and a client that validates the wrong body is a runtime error rather than a compile error.

Note the asymmetry with axios. The generated client uses axios, but this decision is made at generation time, so changing how you treat a status code means regenerating the file, not adjusting a runtime option.

Generated code quality depends on the spec, and the spec may be out of your hands

The tool is tested against the official OpenAPI specification sample documents, and it is built with vitest, so the fixtures it is exercised against are the canonical ones rather than a real service. That is the right kind of test for a generator, but it says nothing about the spec you actually have.

The failure mode to expect is a spec whose modelling differs from the samples. A large spec with many tags will produce a very differently shaped client depending on `--group-strategy`, and a spec that uses `oneOf`, `additionalProperties` or unusual `nullable` handling will surface in the generated zod schemas, where a mismatch turns into a runtime parse error on a real response rather than a compile error in your code. The `--strict-objects` flag, off by default, is the knob that decides whether unknown keys are rejected; leaving it off is the forgiving default.

There is also a structural limit. The project is mainly a CLI that writes a file, so the most stable contract you rely on is the generated output and the flags. Any change in the generator's default template or in zodios itself can reshape that output without a major version bump on your side, which is one more reason to commit the generated file and review the diff rather than generating it at runtime.

A monorepo built with preconstruct, changesets and pnpm

The repository is a pnpm workspace rather than a single package. The root package.json is named `openapi-zod-client-monorepo`, is marked private, and points preconstruct at a single package directory, `lib`. The published package itself lives there, and the `examples/`, `playground/`, `samples/` and `lib/` directories at the top level show a project that has a playground for manual testing and a samples generator script for producing fixtures.

The build tooling is worth a line each. preconstruct handles development and publishing of the `lib` package, changesets handles versioning and release, and the changelog is generated with conventional-changelog. The package requires Node 18.16.0 or newer, and the repository pins pnpm 8.2.0 as its package manager.

On licensing, the root package.json declares `"license": "ISC"` for the monorepo package, and the published package's own licence field is not stated in the package metadata. If the licence terms of a generated client matter to you, which they might if you redistribute it, read the licence field in the installed package rather than assuming the root manifest covers it. The last push was on 2026-08-05, and the most recent published release is 1.18.3 from 2025-02-10, which is consistent with a project that is no longer taking new work.

Editorial conclusion

openapi-zod-client still earns its place in an existing pipeline that already commits a generated zodios client and already uses axios, because the generated file is readable and the CLI flags cover the awkward spec cases. For a new integration, typed-openapi is the better starting point, since the same author deprecated this package in favour of it. Before you commit either one, generate a client from your own spec and read the output, and check the axios dependency and the deprecated-endpoint default, because a spec with many tags produces a very different file shape from a spec with one.

Frequently asked questions

How do I generate a zodios client with openapi-zod-client?

Install it as a development dependency with `pnpm i -D openapi-zod-client`, then run `pnpm openapi-zod-client "./input/file.json" -o "./output/client.ts"`. The input can also be a YAML file or a URL, and the output defaults to the input path with a `.ts` extension.

Is openapi-zod-client still maintained?

No. The project documentation marks it deprecated and points at typed-openapi instead. The last push was on 2026-08-05, and the most recent release is 1.18.3 from 2025-02-10.

What is the difference between openapi-zod-client and typed-openapi?

openapi-zod-client generates a zodios client that uses axios as its HTTP client. typed-openapi generates a headless client where you bring your own fetcher, and can output plain TypeScript types or runtime schemas built with zod, typebox, arktype, valibot, io-ts or yup.

Can openapi-zod-client generate only schemas without a client?

Yes. You can pass your own handlebars template with the `-t` option and add the `--export-schemas` flag, which is how the schemas-only example under `examples/schemas-only/` is produced.

Official sources

  1. astahmer/openapi-zod-client on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/astahmer-openapi-zod-client.svg)](https://hysenlabs.com/projects/astahmer-openapi-zod-client)

Was this useful?