{"templateId":"markdown","versions":[{"version":"v1","label":"1.x (archive)","link":"/docs/cli/v1/guides/customize-client-generation","default":false,"active":false,"folderId":"6f4800fc"},{"version":"v2","label":"2.x (current)","link":"/docs/cli/guides/customize-client-generation","default":true,"active":true,"folderId":"6f4800fc"}],"sharedDataIds":{"sidebar":"sidebar-docs-cli.sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"redocly_category":"Redocly CLI","type":"markdown"},"seo":{"title":"Customize client generation","description":"OpenAPI-generated documentation tool with 24k+ stars on Github - make APIs your company's superpower.","siteUrl":"https://redocly.com","image":"/assets/redocly-card.f670aae34a39545a5ea633a540cb3a4a333a1f23bb2ed3c4a1b17a5fbcf0ac85.db81178d.png","lang":"en-US"},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"customize-client-generation","__idx":0},"children":["Customize client generation"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["How to shape what ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/commands/generate-client"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["generate-client"]}]}," produces — pre-configured publisher defaults and custom generators."," ","This page is for the person who ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["runs the generator"]}," (an SDK publisher, a platform team); for consuming the generated client, see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/guides/use-generated-client"},"children":["Use the generated client"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"publisher-defaults","__idx":1},"children":["Publisher defaults"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Middleware and configuration are normally composed by the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/guides/use-generated-client#middleware"},"children":["consumer"]},"."," ","If you ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["publish an SDK"]}," you can pre-configure the client at generation time with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["--setup <file>"]},": defaults such as the server URL, retries, headers, and middleware are included in the generated client, so the SDK ships with them built in."," ","Setup changes the client's built-in ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["behavior"]},"; it emits no extra file — to derive additional artifacts from the description, use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/guides/use-generated-client#generators"},"children":["generators"]}," instead."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A setup module is a plain file that default-exports a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ config, middleware }"]}," object — no imports required:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"// client-setup.ts\nexport default {\n  config: { serverUrl: 'https://api.acme.com', retry: { retries: 3 } },\n  middleware: [\n    {\n      onRequest: (ctx) => {\n        ctx.headers['X-Acme-SDK'] = '1.4.0';\n      },\n    },\n  ],\n};\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"redocly generate-client openapi.yaml --output src/api/client.ts --setup ./client-setup.ts\n","lang":"sh"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Inclusion is a generation-time transform: only the setup expression lands in the client, so an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inline"]}," client stays zero-dependency, and the included block is typed against the client's own contract in the generated file — a shape mistake fails the consumer's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tsc"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For editor autocomplete while authoring, optionally wrap the object in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["defineClientSetup"]}," — a typing-only helper, stripped at generation time, identical in both runtimes:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"// client-setup.ts — the same setup, typed while editing\nimport { defineClientSetup, type RequestContext } from '@redocly/client-generator';\n\nexport default defineClientSetup({\n  config: { serverUrl: 'https://api.acme.com', retry: { retries: 3 } },\n  middleware: [\n    {\n      onRequest: (ctx: RequestContext) => {\n        ctx.headers['X-Acme-SDK'] = '1.4.0';\n      },\n    },\n  ],\n});\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The pre-configured block runs before the consumer's own setup."," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Config values"]}," layer lowest to highest — later always wins, so a consumer overrides a pre-configured default:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The description's defaults (for example ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["servers[0].url"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The publisher setup."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The app's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["configure()"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Middleware composes"]}," instead (publisher middleware first, then the consumer's)."," ","Express un-bypassable behavior as middleware, not a custom ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]},"."," ","A setup file may import ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["only"]}," from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@redocly/client-generator"]},"."," ","See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/baked-setup"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["baked-setup"]}," example"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"custom-generators","__idx":2},"children":["Custom generators"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The built-in generators cover common targets."," ","For anything else derived from the same description (validators in another library, a permissions map, a house-style SDK), write a ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["custom generator"]},": it reads the same API model the built-ins consume, so its output never drifts from the description."," ","A generator adds artifacts ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["next to"]}," the client — it doesn't change the generated client's behavior; for that, use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#publisher-defaults"},"children":["publisher defaults"]}," or let the consumer compose ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/guides/use-generated-client#middleware"},"children":["middleware"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A generator is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ name, run }"]}," (plus optional compatibility metadata); author it with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["defineGenerator"]}," from the package root, and build real TypeScript with the emit toolkit from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@redocly/client-generator/generate"]}," — the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ts.factory"]}," + printer the built-in generators use, so the schema→type mapping matches the sdk's exactly:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"// response-map-generator.ts\nimport { defineGenerator } from '@redocly/client-generator';\nimport { printStatements, schemaToTypeNode, ts } from '@redocly/client-generator/generate';\n\nconst { factory } = ts;\n\nexport default defineGenerator({\n  name: 'response-map',\n  requires: ['sdk'],\n  run({ model, outputPath }) {\n    // One `ResponseShapes` entry per operation with a JSON success body.\n    const members = model.services\n      .flatMap((service) => service.operations)\n      .flatMap((op) => {\n        const success = op.successResponses.find((r) => r.contentType.includes('json'));\n        if (!success) return [];\n        return [\n          factory.createPropertySignature(\n            undefined,\n            op.name,\n            undefined,\n            schemaToTypeNode(success.schema)\n          ),\n        ];\n      });\n    const alias = factory.createTypeAliasDeclaration(\n      [factory.createModifier(ts.SyntaxKind.ExportKeyword)],\n      'ResponseShapes',\n      undefined,\n      factory.createTypeLiteralNode(members)\n    );\n    return [\n      { path: outputPath.replace(/\\.ts$/, '.responses.ts'), content: printStatements([alias]) },\n    ];\n  },\n});\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The toolkit exports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ts"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["printStatements"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["parseStatements"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationSignature"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["schemaToTypeNode"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pascalCase"]},", and more; the package root exports the model (IR) types."," ","For a trivial artifact, returning a plain string as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["content"]}," works too — no toolkit required."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Select a generator in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]}," by path or package name:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"apis:\n  cafe:\n    root: ./openapi.yaml\n    clientOutput: ./src/api/client.ts\n    client:\n      generators:\n        - sdk\n        - ./tools/response-map-generator.ts # local path (resolved against redocly.yaml)\n        - '@acme/openapi-valibot' # published package\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Or register one ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["inline"]}," with the programmatic API and select it by name:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import { generateClient } from '@redocly/client-generator';\nimport responseMap from './tools/response-map-generator.ts';\n\nawait generateClient({\n  api: './openapi.yaml',\n  output: './src/api/client.ts',\n  customGenerators: [responseMap],\n  generators: ['sdk', 'response-map'],\n});\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Import-specifier generators execute at generation time — they carry the same trust level as any installed dependency you run."," ","See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/ast-toolkit-generator"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ast-toolkit-generator"]}," example"]}," for the runnable toolkit-based plugin (including type-importing referenced schemas), the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/custom-generator"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["custom-generator"]}," example"]}," for a minimal string-building one, and the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/nested-facade"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["nested-facade"]}," example"]}," for a realistic one that derives an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["api.<resource>.<operation>"]}," facade from the description's tags."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"resources","__idx":3},"children":["Resources"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/commands/generate-client"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["generate-client"]}," command"]}," — flags, output modes, and invocation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/configuration/reference/client"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client"]}," configuration"]}," — the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client"]}," block."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/cli/guides/use-generated-client"},"children":["Use the generated client"]}," — the consumer-side guide."]}]}]},"headings":[{"value":"Customize client generation","id":"customize-client-generation","depth":1},{"value":"Publisher defaults","id":"publisher-defaults","depth":2},{"value":"Custom generators","id":"custom-generators","depth":2},{"value":"Resources","id":"resources","depth":2}],"frontmatter":{"seo":{"title":"Customize client generation"}},"lastModified":"2026-07-30T09:50:35.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/cli/guides/customize-client-generation","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}