diff --git a/.changeset/agent-friendly-generators.md b/.changeset/agent-friendly-generators.md
new file mode 100644
index 0000000000..42ee4195d2
--- /dev/null
+++ b/.changeset/agent-friendly-generators.md
@@ -0,0 +1,6 @@
+---
+'@redocly/client-generator': minor
+'@redocly/cli': minor
+---
+
+Added agent-friendly client generation: `python`, `go`, `php`, and `cli` generators beside the TypeScript client, each self-documenting with `--docs`, configurable per generator, and available as source in your own repository through `eject-generator`.
diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md
new file mode 100644
index 0000000000..faf24da081
--- /dev/null
+++ b/.claude/rules/architecture.md
@@ -0,0 +1,47 @@
+# Repository architecture
+
+Where things live, so a change lands in the right package.
+
+This is a TypeScript monorepo with npm workspaces containing four packages:
+
+## `packages/core` (@redocly/openapi-core)
+
+The heart of the project.
+Handles all OpenAPI/AsyncAPI linting, validation, bundling, and decoration logic.
+This package is also used in external apps such as `language-server` and `vs-code-extension`.
+
+Key directories:
+
+- `src/rules/` — Built-in linting rules, organized by spec type (`oas2/`, `oas3/`, `oas3_1/`, `async2/`, `async3/`, `arazzo/`, `common/`). Each rule is its own file.
+- `src/config/` — Configuration loading and resolution (reads `redocly.yaml`).
+- `src/decorators/` — Built-in decorators for transforming API descriptions.
+- `src/bundle/` — Bundling logic that resolves `$ref` across multiple files.
+- `src/resolve.ts` — Document resolution for multi-file specs (local and remote).
+- `src/types/` — TypeScript type definitions for OAS2, OAS3, AsyncAPI, Arazzo.
+
+## `packages/cli` (@redocly/cli)
+
+User-facing CLI layer built on top of core.
+Uses yargs for argument parsing.
+
+- `src/index.ts` — Main command dispatcher.
+- `src/commands/` — One file per command.
+- Commands use `commandWrapper()` for consistent output, config loading, config linting, and exit codes (0 = success, 1 = execution error, 2 = config error).
+
+## `packages/respect-core` (@redocly/respect-core)
+
+API contract testing framework.
+Validates real API responses against OpenAPI/Arazzo specs.
+
+- `src/run.ts` — Test execution logic.
+- `src/modules/` — Core testing modules, including runtime expression evaluation.
+
+## `packages/client-generator` (@redocly/client-generator)
+
+Experimental package for generating clients from OpenAPI descriptions — the TypeScript client
+plus the `python`, `go`, and `php` SDKs, the generated CLI, and its Markdown reference.
+
+- `src/intermediate-representation/` — the language-neutral API model every generator reads.
+- `src/emitters/` — the renderers that turn that model into source text.
+- `src/generators/` — one folder per generator: a thin entry plus the design skill it must match.
+- `src/authoring/` — the language-neutral toolkit generators are written with, ours and users'.
diff --git a/.claude/rules/core-principles.md b/.claude/rules/core-principles.md
index cf785aa508..3706d5b37f 100644
--- a/.claude/rules/core-principles.md
+++ b/.claude/rules/core-principles.md
@@ -19,7 +19,7 @@ Release and commit mechanics are procedures, not principles — they live in
1. Respect the core patterns: Walker, Visitors, and Nodes.
New rules and decorators follow this pattern instead of using regex or manual drilling objects described by the supported specifications.
- The full guide is in [`rules-system.md`](./rules-system.md).
+ The full guide is in [the `rules-system` skill](../skills/rules-system/SKILL.md).
1. Explain in chat, not in files.
Don't create explanation, summary, or design files unless asked.
diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md
index 722c27c7b4..21ad5eee8f 100644
--- a/.claude/rules/testing.md
+++ b/.claude/rules/testing.md
@@ -2,10 +2,52 @@
1. Write meaningful tests that exercise real behavior — not tests that exist only to raise coverage.
One focused, clear test is enough.
-1. Rule tests are unit tests by convention: parse a YAML document, run `lintDocument`, and assert with `toMatchInlineSnapshot` — a behavior test in itself (given this input, these problems).
+1. A unit test lives in a `__tests__` folder beside the file it tests, and mirrors its name:
+ `src/commands/eject-generator.ts` is tested by `src/commands/__tests__/eject-generator.test.ts`.
+ Do not rebuild the source tree inside a `__tests__` folder (`src/__tests__/commands/…`) — the
+ older tests that do are historical, and a reviewer should not have to guess which layout a
+ new test follows. One module gets one test file: split a long one by `describe`, not by adding
+ a second file for the same source.
+1. Rule tests are unit tests by convention: parse a YAML document, run `lintDocument`, and assert
+ with `toMatchInlineSnapshot` — a behavior test in itself (given this input, these problems).
Generate new snapshots and update stale ones as part of the change.
+
+ The pattern — parse, lint, assert on the whole output:
+
+ ```ts
+ import { outdent } from 'outdent';
+ import { parseYamlToDocument, replaceSourceWithRef } from '../../../../__tests__/utils.js';
+ import { createConfig } from '../../../config/index.js';
+ import { lintDocument } from '../../../lint.js';
+ import { BaseResolver } from '../../../resolve.js';
+
+ describe('Oas3 no-my-rule', () => {
+ it('should report a violation', async () => {
+ const document = parseYamlToDocument(
+ outdent`
+ openapi: 3.0.0
+ ...
+ `,
+ 'foobar.yaml'
+ );
+
+ const results = await lintDocument({
+ externalRefResolver: new BaseResolver(),
+ document,
+ config: await createConfig({ rules: { 'no-my-rule': 'error' } }),
+ });
+
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`...`);
+ });
+ });
+ ```
+
1. Compile before testing.
+
Unit tests import from `lib/` (compiled output), not `src/` — run `npm run compile` after every change.
+
1. Run the full suite (`npm test`) when you touch core linting logic, and make sure all tests pass in CI.
+1. Client generation has its own suite: `npm run client-generators` runs the client-generator unit tests plus the `tests/e2e/generate-client` bars (which compile real Python/Go/PHP/TypeScript output).
+ Run it for any generation change; `npm run e2e` does not include those tests.
1. Coverage thresholds (`vitest.config.ts`) are a guide, not a number to game.
If a feature or fix is already covered by e2e tests, propose lowering the threshold rather than padding the suite with unit tests that only chase coverage.
diff --git a/.claude/skills/redocly-cli/SKILL.md b/.claude/skills/redocly-cli/SKILL.md
index 2733049c93..fc3facff9b 100644
--- a/.claude/skills/redocly-cli/SKILL.md
+++ b/.claude/skills/redocly-cli/SKILL.md
@@ -126,7 +126,7 @@ Configure it durably under a `client` block in `redocly.yaml` instead of flags:
```yaml
client:
- generators: [sdk, zod] # add-ons: tanstack-query, swr, mock, transformers, or a plugin path
+ generators: [typescript, zod] # add-ons: tanstack-query, swr, mock, transformers, or a plugin path
outputMode: split
pagination: # config-only, no CLI flag
style: cursor
diff --git a/.claude/rules/rules-system.md b/.claude/skills/rules-system/SKILL.md
similarity index 95%
rename from .claude/rules/rules-system.md
rename to .claude/skills/rules-system/SKILL.md
index 049ee5ddd5..2ba3f18d82 100644
--- a/.claude/rules/rules-system.md
+++ b/.claude/skills/rules-system/SKILL.md
@@ -1,3 +1,8 @@
+---
+name: rules-system
+description: How to write built-in lint rules and decorators for packages/core — the Walker/Visitors/Nodes pattern, visitor hooks, the ctx object, rule registration, and stateful rule examples. Use when adding or changing a rule, decorator, or preprocessor.
+---
+
## Rules System: Walker, Visitors, and Nodes
This is the most important pattern to understand when working in `packages/core`.
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index fbf5de851e..bd171a554b 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -37,10 +37,24 @@ jobs:
uses: davelosert/vitest-coverage-report-action@d63aa97db4c0319f304f1787689de1ca548365cf # v2.11.1
e2e:
- # The e2e suite is split across shards so no single runner carries the whole set.
- # Running all suites in one step was cancelled mid-run by the Actions service once the
- # generate-client suites grew past ~28 (a healthy runner, no resource exhaustion);
- # each shard stays well under that.
+ # Everything under tests/e2e EXCEPT generate-client, which has its own job below.
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 24
+ cache: npm
+ - name: Install dependencies
+ run: npm ci
+ - name: E2E Tests
+ run: npm run e2e
+
+ client-generators:
+ # Client generation has its own job: the client-generator unit tests plus the
+ # generate-client e2e bars, which compile real Python, Go, PHP, and TypeScript output
+ # (including big real-world descriptions) — the slowest tests we have, needing
+ # toolchains nothing else does. Adding a language bar here cannot slow the shared e2e job.
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -52,10 +66,22 @@ jobs:
with:
node-version: 24
cache: npm
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
+ with:
+ python-version: '3.12'
+ - name: Install httpx and pydantic (the Python import bars need them)
+ run: pip install httpx pydantic
+ # Go, gofmt, and php come with the runner image; a bar whose toolchain is missing
+ # skips itself, so a thinner image degrades coverage instead of failing the job.
+ - name: Cache the pinned GitHub REST description
+ uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
+ with:
+ path: tests/e2e/generate-client/.cache
+ key: large-descriptions-${{ hashFiles('tests/e2e/generate-client/large-descriptions.test.ts') }}
- name: Install dependencies
run: npm ci
- - name: E2E Tests (shard ${{ matrix.shard }}/2)
- run: npm run e2e -- --shard=${{ matrix.shard }}/2
+ - name: Client generator tests (shard ${{ matrix.shard }}/2)
+ run: npm run client-generators -- --shard=${{ matrix.shard }}/2
examples:
# The examples gitignore their generated clients (only zero-install-quickstart commits
diff --git a/.gitignore b/.gitignore
index f2173085e2..79060ae38b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,5 @@ __changesets__.json
**/.claude/agent-registry.json
**/.claude/agent-memory-local
**/.claude/first-run
-**/.claude/assistant-daemon-state.json
\ No newline at end of file
+**/.claude/assistant-daemon-state.json
+__pycache__/
diff --git a/.oxfmtrc.json b/.oxfmtrc.json
index fc6793d2c6..c3005c873d 100644
--- a/.oxfmtrc.json
+++ b/.oxfmtrc.json
@@ -11,6 +11,7 @@
"packages/core/src/rules/common/__tests__/fixtures/invalid-yaml.yaml",
"tests/performance/api-definitions/",
"tests/e2e/generate-client/examples/*/src/api/",
+ "tests/e2e/generate-client/examples/*/generators/",
"tests/e2e/generate-client/*-consumer/api*.ts",
"tests/smoke/**/*.yaml",
"snapshot*.txt",
diff --git a/AGENTS.md b/AGENTS.md
index ea29dcee6a..93fb3604a4 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -46,9 +46,12 @@ npm run unit -- -t 'test name pattern'
# Update snapshots
npm run unit -- -u
-# Run e2e tests
+# Run e2e tests (everything under tests/e2e except generate-client)
npm run e2e
+# Run every generator test (client-generator unit + generate-client e2e)
+npm run client-generators
+
# Run the full test suite (compile + typecheck + unit + e2e)
npm test
@@ -64,47 +67,13 @@ npm run cli -- lint openapi.yaml
## Architecture
-This is a TypeScript monorepo with npm workspaces containing four packages:
-
-### `packages/core` (@redocly/openapi-core)
-
-The heart of the project.
-Handles all OpenAPI/AsyncAPI linting, validation, bundling, and decoration logic.
-This package is also used in external apps such as `language-server` and `vs-code-extension`.
-
-Key directories:
-
-- `src/rules/` — Built-in linting rules, organized by spec type (`oas2/`, `oas3/`, `oas3_1/`, `async2/`, `async3/`, `arazzo/`, `common/`). Each rule is its own file.
-- `src/config/` — Configuration loading and resolution (reads `redocly.yaml`).
-- `src/decorators/` — Built-in decorators for transforming API descriptions.
-- `src/bundle/` — Bundling logic that resolves `$ref` across multiple files.
-- `src/resolve.ts` — Document resolution for multi-file specs (local and remote).
-- `src/types/` — TypeScript type definitions for OAS2, OAS3, AsyncAPI, Arazzo.
-
-### `packages/cli` (@redocly/cli)
-
-User-facing CLI layer built on top of core.
-Uses yargs for argument parsing.
-
-- `src/index.ts` — Main command dispatcher.
-- `src/commands/` — One file per command.
-- Commands use `commandWrapper()` for consistent output, config loading, config linting, and exit codes (0 = success, 1 = execution error, 2 = config error).
-
-### `packages/respect-core` (@redocly/respect-core)
-
-API contract testing framework.
-Validates real API responses against OpenAPI/Arazzo specs.
-
-- `src/run.ts` — Test execution logic.
-- `src/modules/` — Core testing modules, including runtime expression evaluation.
-
-### `packages/client-generator` (@redocly/client-generator)
-
-Experimental package for generating TypeScript clients from OpenAPI specs.
+Where each package sits, and the key directories inside it, are in
+[`.claude/rules/architecture.md`](./.claude/rules/architecture.md) — read it before a change lands
+in the wrong package.
## Build System
-`packages/core`, `packages/respect-core`, and `packages/client-generator` are compiled by TypeScript (`tsc -b tsconfig.build.json`).
+`packages/core` and `packages/respect-core` are compiled by TypeScript (`tsc -b tsconfig.build.json`).
`packages/cli` is bundled by esbuild (`packages/cli/scripts/build.mjs`) — it produces `lib/index.js` (entry chunk, ~450 kB) and lazy chunks under `lib/chunks/` (redoc + react, loaded only when `build-docs` runs).
The root `npm run compile` runs both steps: tsc for core/respect-core, then the esbuild bundle for the CLI.
@@ -114,7 +83,7 @@ The published CLI package ships from a staged `.publish/` directory (created by
Linting in `packages/core` rests on three concepts: the **Walker** traverses the parsed API description and resolves `$ref`s, **Visitors** are objects keyed by **Node** type, and the Walker calls each visitor's `enter` / `leave` / `skip` hooks as it reaches a node.
New rules and decorators follow this pattern instead of parsing documents by hand.
-The full guide, with examples, is in [`.claude/rules/rules-system.md`](./.claude/rules/rules-system.md).
+The full guide, with examples, is in [the `rules-system` skill](./.claude/skills/rules-system/SKILL.md).
## Add or change a built-in rule
@@ -144,40 +113,11 @@ Naming and reuse:
- A `redocly.yaml` in the repository root affects unit tests in the CLI package.
Remove it before running them.
- Run the full suite (`npm test`) when you touch core linting logic.
+- Run `npm run client-generators` when you touch client generation — it is the whole generator suite in one command.
-The full testing and QA rules are in
+The full testing and QA rules — including the rule test pattern to copy — are in
[`.claude/rules/testing.md`](./.claude/rules/testing.md).
-The rule test pattern looks like this:
-
-```ts
-import { outdent } from 'outdent';
-import { parseYamlToDocument, replaceSourceWithRef } from '../../../../__tests__/utils.js';
-import { createConfig } from '../../../config/index.js';
-import { lintDocument } from '../../../lint.js';
-import { BaseResolver } from '../../../resolve.js';
-
-describe('Oas3 no-my-rule', () => {
- it('should report a violation', async () => {
- const document = parseYamlToDocument(
- outdent`
- openapi: 3.0.0
- ...
- `,
- 'foobar.yaml'
- );
-
- const results = await lintDocument({
- externalRefResolver: new BaseResolver(),
- document,
- config: await createConfig({ rules: { 'no-my-rule': 'error' } }),
- });
-
- expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`...`);
- });
-});
-```
-
## Code quality — no AI slop
Before opening a PR, strip the things an assistant tends to add that a human reviewer would not:
@@ -207,7 +147,7 @@ The full release and commit workflow is in [`.claude/rules/workflow.md`](./.clau
- Every feature or fix needs a changeset: run `npx changeset` and describe the change in sentence case.
If the change lives in `packages/core` or `packages/respect-core` but affects CLI behavior, include `@redocly/cli` as well.
- `@redocly/cli`, `@redocly/openapi-core`, and `@redocly/respect-core` share one version and release together; `@redocly/client-generator` is versioned separately.
+ All three packages share one version and release together.
- Use [Conventional Commits](https://www.conventionalcommits.org/) for commit messages.
- Don't add AI co-author or "Generated by" lines to commits.
- Don't modify the pull request template.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9351364581..d7101340e7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -238,6 +238,28 @@ Note that the snapshot does not always match the command output because of the w
This is intentional so outputs stay consistent for snapshot testing.
The order of stdout and stderr in a snapshot may differ from what you see in the terminal, but the combined output is stable.
+### Generator tests
+
+Client generation has its own suite: `npm run client-generators` runs the `@redocly/client-generator` unit tests together with the `tests/e2e/generate-client` end-to-end tests, so one command covers everything about generation.
+
+```bash
+npm run client-generators # every generator test
+npm run client-generators -- tests/e2e/generate-client/go.test.ts # one file
+npm run client-generators -- -t 'gofmt' # by test name
+```
+
+Those e2e tests compile their output with real toolchains, so what is available decides what runs:
+
+- **Python** (`python3`, plus `httpx` for the import bars) and **Go** (`go build`, `go vet`, `gofmt`) and **PHP** (`php -l`) — a bar for a missing toolchain skips itself rather than failing, so a partial local setup still gives a useful run. CI installs Python and `httpx`; Go and PHP come with the runner image.
+- The largest bars generate from big real-world descriptions (Rebilly, the GitHub REST API), which is why they are slow and why the suite has its own CI job — a growing set of compiled-language bars must not slow the shared e2e job.
+
+`npm run e2e` covers everything under `tests/e2e/` **except** `generate-client`.
+`npm run unit` still includes the client-generator unit tests, so the coverage report stays whole.
+
+Several of these tests run a local HTTP server and assert on its request log.
+On a machine with many cores, vitest runs enough of them in parallel to occasionally reset a connection — a failure that says nothing about the code under test.
+Reading a server's log goes through `serverLog()` in `tests/e2e/generate-client/helpers.ts`, which retries for that reason; if you see an isolated `ECONNRESET` or `fetch failed`, re-run the file before investigating.
+
### Smoke tests
Smokes are for testing the CLI in different environments.
diff --git a/docs/@v2/commands/eject-generator.md b/docs/@v2/commands/eject-generator.md
new file mode 100644
index 0000000000..4596419700
--- /dev/null
+++ b/docs/@v2/commands/eject-generator.md
@@ -0,0 +1,113 @@
+# `eject-generator`
+
+## Introduction
+
+The `eject-generator` command copies a built-in client generator into your repository as an editable file.
+You own the ejected generator and can customize it.
+The generated client stays generated and reproducible.
+Do not edit it manually.
+You or your agent edit the generator, and the `redocly generate-client` command rebuilds the client.
+When the spec changes later, the command regenerates the client and keeps your customization.
+
+You can eject every built-in generator: the SDKs (`typescript`, `python`, `go`, `php`) and the add-on generators (`zod`, `mock`, `cli`, `swr`, `tanstack-query`, `transformers`).
+A generator that writes reference documentation carries that page with it, so ejecting `cli` or `python` also hands you the layout of its page.
+The `tanstack-query-vue`, `-svelte`, and `-solid` variants are the same generator with one different argument.
+Eject `tanstack-query` and set the framework in your copy.
+
+## Usage
+
+```bash
+redocly eject-generator python
+redocly eject-generator zod --dir ./generators
+redocly eject-generator php --update
+redocly eject-generator php --force
+```
+
+## Options
+
+| Option | Type | Description |
+| ---------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
+| generator | string | The built-in generator to eject. |
+| `--config` | string | The path to the config file. |
+| `--dir` | string | The directory that receives the ejected files. Default: `./generators`. |
+| `--update` | boolean | Do a three-way merge of the current built-in version into your customized copy. The command marks conflicts with standard markers. |
+| `--force` | boolean | Overwrite an existing ejected file and discard the local edits. |
+
+## How it works
+
+The eject operation writes two files:
+
+- `
/.mjs` is the generator itself, as a plain ESM file that you own.
+ The file contains everything that it needs to run standalone.
+ A language generator (`python`, `go`, `php`) is one self-contained file.
+ You get its source exactly as it was written.
+ A TypeScript generator is a thin entry point that uses shared emitters, so you get it bundled together with those emitters.
+ The bundle is not minified, and a comment marks each source module.
+
+ In both cases, the file imports the authoring toolkit from `@redocly/client-generator`.
+ A bundled generator also imports `logger` and `isPlainObject` from `@redocly/openapi-core`, which is a dependency of the toolkit.
+ If your package manager does not hoist dependencies, add `@redocly/openapi-core` explicitly.
+
+- `.claude/skills/-generator/SKILL.md` is the design of the generator, written as an agent skill.
+ The skill records the decisions that the code implements, and the loop to follow when you change the generator.
+ First state the change in the skill, then make the code match.
+ Coding agents load skills automatically, so your agent starts from the design and does not reverse-engineer the code.
+
+The first eject also writes `.claude/skills/client-generators/SKILL.md`, the shared authoring guide.
+The guide describes the generator contract, the API model, and the helper library.
+You can edit the skills, in the same way as the generator.
+The `--update` option does a three-way merge of your skill edits with the newer version.
+A fresh eject or `--force` writes the skills as Redocly ships them.
+
+In addition to the code, the command also writes a short pointer to the skills into `/AGENTS.md`.
+This pointer explains the directory to a reader who has no context.
+The command keeps everything that you add outside the markers in that file.
+
+The eject command also configures your project.
+It adds `@redocly/client-generator` to your `devDependencies` if the package is not there.
+It also points your config at the ejected file: in `client.generators`, the path to your copy replaces the built-in name.
+If the config has no `client.generators` list yet, the command adds one.
+
+```yaml
+client:
+ generators:
+ - ./generators/python.mjs
+```
+
+If you leave the ejected generator unmodified, its output is byte-identical to the output of the built-in generator.
+To roll back, delete the file and the config line.
+
+## Run the ejected generator
+
+Generation is the same command as before the eject, because the config now points at your copy:
+
+```sh
+redocly generate-client openapi.yaml --output src/client.ts
+```
+
+If you did not wire the config, name the file with `--generator`:
+
+```sh
+redocly generate-client openapi.yaml --output src/client.ts --generator ./generators/python.mjs
+```
+
+The command reports a generator that takes over a built-in name, so you can see that your copy is the one that runs.
+Edit the file and run the command again to see the change.
+The eject command prints these instructions as well.
+
+## Update an ejected generator
+
+The `redocly eject-generator --update` command merges a newer version into your copy.
+That version is the one shipped by your installed `@redocly/client-generator` package.
+The three-way merge uses the version recorded in the header of the ejected file as the common ancestor.
+Because of this, you do not have to commit extra files, and there is no snapshot to keep in sync.
+
+The command merges the two skills in the same way, so an update keeps the design notes that you added to them.
+The command marks conflicts with standard `<<<<<<<` markers.
+Resolve the conflicts manually.
+
+An ejected generator continues to operate across CLI upgrades if the authoring contract that it was written against stays compatible.
+The contract follows the `@redocly/client-generator` version.
+A breaking change increases the major version (the minor version, while the package is `0.x`).
+A generator ejected from an incompatible version fails before it runs.
+The error displays the version that the generator expects, the version that you have, and the `--update` command that aligns them.
diff --git a/docs/@v2/commands/generate-client.md b/docs/@v2/commands/generate-client.md
index c9d2471a82..f2f5d41540 100644
--- a/docs/@v2/commands/generate-client.md
+++ b/docs/@v2/commands/generate-client.md
@@ -1,24 +1,62 @@
# `generate-client`
{% admonition type="warning" name="Experimental" %}
-`generate-client` is an experimental feature: its flags, generated output, configuration schema, and custom-generator API may change in any minor release until it's stable.
-We'd love your feedback while we stabilize it.
+`generate-client` is an experimental feature.
+Its flags, generated output, configuration schema, and custom-generator API can change in any minor release until the feature is stable.
+Send us your feedback while we stabilize the feature.
{% /admonition %}
+## Quickstart
+
+Point the command at a description and give it an output path:
+
+```bash
+redocly generate-client openapi.yaml --output src/client.ts
+```
+
+That writes one self-contained file with a typed function for each operation:
+
+```ts
+import { listOrders, createOrder, configure } from './client.js';
+
+configure({ auth: { bearer: process.env.API_TOKEN } });
+
+const orders = await listOrders({ query: { status: 'open', limit: 10 } });
+const created = await createOrder({ body: { items: [{ menuItemId: 'itm_1', quantity: 2 }] } });
+```
+
+The client has no dependencies, and it carries the behavior an API needs: auth for every scheme the description declares, opt-in retries, timeouts, middleware, pagination iterators, and typed server-sent events.
+Add a flag for each extra artifact you want:
+
+```bash
+redocly generate-client openapi.yaml -o src/client.ts \
+ --generator zod --generator tanstack-query --generator mock --docs
+```
+
+The rest of this page describes the flags.
+[Use the generated client](../guides/use-generated-client.md) describes what the output does.
+
## Introduction
The `generate-client` command generates a typed TypeScript client from an OpenAPI 3.x description.
-Swagger 2.0 descriptions are also accepted and normalized to the 3.x shape before generation.
-The description is validated first: unresolved `$ref`s or structural errors fail generation with the problems listed, independent of your lint configuration.
+The command also accepts Swagger 2.0 descriptions and normalizes them to the 3.x shape before generation.
+The command validates the description first.
+If the description has unresolved `$ref`s or structural errors, the command stops the generation and lists the problems.
+This validation does not depend on your lint configuration.
-The generated client has zero runtime dependencies by default — it uses only web-standard APIs (`fetch`, `AbortController`, `URLSearchParams`), so it runs in browsers, Node, Bun, Deno, and edge runtimes.
-By default it emits a single self-contained file with inline types and one async function per operation.
+By default, the generated client has zero runtime dependencies.
+The client uses only web-standard APIs (`fetch`, `AbortController`, `URLSearchParams`).
+Because of this, the client runs in browsers, Node, Bun, Deno, and edge runtimes.
+By default, the command writes one self-contained file with inline types and one async function for each operation.
-The `` argument is a file path, a URL, or an [`apis:` alias](../configuration/index.md), resolved the same way as in other commands such as `bundle` and `lint`.
-An alias, or a path matching an api's `root`, uses that api's `client` block and `clientOutput`; an unmatched path or URL uses the top-level `client` defaults.
-With no argument, a client is generated for every api that declares a `client` block or a `clientOutput` (see [`client` configuration](../configuration/reference/client.md)).
+The `` argument is a file path, a URL, or an [`apis:` alias](../configuration/index.md).
+The command resolves the argument in the same way as other commands, for example `bundle` and `lint`.
+An alias, or a path that matches the `root` of an api, uses the `client` block and the `clientOutput` of that api.
+An unmatched path or URL uses the top-level `client` defaults.
+If you give no argument, the command generates a client for each api that declares a `client` block or a `clientOutput` (see [`client` configuration](../configuration/reference/client.md)).
-This page covers running the command; for the generated client's runtime API (auth, error handling, middleware, retries, and the add-on generators), see [Use the generated client](../guides/use-generated-client.md).
+This page tells you how to run the command.
+For the runtime API of the generated client (auth, error handling, middleware, retries, and the add-on generators), see [Use the generated client](../guides/use-generated-client.md).
## Usage
@@ -33,37 +71,42 @@ redocly generate-client [--help] [--version]
## Options
-| Option | Type | Description |
-| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `api` | string | OpenAPI description file path, URL, or an `apis:` alias. Omit it to generate for every api that has a `client` block or `clientOutput`. |
-| `--output`, `-o` | string | Output path (must end in `.ts`); the entry file in multi-file modes. Defaults to the api's `clientOutput`, else `.client.ts` next to the configuration file. Single-API invocations only. |
-| `--output-mode` | string | File layout. See [Choose an output mode](#choose-an-output-mode).
**Possible values:** `single`, `split`. Default value is `single`. |
-| `--runtime` | string | Where the client's engine lives. See [Choose a runtime](#choose-a-runtime).
**Possible values:** `inline`, `package`. Default value is `inline`. |
-| `--import-ext` | string | Extension in generated relative imports. See [Run with Node directly](../guides/use-generated-client.md#run-with-node-directly).
**Possible values:** `js` (the tsc/bundler convention), `ts` (for Node's built-in type stripping). Default value is `js`. |
-| `--generator` | [string] | Generator to run — a built-in name (`tanstack-query` also has `-vue`/`-svelte`/`-solid` variants) or a custom generator's path or package; repeat the flag to run several. Default value is `sdk`. See [Generators](../guides/use-generated-client.md#generators). |
-| `--args-style` | string | How operation inputs are passed. See [Argument style](../guides/use-generated-client.md#argument-style).
**Possible values:** `flat`, `grouped`. Default value is `flat`. |
-| `--error-mode` | string | How operations report HTTP errors. See [Error handling](../guides/use-generated-client.md#error-handling).
**Possible values:** `throw`, `result`. Default value is `throw`. |
-| `--date-type` | string | Type of `date`/`date-time` fields; pair `Date` with the `transformers` generator.
**Possible values:** `string`, `Date`. Default value is `string`. |
-| `--mock-data` | string | Data mode for the `mock` generator.
**Possible values:** `static` (deterministic literals), `faker` (`@faker-js/faker` calls). Default value is `static`. |
-| `--mock-seed` | number | Seed for `faker`-mode mocks, for reproducible data. Ignored in `static` mode. |
-| `--server-url` | string | Override the server URL included in the client as its default. Accepts an absolute (`https://api.example.com`) or relative (`/v1`) URL. Defaults to `servers[0].url`. The app can also repoint the client at runtime — `createClient({ serverUrl })` or `configure({ serverUrl })`, see [Authentication](../guides/use-generated-client.md#authentication) in the usage guide. |
-| `--setup` | string | Path to a publisher setup module that gets included in the client — pre-configure defaults such as the server URL, retries, headers, and middleware, so a published SDK ships with them built in. See [Publisher defaults](../guides/customize-client-generation.md#publisher-defaults). |
-| `--config` | string | Specify path to the [configuration file](#generate-from-the-configuration-file). |
-| `--help` | boolean | Show help. |
-| `--version` | boolean | Show version number. |
+| Option | Type | Description |
+| ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `api` | string | The file path to the OpenAPI description, a URL, or an `apis:` alias. Omit it to generate a client for each api that has a `client` block or `clientOutput`. |
+| `--output`, `-o` | string | The output path (it must end in `.ts`). In multi-file modes, this is the entry file. Defaults to the `clientOutput` of the api, else `.client.ts` next to the configuration file. Use this option only when you generate one API. |
+| `--output-mode` | string | The file layout. See [Choose an output mode](#choose-an-output-mode).
**Possible values:** `single`, `split`. Default: `single`. |
+| `--runtime` | string | The location of the client engine. See [Choose a runtime](#choose-a-runtime).
**Possible values:** `inline`, `package`. Default: `inline`. |
+| `--import-ext` | string | The extension in the generated relative imports. See [Run with Node directly](../guides/use-generated-client.md#run-with-node-directly).
**Possible values:** `js` (the tsc/bundler convention), `ts` (for Node's built-in type stripping). Default: `js`. |
+| `--generator` | [string] | The generator to run: a built-in name, or the path or package of a custom generator. Repeat the flag to run more than one generator. Default value is `typescript`. See [Generators](../guides/use-generated-client.md#generators) for the full list. |
+| `--args-style` | string | Sets how you pass inputs to operations. See [Argument style](../guides/use-generated-client.md#argument-style).
**Possible values:** `grouped`, `flat`. Default: `grouped`. |
+| `--error-mode` | string | Sets how operations report HTTP errors. See [Error handling](../guides/use-generated-client.md#error-handling).
**Possible values:** `throw`, `result`. Default: `throw`. |
+| `--date-type` | string | The type of the `date`/`date-time` fields. If you use `Date`, also use the `transformers` generator.
**Possible values:** `string`, `Date`. Default: `string`. |
+| `--mock-data` | string | The data mode for the `mock` generator.
**Possible values:** `static` (deterministic literals), `faker` (`@faker-js/faker` calls). Default: `static`. |
+| `--mock-seed` | number | The seed for `faker`-mode mocks. Use it to get reproducible data. The command ignores it in `static` mode. |
+| `--server-url` | string | Overrides the default server URL in the client. The option accepts an absolute URL (`https://api.example.com`) or a relative URL (`/v1`). Defaults to `servers[0].url`. The app can also change the server URL at runtime with `createClient({ serverUrl })` or `configure({ serverUrl })`. See [Authentication](../guides/use-generated-client.md#authentication) in the usage guide. |
+| `--setup` | string | The path to a publisher setup module that the command includes in the client. Use it to pre-configure defaults, for example the server URL, retries, headers, and middleware. A published SDK then contains these defaults. See [Publisher defaults](../guides/customize-client-generation.md#publisher-defaults). |
+| `--docs` | boolean | Also write the reference documentation for what this run generates: one Markdown page for each selected generator that documents itself (the CLI, and each SDK). Default value is `false`. |
+| `--go-package` | string | The package clause in the output of the `go` generator. It must be a valid Go package name (lowercase letters, digits, and `_`; it must not start with a digit or be a keyword). Default value is `client`. |
+| `--config` | string | Specify the path to the [configuration file](#generate-from-the-configuration-file). |
+| `--help` | boolean | Display help. |
+| `--version` | boolean | Display version number. |
## Examples
### Generate from the configuration file
-Instead of passing flags every time, keep the settings in `redocly.yaml` under a top-level `client` block and per-API `apis..client` / `clientOutput` — see the [`client` configuration reference](../configuration/reference/client.md) for the fields.
+You do not have to pass flags each time.
+Keep the settings in `redocly.yaml` under a top-level `client` block and per-API `apis..client` / `clientOutput`.
+See the [`client` configuration reference](../configuration/reference/client.md) for the fields.
CLI flags take precedence over the configuration.
-Auto-pagination has no CLI flag; it's declared only as [`client.pagination`](../configuration/reference/client.md#pagination-object) configuration or the `x-redocly-pagination` operation extension.
+Auto-pagination has no CLI flag.
+Declare it only as the [`client.pagination`](../configuration/reference/client.md#pagination-object) configuration or the `x-redoclyPagination` operation extension.
```yaml
client:
generators:
- - sdk
+ - typescript
apis:
cafe:
root: ./openapi.yaml
@@ -77,7 +120,8 @@ redocly generate-client cafe # just the `cafe` api
### Generate from a file path or URL
-An unmatched path or URL uses the top-level `client` defaults; `--output` names the entry file:
+An unmatched path or URL uses the top-level `client` defaults.
+The `--output` flag names the entry file:
```bash
redocly generate-client openapi.yaml --output dist/client.ts
@@ -85,10 +129,13 @@ redocly generate-client openapi.yaml --output dist/client.ts
### Choose an output mode
-`--output-mode` controls how the client is split across files:
+The `--output-mode` flag controls how the command splits the client into files:
-- `single` (default) — one file (self-contained with the default `inline` runtime).
-- `split` — two files: the schema types and type guards move to a sibling `.schemas.ts`, and the entry file re-exports them, so your imports are the same as in `single`.
+- `single` (default): the command writes one file.
+ The file is self-contained with the default `inline` runtime.
+- `split`: the command writes two files.
+ It puts the schema types and the type guards in a sibling file, `.schemas.ts`. The entry file re-exports them.
+ Because of this, your imports are the same as in `single`.
```bash
redocly generate-client openapi.yaml -o src/api/client.ts --output-mode split
@@ -98,18 +145,23 @@ Both modes work with both runtimes.
### Choose a runtime
-`--runtime` controls where the client's engine (request building, auth, retries, middleware, SSE) lives:
+The `--runtime` flag controls the location of the client engine (request building, auth, retries, middleware, SSE):
-- `inline` (default) — the runtime source is embedded in the generated output (only the parts your API needs): self-contained, zero runtime dependencies.
-- `package` — the generated file imports the runtime from `@redocly/client-generator` and contains only the types, operation descriptors, and thin call wrappers.
+- `inline` (default): the command embeds the runtime source in the generated output.
+ It embeds only the parts that your API needs.
+ The output is self-contained and has zero runtime dependencies.
+- `package`: the generated file imports the runtime from `@redocly/client-generator`.
+ The file contains only the types, the operation descriptors, and thin call wrappers.
-Choose `package` when you want engine fixes to arrive via `npm update @redocly/client-generator` with no regeneration; the consuming app must then have that package installed as a regular dependency.
-Your application code is identical in both modes.
+Choose `package` if you want to get engine fixes with `npm update @redocly/client-generator` and no regeneration.
+In this mode, the app that uses the client must install that package as a regular dependency.
+Your application code is the same in both modes.
See [Package runtime](../guides/use-generated-client.md#package-runtime) in the usage guide and the [`package-runtime` example](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/package-runtime).
## Resources
-- [Use the generated client](../guides/use-generated-client.md) — the runtime API and the add-on generators.
-- [`client` configuration](../configuration/reference/client.md) — the `redocly.yaml` `client` block.
-- [Lint command](./lint.md) to validate your API description before generating a client.
-- [Bundle command](./bundle.md) to combine a multi-file description into a single input file.
+- **[Use the generated client](../guides/use-generated-client.md)** - Learn how to use the client produced by the `generate-client` command
+- **[Move an app to a generated client](../guides/migrate-to-generated-client.md)** - Replace a hand-written client, one call site at a time
+- **[`client` configuration](../configuration/reference/client.md)** - Explore the settings for the `generate-client` command
+- **[Lint command](./lint.md)** - Validate your API description before you generate a client
+- **[Bundle command](./bundle.md)** - Combine a multi-file description into one input file
diff --git a/docs/@v2/commands/index.md b/docs/@v2/commands/index.md
index 3d4107239e..8688db9a70 100644
--- a/docs/@v2/commands/index.md
+++ b/docs/@v2/commands/index.md
@@ -9,21 +9,22 @@ Documentation commands:
- [`preview`](preview.md) Start a local preview of a Redocly project with one of the product NPM packages.
- [`translate`](translate.md) Generate translation keys for a Redocly Realm, Reef, or Revel project.
- [`eject`](eject.md) Eject and modify components from the core theme in a Redocly Realm, Reef, or Revel project.
-- [`build-docs`](build-docs.md) Build API description into an HTML file.
+- [`build-docs`](build-docs.md) Build an API description into an HTML file.
API management commands:
-- [`bundle`](bundle.md) Bundle API description.
+- [`bundle`](bundle.md) Bundle an API description.
- [`generate-client`](generate-client.md) Generate a typed TypeScript client from an OpenAPI description [experimental feature].
+- [`eject-generator`](eject-generator.md) Copy a built-in client generator into your repository as an editable file [experimental feature].
- [`join`](join.md) Join API descriptions [experimental feature].
- [`score`](score.md) Score an API for integration simplicity and AI agent readiness.
-- [`split`](split.md) Split API description into a multi-file structure.
+- [`split`](split.md) Split an API description into a multi-file structure.
- [`stats`](stats.md) Gather statistics for a document.
Linting commands:
-- [`lint`](lint.md) Lint API description.
-- [`check-config`](check-config.md) Lint Redocly configuration file.
+- [`lint`](lint.md) Lint an API description.
+- [`check-config`](check-config.md) Lint the Redocly configuration file.
Testing commands:
@@ -46,11 +47,13 @@ Supporting commands:
## Additional options
-There are some parameters supported by all commands:
+All commands support these parameters:
-`--version` display the current version of `redocly`.
+`--version` displays the current version of `redocly`.
-`--help` display the command help, or the help for the subcommand if you used one. For example:
+`--help` displays the help for the command.
+If you used a subcommand, it displays the help for that subcommand.
+For example:
```bash
npx @redocly/cli@latest lint --help
@@ -60,13 +63,15 @@ Try these with any of the other commands.
## Config file
-Redocly CLI comes with one primary configuration file (`redocly.yaml`), also known as the Redocly configuration file.
-This file defines all of the config options available to you, including the location of your files (for unbundling and bundling), and linting rules (for validation against the OpenAPI Specification).
+Redocly CLI has one primary configuration file (`redocly.yaml`), also called the Redocly configuration file.
+This file defines all of the configuration options available to you.
+These options include the location of your files (for unbundling and bundling) and the linting rules (for validation against the OpenAPI Specification).
-The Redocly configuration file must sit in your root directory.
-If Redocly CLI finds `redocly.yaml` in the root directory, it uses the options set in that file when executing commands.
+The Redocly configuration file must be in your root directory.
+If Redocly CLI finds `redocly.yaml` in the root directory, it uses the options set in that file when it executes commands.
-You can also specify a config file to most commands using `--config myconfig.yaml` as part of the command. For example:
+For most commands, you can also specify a configuration file with `--config myconfig.yaml` as part of the command.
+For example:
```bash
npx @redocly/cli@latest lint --config redocly-official.yaml openapi.yaml
diff --git a/docs/@v2/configuration/reference/client.md b/docs/@v2/configuration/reference/client.md
index da8a7cebc9..ecfa9b3db5 100644
--- a/docs/@v2/configuration/reference/client.md
+++ b/docs/@v2/configuration/reference/client.md
@@ -2,80 +2,99 @@
## Introduction
-The `client` configuration provides settings for the [`generate-client`](../../commands/generate-client.md) command.
-The block can be used at the root of the configuration file, where it holds defaults, and inside an [API-specific section](./apis.md) (`apis..client`), where it overrides the root block for the specific API.
+The `client` configuration contains the settings for the [`generate-client`](../../commands/generate-client.md) command.
+You can put the block at the root of the configuration file, where it holds the defaults.
+You can also put it inside an [API-specific section](./apis.md) (`apis..client`), where it overrides the root block for that API.
The input and output are not part of the `client` block:
-- **input** — `apis..root`, or a path or alias passed on the command line.
-- **output** — `apis..clientOutput`; when omitted it defaults to `.client.ts` next to the configuration file.
- The `--output` flag overrides it for single-API invocations.
+- **input** — `apis..root`, or a path or alias that you give on the command line.
+- **output** — `apis..clientOutput`.
+ If you omit it, the default is `.client.ts` next to the configuration file.
+ The `--output` flag overrides it when you generate one API.
## Options
-Each scalar option mirrors the matching CLI flag and shares its default — see the [command options](../../commands/generate-client.md#options) for the full description of each value.
-The `pagination` option is config-only — a structured, durable contract that belongs in versioned configuration rather than a shell string.
-For runs without a configuration file, declare pagination per operation with the `x-redocly-pagination` extension in the description, or pass `pagination` to the programmatic `generateClient(...)`.
-
-| Option | Type | Description |
-| ---------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `generators` | [string] | Generators to run, in order. Each entry is a built-in name (`sdk`, `zod`, `tanstack-query` — or its `-vue`/`-svelte`/`-solid` variants — `swr`, `mock`, `transformers`) or a custom generator's path or package name. |
-| `outputMode` | string | File layout: `single` or `split`. |
-| `runtime` | string | Runtime distribution: `inline` or `package`. |
-| `importExt` | string | Extension in generated relative imports: `js` (default, for tsc and bundlers) or `ts` (for Node's built-in type stripping). |
-| `argsStyle` | string | How operation inputs are passed: `flat` or `grouped`. |
-| `errorMode` | string | How operations report HTTP errors: `throw` or `result`. |
-| `dateType` | string | Type of `date`/`date-time` fields: `string` or `Date`. |
-| `mockData` | string | Data mode for the `mock` generator: `static` or `faker`. |
-| `mockSeed` | number | Seed for `faker`-mode mocks. |
-| `queryKeyPrefix` | string | Leading element for every `tanstack-query` query/mutation key — namespaces the cache when several generated APIs share one QueryClient. Config-only, no flag. |
-| `serverUrl` | string | Server URL included in the client as its default; falls back to `servers[0].url`. |
-| `setup` | string | Path to a publisher setup module that gets included in the client — pre-configures defaults such as the server URL, retries, headers, and middleware. See [Publisher defaults](../../guides/customize-client-generation.md#publisher-defaults). |
-| `pagination` | [Pagination object](#pagination-object) | Declares how the API paginates, so paginated operations gain typed `.pages()`/`.items()` async iterators. |
+Each scalar option matches the related CLI flag and has the same default.
+See the [command options](../../commands/generate-client.md#options) for the full description of each value.
+The `pagination` option is available only in the configuration file.
+It is a structured, durable contract that belongs in versioned configuration, not in a shell string.
+If you run without a configuration file, declare pagination for each operation with the `x-redoclyPagination` extension in the description.
+As an alternative, pass `pagination` to the programmatic `generateClient(...)`.
+
+| Option | Type | Description |
+| ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `generators` | [string] | The generators to run, in order. Each entry is a built-in name (`typescript`, `zod`, `tanstack-query` or its `-vue`/`-svelte`/`-solid` variants, `swr`, `mock`, `transformers`, `cli`, `python`, `go`, `php`), or the path or package name of a custom generator. |
+| `outputMode` | string | The file layout: `single` or `split`. This option applies to TypeScript output only. The `python`, `go`, and `php` SDKs always emit one self-contained file. |
+| `runtime` | string | The runtime distribution: `inline` or `package`. This option applies to TypeScript output only. The `python`, `go`, and `php` SDKs always embed their runtime. |
+| `importExt` | string | The extension in generated relative imports: `js` (default, for tsc and bundlers) or `ts` (for Node's built-in type stripping). This option applies to TypeScript output only. |
+| `argsStyle` | string | How the client receives operation inputs: `grouped` (default) groups them by transport layer (`path`, `query`, `headers`, `cookies`, `body`), and `flat` merges them into one object. This option applies to TypeScript output only. Each language SDK follows its own idiom (keyword arguments, named arguments, a params struct). |
+| `errorMode` | string | How operations report HTTP errors: `throw` or `result`. The `python` SDK implements both. The `go` and `php` SDKs support only `throw`, because that is the language idiom, and they reject `result`. |
+| `dateType` | string | The type of `date`/`date-time` fields: `string` or `Date`. Every language applies it: `Date` in TypeScript, `datetime`/`date` in Python, `time.Time`/`Date` in Go, `DateTimeImmutable` in PHP. |
+| `mockData` | string | The data mode for the `mock` generator: `static` or `faker`. |
+| `mockSeed` | number | The seed for mocks in `faker` mode. |
+| `queryKeyPrefix` | string | The first element of every `tanstack-query` query key and mutation key. It separates the cache entries when several generated APIs share one QueryClient. This option is available only in the configuration file and has no flag. |
+| `codeSamples` | boolean | Emit `