How to shape what generate-client produces — pre-configured publisher defaults and custom generators. This page is for the person who runs the generator (an SDK publisher, a platform team); for consuming the generated client, see Use the generated client.
Middleware and configuration are normally composed by the consumer. If you publish an SDK you can pre-configure the client at generation time with --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 behavior; it emits no extra file — to derive additional artifacts from the description, use generators instead.
A setup module is a plain file that default-exports a { config, middleware } object — no imports required:
// client-setup.ts
export default {
config: { serverUrl: 'https://api.acme.com', retry: { retries: 3 } },
middleware: [
{
onRequest: (ctx) => {
ctx.headers['X-Acme-SDK'] = '1.4.0';
},
},
],
};redocly generate-client openapi.yaml --output src/api/client.ts --setup ./client-setup.tsInclusion is a generation-time transform: only the setup expression lands in the client, so an 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 tsc.
For editor autocomplete while authoring, optionally wrap the object in defineClientSetup — a typing-only helper, stripped at generation time, identical in both runtimes:
// client-setup.ts — the same setup, typed while editing
import { defineClientSetup, type RequestContext } from '@redocly/client-generator';
export default defineClientSetup({
config: { serverUrl: 'https://api.acme.com', retry: { retries: 3 } },
middleware: [
{
onRequest: (ctx: RequestContext) => {
ctx.headers['X-Acme-SDK'] = '1.4.0';
},
},
],
});The pre-configured block runs before the consumer's own setup. Config values layer lowest to highest — later always wins, so a consumer overrides a pre-configured default:
- The description's defaults (for example
servers[0].url). - The publisher setup.
- The app's
configure().
Middleware composes instead (publisher middleware first, then the consumer's). Express un-bypassable behavior as middleware, not a custom fetch. A setup file may import only from @redocly/client-generator. See the baked-setup example.
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 custom generator: it reads the same API model the built-ins consume, so its output never drifts from the description. A generator adds artifacts next to the client — it doesn't change the generated client's behavior; for that, use publisher defaults or let the consumer compose middleware.
A generator is { name, run } (plus optional compatibility metadata); author it with defineGenerator from the package root, and build real TypeScript with the emit toolkit from @redocly/client-generator/generate — the same ts.factory + printer the built-in generators use, so the schema→type mapping matches the sdk's exactly:
// response-map-generator.ts
import { defineGenerator } from '@redocly/client-generator';
import { printStatements, schemaToTypeNode, ts } from '@redocly/client-generator/generate';
const { factory } = ts;
export default defineGenerator({
name: 'response-map',
requires: ['sdk'],
run({ model, outputPath }) {
// One `ResponseShapes` entry per operation with a JSON success body.
const members = model.services
.flatMap((service) => service.operations)
.flatMap((op) => {
const success = op.successResponses.find((r) => r.contentType.includes('json'));
if (!success) return [];
return [
factory.createPropertySignature(
undefined,
op.name,
undefined,
schemaToTypeNode(success.schema)
),
];
});
const alias = factory.createTypeAliasDeclaration(
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
'ResponseShapes',
undefined,
factory.createTypeLiteralNode(members)
);
return [
{ path: outputPath.replace(/\.ts$/, '.responses.ts'), content: printStatements([alias]) },
];
},
});The toolkit exports ts, printStatements, parseStatements, operationSignature, schemaToTypeNode, pascalCase, and more; the package root exports the model (IR) types. For a trivial artifact, returning a plain string as content works too — no toolkit required.
Select a generator in redocly.yaml by path or package name:
apis:
cafe:
root: ./openapi.yaml
clientOutput: ./src/api/client.ts
client:
generators:
- sdk
- ./tools/response-map-generator.ts # local path (resolved against redocly.yaml)
- '@acme/openapi-valibot' # published packageOr register one inline with the programmatic API and select it by name:
import { generateClient } from '@redocly/client-generator';
import responseMap from './tools/response-map-generator.ts';
await generateClient({
api: './openapi.yaml',
output: './src/api/client.ts',
customGenerators: [responseMap],
generators: ['sdk', 'response-map'],
});Import-specifier generators execute at generation time — they carry the same trust level as any installed dependency you run. See the ast-toolkit-generator example for the runnable toolkit-based plugin (including type-importing referenced schemas), the custom-generator example for a minimal string-building one, and the nested-facade example for a realistic one that derives an api.<resource>.<operation> facade from the description's tags.
generate-clientcommand — flags, output modes, and invocation.clientconfiguration — theredocly.yamlclientblock.- Use the generated client — the consumer-side guide.