···144144The `ofetch` client supports two complementary options:
145145146146- built-in Hey API interceptors exposed via `client.interceptors`
147147-- native `ofetch` hooks passed through config (e.g. `onRequest`)
147147+- native `ofetch` hooks passed through config (e.g., `onRequest`)
148148149149### Example: Request interceptor
150150
+62-59
docs/openapi-ts/configuration/output.md
···36363737You can learn more about complex use cases in the [Advanced](/openapi-ts/configuration#advanced) section.
38383939-## File Name
3939+## File
4040+4141+Control how files are named and annotated in the generated output.
4242+4343+### File Name
40444145You can customize the naming and casing pattern for files using the `fileName` option.
4246···108112109113:::
110114111111-## Module Extension
115115+### File Header
112116113113-You can customize the extension used for TypeScript modules.
117117+The generated output includes a notice in every file warning that any modifications will be lost when the files are regenerated. You can customize or disable this notice using the `header` option.
114118115119::: code-group
116120117117-```js [default]
121121+```js [example]
122122+/* eslint-disable */
123123+// This file is auto-generated by @hey-api/openapi-ts
124124+125125+/** ... */
126126+```
127127+128128+<!-- prettier-ignore-start -->
129129+```js [config]
118130export default {
119131 input: 'hey-api/backend', // sign up at app.heyapi.dev
120132 output: {
121121- importFileExtension: undefined, // [!code ++]
133133+ header: (ctx) => [ // [!code ++]
134134+ '/* eslint-disable */', // [!code ++]
135135+ ...ctx.defaultValue, // [!code ++]
136136+ ], // [!code ++]
122137 path: 'src/client',
123138 },
124139};
125140```
141141+<!-- prettier-ignore-end -->
126142127127-```js [disabled]
143143+:::
144144+145145+## Module
146146+147147+Control how module specifiers are generated in the output.
148148+149149+### Module Extension
150150+151151+Set `module.extension` to define the file extension used in import specifiers. This is useful when targeting environments that require fully specified imports (e.g., Node ESM or certain bundlers).
152152+153153+::: code-group
154154+155155+```js [example]
156156+import foo from './foo.js';
157157+import bar from './bar.js';
158158+```
159159+160160+```js [config]
128161export default {
129162 input: 'hey-api/backend', // sign up at app.heyapi.dev
130163 output: {
131131- importFileExtension: null, // [!code ++]
164164+ module: {
165165+ extension: '.js', // [!code ++]
166166+ },
132167 path: 'src/client',
133168 },
134169};
135170```
136171137137-```js [js]
138138-export default {
139139- input: 'hey-api/backend', // sign up at app.heyapi.dev
140140- output: {
141141- importFileExtension: '.js', // [!code ++]
142142- path: 'src/client',
143143- },
144144-};
172172+:::
173173+174174+### Module Path
175175+176176+Use `module.resolve` for full control over how module specifiers are generated. This lets you override specific modules or redirect them to custom locations (e.g., CDNs or internal aliases).
177177+178178+::: code-group
179179+180180+```js [example]
181181+import * as z from 'https://esm.sh/zod';
145182```
146183147147-```js [ts]
184184+<!-- prettier-ignore-start -->
185185+```js [config]
148186export default {
149187 input: 'hey-api/backend', // sign up at app.heyapi.dev
150188 output: {
151151- importFileExtension: '.ts', // [!code ++]
189189+ module: {
190190+ resolve(path) { // [!code ++]
191191+ if (path === 'zod') { // [!code ++]
192192+ return 'https://esm.sh/zod'; // [!code ++]
193193+ } // [!code ++]
194194+ }, // [!code ++]
195195+ },
152196 path: 'src/client',
153197 },
154198};
155199```
200200+<!-- prettier-ignore-end -->
156201157202:::
158158-159159-By default, we don't add a file extension and let the runtime resolve it.
160160-161161-```js
162162-import foo from './foo';
163163-```
164164-165165-If we detect a [TSConfig file](#tsconfig-path) with `moduleResolution` option set to `nodenext`, we default the extension to `.js`.
166166-167167-```js
168168-import foo from './foo.js';
169169-```
170203171204## Source
172205···333366export type ChatCompletion = string;
334367335368export type ChatCompletion_N2 = number;
336336-```
337337-338338-:::
339339-340340-## File Header
341341-342342-The generated output includes a notice in every file warning that any modifications will be lost when the files are regenerated. You can customize or disable this notice using the `header` option.
343343-344344-::: code-group
345345-346346-<!-- prettier-ignore-start -->
347347-```js [config]
348348-export default {
349349- input: 'hey-api/backend', // sign up at app.heyapi.dev
350350- output: {
351351- header: [
352352- '/* eslint-disable */', // [!code ++]
353353- '// This file is auto-generated by @hey-api/openapi-ts', // [!code ++]
354354- ],
355355- path: 'src/client',
356356- },
357357-};
358358-```
359359-<!-- prettier-ignore-end -->
360360-361361-```ts [example]
362362-/* eslint-disable */
363363-// This file is auto-generated by @hey-api/openapi-ts
364364-365365-/** ... */
366369```
367370368371:::
+1-1
docs/openapi-ts/migrating.md
···422422423423### Bundle `@hey-api/client-*` plugins
424424425425-In previous releases, you had to install a separate client package to generate a fully working output, e.g. `npm install @hey-api/client-fetch`. This created a few challenges: getting started was slower, upgrading was sometimes painful, and bundling too. Beginning with v0.73.0, all Hey API clients are bundled by default and don't require installing any additional dependencies. You can remove any installed client packages and re-run `@hey-api/openapi-ts`.
425425+In previous releases, you had to install a separate client package to generate a fully working output, e.g., `npm install @hey-api/client-fetch`. This created a few challenges: getting started was slower, upgrading was sometimes painful, and bundling too. Beginning with v0.73.0, all Hey API clients are bundled by default and don't require installing any additional dependencies. You can remove any installed client packages and re-run `@hey-api/openapi-ts`.
426426427427```sh
428428npm uninstall @hey-api/client-fetch
+1-1
docs/openapi-ts/plugins/concepts/resolvers.md
···130130131131### Replace default base
132132133133-You might want to replace the default base schema, e.g. `v.object()`.
133133+You might want to replace the default base schema, e.g., `v.object()`.
134134135135```js
136136export const vUser = v.object({
+3-3
docs/openapi-ts/plugins/pinia-colada.md
···60606161## Queries
62626363-Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Query`, e.g. `getPetByIdQuery()`.
6363+Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Query`, e.g., `getPetByIdQuery()`.
64646565::: code-group
6666···191191192192:::
193193194194-Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g. `getPetByIdQueryKey()`.
194194+Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g., `getPetByIdQueryKey()`.
195195196196::: code-group
197197···223223224224## Mutations
225225226226-Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g. `addPetMutation()`.
226226+Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g., `addPetMutation()`.
227227228228::: code-group
229229
+5-5
docs/openapi-ts/plugins/tanstack-query.md
···115115116116## Queries
117117118118-Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Options`, e.g. `getPetByIdOptions()`.
118118+Queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations). The generated query functions follow the naming convention of SDK functions and by default append `Options`, e.g., `getPetByIdOptions()`.
119119120120::: code-group
121121···281281282282:::
283283284284-Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g. `getPetByIdQueryKey()`.
284284+Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `QueryKey`, e.g., `getPetByIdQueryKey()`.
285285286286::: code-group
287287···313313314314## Infinite Queries
315315316316-Infinite queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations) if we detect a [pagination](/openapi-ts/configuration/parser#pagination) parameter. The generated infinite query functions follow the naming convention of SDK functions and by default append `InfiniteOptions`, e.g. `getFooInfiniteOptions()`.
316316+Infinite queries are generated from [query operations](/openapi-ts/configuration/parser#hooks-query-operations) if we detect a [pagination](/openapi-ts/configuration/parser#pagination) parameter. The generated infinite query functions follow the naming convention of SDK functions and by default append `InfiniteOptions`, e.g., `getFooInfiniteOptions()`.
317317318318::: code-group
319319···483483484484:::
485485486486-Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `InfiniteQueryKey`, e.g. `getPetByIdInfiniteQueryKey()`.
486486+Alternatively, you can access the same query key by calling query key functions. The generated query key functions follow the naming convention of SDK functions and by default append `InfiniteQueryKey`, e.g., `getPetByIdInfiniteQueryKey()`.
487487488488::: code-group
489489···515515516516## Mutations
517517518518-Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g. `addPetMutation()`.
518518+Mutations are generated from [mutation operations](/openapi-ts/configuration/parser#hooks-mutation-operations). The generated mutation functions follow the naming convention of SDK functions and by default append `Mutation`, e.g., `addPetMutation()`.
519519520520::: code-group
521521
+1-1
docs/openapi-ts/plugins/transformers.md
···21212222Transformers handle only the most common scenarios. Some of the known limitations are:
23232424-- union types are not transformed (e.g. if you have multiple possible response shapes)
2424+- union types are not transformed (e.g., if you have multiple possible response shapes)
2525- only types defined through `$ref` are transformed
2626- error responses are not transformed
2727
···11import type { BaseOutput, BaseUserOutput, UserPostProcessor } from '@hey-api/shared';
22-import type { AnyString } from '@hey-api/types';
3243import type { PostProcessorPreset } from './postprocess';
5466-type ImportFileExtensions = '.py';
77-88-export type UserOutput = BaseUserOutput & {
99- /**
1010- * If specified, this will be the file extension used when importing
1111- * other modules. By default, we don't add a file extension and let the
1212- * runtime resolve it. If you're using moduleResolution `nodenext` or
1313- * `node16`, we default to `.js`.
1414- *
1515- * @default undefined
1616- */
1717- importFileExtension?: ImportFileExtensions | AnyString | null;
55+export type UserOutput = BaseUserOutput<'.py'> & {
186 /**
197 * Post-processing commands to run on the output folder, executed in order.
208 *
···3523 preferExportAll?: boolean;
3624};
37253838-export type Output = BaseOutput & {
3939- /**
4040- * If specified, this will be the file extension used when importing
4141- * other modules. By default, we don't add a file extension and let the
4242- * runtime resolve it. If you're using moduleResolution `nodenext` or
4343- * `node16`, we default to `.js`.
4444- */
4545- importFileExtension: ImportFileExtensions | AnyString | null | undefined;
2626+export type Output = BaseOutput<'.py'> & {
4627 /**
4728 * Whether `export * from 'module'` should be used when possible
4829 * instead of named exports.
···4455import type { Formatters, Linters, PostProcessorPreset } from './postprocess';
6677-type ImportFileExtensions = '.js' | '.ts';
88-99-export type UserOutput = BaseUserOutput & {
77+export type UserOutput = BaseUserOutput<'.js' | '.ts'> & {
108 /**
119 * Which formatter to use to process output folder?
1210 *
···2119 * `node16`, we default to `.js`.
2220 *
2321 * @default undefined
2222+ * @deprecated Use `module.extension` instead.
2423 */
2525- importFileExtension?: ImportFileExtensions | AnyString | null;
2424+ importFileExtension?: '.js' | '.ts' | AnyString | null;
2625 /**
2726 * Which linter to use to process output folder?
2827 *
···6059 tsConfigPath?: AnyString | null;
6160};
62616363-export type Output = BaseOutput & {
6262+export type Output = BaseOutput<'.js' | '.ts'> & {
6463 /**
6564 * Which formatter to use to process output folder?
6665 */
6766 format: Formatters | null;
6868- /**
6969- * If specified, this will be the file extension used when importing
7070- * other modules. By default, we don't add a file extension and let the
7171- * runtime resolve it. If you're using moduleResolution `nodenext` or
7272- * `node16`, we default to `.js`.
7373- */
7474- importFileExtension: ImportFileExtensions | AnyString | null | undefined;
7567 /**
7668 * Which linter to use to process output folder?
7769 */
···11import type { RenderContext, Renderer } from '@hey-api/codegen-core';
22-import type { ResolveModuleName } from '@hey-api/shared';
22+import type { BaseOutput } from '@hey-api/shared';
33import type { MaybeArray, MaybeFunc } from '@hey-api/types';
44import ts from 'typescript';
55···3737 */
3838 private _header?: HeaderArg;
3939 /**
4040- * Whether `export * from 'module'` should be used when possible instead of named exports.
4141- *
4242- * @private
4343- */
4444- private _preferExportAll: boolean;
4545- /**
4646- * Controls whether imports/exports include a file extension (e.g., '.ts' or '.js').
4040+ * Options for module specifier resolution.
4741 *
4842 * @private
4943 */
5050- private _preferFileExtension: string;
4444+ private _module?: Partial<BaseOutput>['module'];
5145 /**
5252- * Optional function to transform module specifiers.
4646+ * Whether `export * from 'module'` should be used when possible instead of named exports.
5347 *
5448 * @private
5549 */
5656- private _resolveModuleName?: ResolveModuleName;
5050+ private _preferExportAll: boolean;
57515852 constructor(
5959- args: {
5353+ args: Pick<Partial<BaseOutput>, 'module'> & {
6054 header?: HeaderArg;
6155 preferExportAll?: boolean;
6262- preferFileExtension?: string;
6363- resolveModuleName?: ResolveModuleName;
6456 } = {},
6557 ) {
6658 this._header = args.header;
5959+ this._module = args.module;
6760 this._preferExportAll = args.preferExportAll ?? false;
6868- this._preferFileExtension = args.preferFileExtension ?? '';
6969- this._resolveModuleName = args.resolveModuleName;
7061 }
71627263 render(ctx: RenderContext<TsDsl>): string {
···195186 const sortKey = moduleSortKey({
196187 file: ctx.file,
197188 fromFile: exp.from,
198198- preferFileExtension: this._preferFileExtension,
189189+ preferFileExtension: this._module?.extension || '',
199190 root: ctx.project.root,
200191 });
201201- const modulePath = this._resolveModuleName?.(sortKey[2], ctx) ?? sortKey[2];
192192+ const modulePath = this._module?.resolve?.(sortKey[2], ctx) ?? sortKey[2];
202193 const [groupIndex] = sortKey;
203194204195 if (!groups.has(groupIndex)) groups.set(groupIndex, new Map());
···261252 const sortKey = moduleSortKey({
262253 file: ctx.file,
263254 fromFile: imp.from,
264264- preferFileExtension: this._preferFileExtension,
255255+ preferFileExtension: this._module?.extension || '',
265256 root: ctx.project.root,
266257 });
267267- const modulePath = this._resolveModuleName?.(sortKey[2], ctx) ?? sortKey[2];
258258+ const modulePath = this._module?.resolve?.(sortKey[2], ctx) ?? sortKey[2];
268259 const [groupIndex] = sortKey;
269260270261 if (!groups.has(groupIndex)) groups.set(groupIndex, new Map());
+38-28
packages/shared/src/config/shared.ts
···11import type { NameConflictResolver, RenderContext, Symbol } from '@hey-api/codegen-core';
22-import type { MaybeArray } from '@hey-api/types';
22+import type { AnyString, MaybeArray } from '@hey-api/types';
3344import type { Plugin } from '../plugins/types';
55import type { Logs } from '../types/logs';
···8282/**
8383 * Base output shape all packages must satisfy.
8484 */
8585-export interface BaseUserOutput {
8585+export interface BaseUserOutput<TModuleExtension extends string = string> {
8686 /**
8787 * Defines casing of the output fields. By default, we preserve `input`
8888 * values as data transforms incur a performance penalty at runtime.
···156156 */
157157 indexFile?: boolean;
158158 /**
159159+ * Options for module specifier resolution.
160160+ */
161161+ module?: {
162162+ /**
163163+ * If specified, this will be the extension used when importing other
164164+ * modules. By default, we don't add an extension unless we detect that
165165+ * you're using a module resolution strategy that requires one.
166166+ *
167167+ * @default undefined
168168+ */
169169+ extension?: TModuleExtension | AnyString | null;
170170+ /**
171171+ * Function to transform module specifiers.
172172+ *
173173+ * @default undefined
174174+ */
175175+ resolve?: ResolveModuleFn;
176176+ };
177177+ /**
159178 * Optional name conflict resolver to customize how naming conflicts
160179 * are handled.
161180 */
···168187 * Optional function to transform module specifiers.
169188 *
170189 * @default undefined
190190+ * @deprecated use `module.resolve` instead
171191 */
172172- resolveModuleName?: ResolveModuleName;
192192+ resolveModuleName?: ResolveModuleFn;
173193 /**
174194 * Configuration for generating a copy of the input source used to produce this output.
175195 *
···185205/**
186206 * Base output shape all packages must satisfy.
187207 */
188188-export interface BaseOutput {
208208+export interface BaseOutput<TModuleExtension extends string = string> {
189209 /**
190210 * Defines casing of the output fields. By default, we preserve `input`
191211 * values as data transforms incur a performance penalty at runtime.
···198218 * input, or package version changes.
199219 */
200220 clean: boolean;
201201- /**
202202- * Whether to generate an entry file that re-exports symbols for convenient imports.
203203- */
221221+ /** Whether to generate an entry file that re-exports symbols for convenient imports. */
204222 entryFile: boolean;
205223 /**
206224 * Optional function to transform file names before they are used.
···221239 */
222240 suffix: string | null;
223241 };
224224- /**
225225- * Text to include at the top of every generated file.
226226- */
242242+ /** Text to include at the top of every generated file. */
227243 header: OutputHeader;
228244 /**
229245 * Whether to generate an entry file that re-exports symbols for convenient imports.
···231247 * @deprecated use `entryFile` instead
232248 */
233249 indexFile: boolean;
234234- /**
235235- * Optional name conflict resolver to customize how naming conflicts
236236- * are handled.
237237- */
250250+ /** Options for module specifier resolution. */
251251+ module: {
252252+ /** The extension used when importing other modules. */
253253+ extension: TModuleExtension | AnyString | null;
254254+ /** Function to transform module specifiers. */
255255+ resolve: ResolveModuleFn | undefined;
256256+ };
257257+ /** Name conflict resolver to customize how naming conflicts are handled. */
238258 nameConflictResolver: NameConflictResolver | undefined;
239239- /**
240240- * The absolute path to the output folder.
241241- */
259259+ /** The absolute path to the output folder. */
242260 path: string;
243243- /**
244244- * Post-processing commands to run on the output folder, executed in order.
245245- */
261261+ /** Post-processing commands to run on the output folder, executed in order. */
246262 postProcess: ReadonlyArray<PostProcessor>;
247247- /**
248248- * Optional function to transform module specifiers.
249249- */
250250- resolveModuleName: ResolveModuleName | undefined;
251251- /**
252252- * Configuration for generating a copy of the input source used to produce this output.
253253- */
263263+ /** Configuration for generating a copy of the input source used to produce this output. */
254264 source: SourceConfig;
255265}
256266···350360/**
351361 * Function to transform module specifiers.
352362 */
353353-export type ResolveModuleName = (moduleName: string, ctx: RenderContext) => string | undefined;
363363+export type ResolveModuleFn = (path: string, ctx: RenderContext) => string | undefined;