Skip to navigation

TypeScript configuration

View as Markdown

You can customize the behavior of the TypeScript SDK generator in generators.yml:

generators.yml
groups:
ts-sdk:
generators:
- name: fern-typescript-sdk
version: 3.95.1
config:
namespaceExport: AcmePayments
noSerdeLayer: false
generateSubpackageExports: true
allowExtraFields
boolean

Allow fields that are not defined in object schemas. This only applies to serde.

See TypeScript serde layer for more information.

auto-generate-idempotency-key
boolean | objectDefaults to false

Overrides the API-wide api.settings.auto-generate-idempotency-key for this SDK. Set true to attach an idempotency-key header to eligible requests (POST and PUT by default) unless the caller provides one, or false to opt this SDK out when auto-generation is enabled API-wide. Pass an object to customize header-name and methods.

defaultTimeout
number | 'infinity'

The default timeout for network requests, in milliseconds. Set to 'infinity' to disable the timeout. In the generated client, this can be overridden at the request level.

enableInlineTypes
booleanDefaults to true

When enabled, the inline schemas will be generated as nested types in TypeScript. This results in cleaner type names and a more intuitive developer experience.

enableInlineTypes: false:

// MyRootType.ts
import * as MySdk from "...";
export interface MyRootType {
foo: MySdk.MyRootTypeFoo;
}
// MyRootTypeFoo.ts
import * as MySdk from "...";
export interface MyRootTypeFoo {
bar: MySdk.MyRootTypeFooBar;
}
// MyRootTypeFooBar.ts
import * as MySdk from "...";
export interface MyRootTypeFooBar {}

enableInlineTypes: true:

// MyRootType.ts
import * as MySdk from "...";
export interface MyRootType {
foo: MyRootType.Foo;
}
export namespace MyRootType {
export interface Foo {
bar: Foo.Bar;
}
export namespace Foo {
export interface Bar {}
}
}

Now users can get the deep nested Bar type as follows:

import { MyRootType } from MySdk;
const bar: MyRootType.Foo.Bar = {};
esmOnly
booleanDefaults to false

Ships only the ECMAScript module (ESM) build of the generated package. By default, the generator publishes both CommonJS and ESM builds, which exposes consumers to the dual package hazard. When enabled, package.json sets "type": "module", main, module, and types point at ./dist/esm/index.mjs and ./dist/esm/index.d.mts, the exports map (including subpackage exports) has no require conditions, and no tsconfig.cjs.json or build:cjs script is generated.

esmOnly can’t be combined with useLegacyExports: true or bundle: true; generation fails with an error.

generators.yml
groups:
ts-sdk:
generators:
- name: fern-typescript-sdk
version: 3.95.1
config:
esmOnly: true
exactOptionalPropertyTypes
booleanDefaults to false

When enabled, every optional property in the generated SDK is emitted as prop?: T | undefined, and the generated tsconfig files enable TypeScript’s exactOptionalPropertyTypes compiler option. This covers model types, inlined request types, client options, errors, and the core/ utilities, so consumers whose own tsconfig sets exactOptionalPropertyTypes: true can use the SDK without type errors.

This option applies with or without the serde layer, so it can be combined with either value of noSerdeLayer.

# generators.yml
config:
exactOptionalPropertyTypes: true
extraDependencies
objectDefaults to {}
Enterprise feature

This feature is available only for the Enterprise plan. To get started, reach out to support@buildwithfern.com.

Specify extra dependencies in the generated package.json. This is useful when you add custom code to your SDK that requires additional dependencies.

# generators.yml
config:
extraDependencies:
lodash: "3.0.2"
extraDevDependencies
objectDefaults to {}
Enterprise feature

This feature is available only for the Enterprise plan. To get started, reach out to support@buildwithfern.com.

Specify extra dev dependencies in the generated package.json.

# generators.yml
config:
extraDevDependencies:
jest: "29.0.7"
Only applies when publishing to Github.
extraPeerDependencies
object

Specify extra peer dependencies in the generated package.json:

# generators.yml
config:
extraPeerDependencies:
react: ">=16.8.0 <19.0.0"
"react-dom": ">=16.8.0 <19.0.0"
extraPeerDependenciesMeta
object

Specify extra peer dependencies meta fields in the generated package.json:

# generators.yml
config:
extraPeerDependencies:
react: ">=16.8.0 <19.0.0"
"react-dom": ">=16.8.0 <19.0.0"
fetchSupport
'node-fetch' | 'native'

Choose whether you want to include node-fetch to support Node.js versions before Node.js 18, or choose native to use the native fetch API available in Node.js 18 and later.

fileResponseType
'stream' | 'binary-response'

Change the type of response returned to the user for a binary HTTP response:

  • stream: Returns a stream. See streamType, which controls the type of stream returned.
  • binary-response: Returns the BinaryResponse type, which allows the user to choose how to consume the binary HTTP response. Here’s how your users can interact with the BinaryResponse:
const response = await client.getFile(...);
const stream = response.stream();
// const arrayBuffer = await response.arrayBuffer();
// const blob = await response.blob();
// const bytes = await response.bytes();
const bodyUsed = response.bodyUsed;
formDataSupport
'Node16' | 'Node18'

Choose whether you want to support Node.js 16 and above (Node16), or Node.js 18 and above (Node18).

  • Node16 uses multiple dependencies to support multipart forms, including form-data, formdata-node, and form-data-encoder.
  • Node18 uses the native FormData API, and accepts a wider range of types for file uploads, such as Buffer, File, Blob, Readable, ReadableStream, ArrayBuffer, and Uint8Array
generateSubpackageExports
booleanDefaults to true

Generates subpackage exports that allow users to import individual clients directly, rather than importing the entire SDK. This enables JavaScript bundlers to tree-shake unused code, significantly reducing bundle sizes.

import { BarClient } from '@acme/sdk/foo/bar';
// Imports only the Bar subpackage
const client = new BarClient({...});

Subpackage exports are also documented in the generated README.md when this option is enabled.

guardProcessEnvAccess
booleanDefaults to false

Wraps credential environment variable reads in the generated auth providers (API key, bearer, basic, and OAuth) in a typeof process !== "undefined" check. Enable this when the SDK runs in browsers, Cloudflare Workers, Deno, or other runtimes without a Node process global; otherwise process.env reads throw a ReferenceError when a credential isn’t passed to the client. Node.js behavior is unchanged.

generators.yml
groups:
ts-sdk:
generators:
- name: fern-typescript-sdk
version: 3.95.1
config:
guardProcessEnvAccess: true
includeContentHeadersOnFileDownloadResponse
boolean

Includes the content type and content length from binary responses. The user will receive an object of the following type:

{
data: <BINARY_RESPONSE_TYPE>;
contentLengthInBytes?: number;
contentType?: string;
}

<BINARY_RESPONSE_TYPE> is core.BinaryResponse or a stream, depending on fileResponseType setting.

includeCredentialsOnCrossOriginRequests
booleanDefaults to false

When enabled, withCredentials is set to true when making network requests.

includeOtherInUnionTypes
boolean
includeUtilsOnUnionMembers
boolean
inlineFileProperties
booleanDefaults to true

Generate file upload properties as inline request properties (instead of positional parameters).

inlineFileProperties: false:

/**
* @param {File | fs.ReadStream} file
* @param {File[] | fs.ReadStream[]} fileList
* @param {File | fs.ReadStream | undefined} maybeFile
* @param {File[] | fs.ReadStream[] | undefined} maybeFileList
* @param {Acme.MyRequest} request
* @param {Service.RequestOptions} requestOptions - Request-specific configuration.
*
* @example
* await client.service.post(fs.createReadStream("/path/to/your/file"), [fs.createReadStream("/path/to/your/file")], fs.createReadStream("/path/to/your/file"), [fs.createReadStream("/path/to/your/file")], {})
*/
public async post(
file: File | fs.ReadStream,
fileList: File[] | fs.ReadStream[],
maybeFile: File | fs.ReadStream | undefined,
maybeFileList: File[] | fs.ReadStream[] | undefined,
request: Acme.MyRequest,
requestOptions?: Acme.RequestOptions
): Promise<void> {
...
}

inlineFileProperties: true:

/**
* @param {Acme.MyRequest} request
* @param {Service.RequestOptions} requestOptions - Request-specific configuration.
*
* @example
* await client.service.post({
* file: fs.createReadStream("/path/to/your/file"),
* fileList: [fs.createReadStream("/path/to/your/file")]
* })
*/
public async post(
request: Acme.MyRequest,
requestOptions?: Service.RequestOptions
): Promise<void> {
...
}
inlinePathParameters
booleanDefaults to true

Inline path parameters into request types.

inlinePathParameters: false:

await service.getFoo("pathParamValue", { id: "SOME_ID" });

inlinePathParameters: true:

await service.getFoo({ pathParamName: "pathParamValue", id: "SOME_ID" });
namespaceExport
string

Customizes the exported namespace and client class names in the generated SDK. Must be in PascalCase.

By default, names are derived from the organization and API names defined in your API definition:

import { AcmeApi, AcmeApiClient } from "@acme/node";

Setting namespaceExport overrides these default names:

generators.yml
config:
namespaceExport: AcmePayments
import { AcmePayments, AcmePaymentsClient } from "@acme/node";
maxRetries
number

The default number of retries for failed requests. When not set, the generated SDK uses its own built-in default. SDK users can still override this per-request via request options.

naming
string | object

Customize the namespace export and class/type names. Accepts a string shorthand or a full object.

String shorthand — sets the namespace and derives all class names via PascalCase:

generators.yml
config:
naming: acme
import { acme, AcmeClient } from "acme";

Object form — override individual names:

generators.yml
config:
naming:
namespace: acme
client: AcmeSdkClient
error: AcmeSdkError
environment: AcmeSdkEnvironment
import { acme, AcmeSdkClient } from "acme";
FieldTypeDescription
namespacestringNamespace export name. Equivalent to namespaceExport.
clientstringClient class name (default: ${PascalCase(namespace)}Client).
errorstringGeneric API error class name (default: ${PascalCase(namespace)}Error).
timeoutErrorstringTimeout error class name (default: ${PascalCase(namespace)}TimeoutError).
environmentstringEnvironment enum name (default: ${PascalCase(namespace)}Environment).
environmentUrlsstringEnvironment URLs type name (default: ${PascalCase(namespace)}EnvironmentUrls).
versionstringVersion enum name (default: ${PascalCase(namespace)}Version).

namespaceExport is still supported for backwards compatibility but naming.namespace takes precedence.

neverThrowErrors
booleanDefaults to false

When enabled, the client doesn’t throw errors when a non-200 response is received from the server. Instead, the response is wrapped in an ApiResponse.

const response = await client.callEndpoint(...);
if (response.ok) {
console.log(response.body)
} else {
console.error(respons.error)
}
noOptionalProperties
booleanDefaults to false

By default, Fern’s optional<> properties will translate to optional TypeScript properties:

Person:
properties:
name: string
age: optional<integer>
interface Person {
name: string;
age?: number;
}

When noOptionalProperties is enabled, the generated properties are never optional. Instead, the type is generated with | undefined. As a result, users must explicitly set the property to a value or undefined.

interface Person {
name: string;
age: number | undefined;
}
noSerdeLayer
booleanDefaults to true

Controls whether the serde layer is enabled for serialization/deserialization.

When noSerdeLayer: false, the generated client includes custom serialization code that transforms property names to camelCase, validates requests/responses at runtime, and supports complex types.

See TypeScript serde layer for detailed guidance on when to enable this option.

offsetSemantics
'item-index' | 'page-index'Defaults to item-index

Controls how the offset parameter is interpreted for auto-paginated endpoints.

  • item-index: The offset counts individual items (e.g., offset 20 skips the first 20 items).
  • page-index: The offset counts pages (e.g., offset 3 skips to page 3).
omitFernHeaders
booleanDefaults to false

When enabled, the generated SDK omits the X-Fern-Language, X-Fern-SDK-Name, and X-Fern-SDK-Version headers from HTTP requests. These headers are included by default to help API providers identify SDK traffic.

includePlatformHeaders
booleanDefaults to false

When enabled, the generated SDK sends a single structured User-Agent header of the form {sdkName}/{version} ({os}; {arch}) {runtime}/{runtimeVersion} (for example, my-sdk/0.0.1 (linux; x86_64) Node/20.11.0), carrying SDK, operating system, architecture, and runtime information in place of the default User-Agent and discrete platform headers. A configured user-agent template supplies the leading product token. If omitFernHeaders is enabled, no User-Agent or platform headers are sent and this option has no effect.

allowUserAgentAppInfo
booleanDefaults to false

When enabled, the generated client accepts an optional appInfo client option, which appends a {name}/{version} ({comment}) product token to the User-Agent header so an application built on the SDK can identify itself to the API. The application passes a required name plus an optional version and comment; omitted or blank values are dropped from the token. A User-Agent set explicitly in client or request headers takes precedence.

appInfo: { name: "partner-app", version: "3.1.0", comment: "+https://partner.example" }
// User-Agent: my-sdk/0.0.1 (linux; x86_64) Node/20.11.0 partner-app/3.1.0 (+https://partner.example)
optional-auth
booleanDefaults to false

When enabled, client auth parameters (bearer token, basic auth credentials, header auth) remain optional even when the spec’s security requirements mandate auth on every endpoint, and requests are sent without an auth header when no credential is provided. By default, the client throws when a mandatory credential is missing.

Enable this when callers authenticate through a mechanism other than the API’s own scheme, such as cloud provider credentials, and would otherwise have to pass a placeholder value.

outputSourceFiles
booleanDefaults to true

Controls the output format of generated files:

  • When true (default): Outputs raw TypeScript .ts files
  • When false: Runs TypeScript compiler and outputs compiled .js files with .d.ts declaration files
This option only applies when using local file system output. This setting is ignored when publishing to GitHub or npm, where files are always compiled.
packageJson
object

When you specify an object in packageJson, it will be merged into the package.json file. This is the recommended way to customize your SDK’s package.json.

# generators.yml
config:
packageJson:
description: The SDK for Acme Corp's API.
author:
name: Acme Corp
url: https://developer.acmecorp.com
email: developers@acmecorp.com
bugs:
url: https://developer.acmecorp.com
email: developers@acmecorp.com

You can also use packageJson.exports to register custom subpath exports (e.g. import { myHelper } from "@acme/sdk/helper"). The generator only auto-generates export entries for your API definition, so custom files need to be added manually—otherwise Node.js won’t resolve subpath imports for them. See Adding custom code for details.

How nested objects are combined with the generated values is controlled by packageJsonMergeStrategy.

packageJsonMergeStrategy
'shallow' | 'deep'Defaults to shallow

Controls how nested objects in packageJson are merged into the generated package.json.

  • shallow (default): nested objects are replaced wholesale. For example, overriding exports["."] replaces the generated import/require/default conditions with exactly what you wrote.
  • deep: nested objects are merged recursively. Your keys win and are emitted first, so a custom exports condition is matched before the generated ones.

Use deep to add a custom export condition without redefining the whole subpath:

# generators.yml
config:
packageJsonMergeStrategy: deep
packageJson:
exports:
".":
"my-dev-condition":
types: "./dist/cjs/index.d.ts"
default: "./src/index.ts"

Neither strategy can remove a generated key.

package-name
stringDefaults to null

Specifies the TypeScript package name that users will import your generated client from.

For example, setting package-name: "my_custom_package" enables users to use my_custom_package import Client to import your client.

packagePath
string

Specify the path where the source files for the generated SDK should be placed.

publishToJsr
boolean

Publish your SDK to JSR. When enabled, the generator will generate a jsr.json as well as a GitHub workflow to publish to JSR.

requireBaseUrl
booleanDefaults to false

When enabled, baseUrl becomes a required client option and environment becomes optional. Generated code snippets and the README.md construct the client with baseUrl and omit the environments section.

const client = new AcmeClient({ baseUrl: "https://api.acme.com" });

Enable this for APIs with no named environments, or when callers point the SDK at an arbitrary host. The option is ignored for APIs with multiple base URLs, whose clients resolve each URL from environment.

respectOptionalRequestBody
booleanDefaults to false

Lets callers omit the request argument on endpoints with an optional request body. A call that omits it sends no body and no Content-Type header, instead of an empty JSON object ({}). Enable this when your API treats a missing body differently from an empty one.

Only applies to request bodies declared as a single named type. Required bodies and inlined request properties are unaffected.

retainOriginalCasing
booleanDefaults to false

When enabled, property names in the generated code retain their original casing from the API definition instead of being converted to camelCase.

# generators.yml
config:
retainOriginalCasing: true

Example with OpenAPI input:

# OpenAPI schema
components:
schemas:
User:
type: object
properties:
user_id:
type: string
display_name:
type: string

Generated TypeScript with retainOriginalCasing: true:

export interface User {
user_id: string;
display_name: string;
}

Generated TypeScript with default settings (retainOriginalCasing: false):

export interface User {
userId: string;
displayName: string;
}
generateWebSocketClients
boolean

Generate WebSocket clients from your AsyncAPI channel definitions.

Previously named shouldGenerateWebsocketClients, which is still accepted as a deprecated alias.

skipResponseValidation
booleanDefaults to false

By default, the client will throw an error if the response from the server doesn’t match the expected type (based on how the response is modeled in your API specification).

If skipResponseValidation is set to true, the client will never throw if the response is misshapen. Instead, the client will log the issue using console.warn and return the data (casted to the expected response type).

Response validation only occurs when the Serde layer is enabled (noSerdeLayer: false). The Serde layer is disabled by default (noSerdeLayer: true).
streamType
'wrapper' | 'web'

Change the type of stream that is used in the generated SDK.

  • wrapper: The streams use a wrapper with multiple underlying implementations to support versions of Node.js before Node.js 18.
  • web: The streams use the web standard ReadableStream.

The default is web.

treatUnknownAsAny
booleanDefaults to false
user-agent
stringDefaults to {packageName}/{version}

Sets a custom User-Agent header template for requests sent by the generated SDK. The template is resolved at generation time and supports the {packageName}, {version}, {language}, {generatorVersion}, {organization}, and {apiName} placeholders.

generators.yml
config:
user-agent: "plantstore-node-sdk/{version}"

This sends User-Agent: plantstore-node-sdk/0.1.0. With includePlatformHeaders enabled, the resolved value leads the structured header: User-Agent: plantstore-node-sdk/0.1.0 (linux; x86_64) Node/20.11.0. A value that doesn’t end in a version, such as @plantstore/sdk, is used as-is. The allowUserAgentAppInfo token is appended after either form.

useBigInt
booleanDefaults to false

When useBigInt is set to true, a customized JSON serializer & deserializer is used that will preserve the precision of bigint’s, as opposed to the native JSON.stringify and JSON.parse function which converts bigint’s to number’s losing precision.

When combining useBigInt with our serialization layer (noSerdeLayer: false), both the request and response properties that are marked as long and bigint in OpenAPI/Fern spec, will consistently be bigint’s. However, when disabling the serialization layer (noSerdeLayer: true), they will be typed as number | bigint. See TypeScript serde layer for more information.

Here’s an overview of what to expect from the generated types when combining useBigInt and noSerdeLayer with the following Fern definition:

Fern definition:

types:
ObjectWithOptionalField:
properties:
longProp: long
bigIntProp: bigint

TypeScript output:

// useBigInt: true
// noSerdeLayer: false
interface ObjectWithLongAndBigInt {
longProp: bigint;
bigIntProp: bigint;
}
// useBigInt: true
// noSerdeLayer: true
interface ObjectWithLongAndBigInt {
longProp: bigint | number;
bigIntProp: bigint | number;
}
// useBigInt: false
// noSerdeLayer: false
interface ObjectWithLongAndBigInt {
longProp: number;
bigIntProp: string;
}
// useBigInt: false
// noSerdeLayer: true
interface ObjectWithLongAndBigInt {
longProp: number;
bigIntProp: string;
}
useBrandedStringAliases
booleanDefaults to false

When useBrandedStringAliases is disabled (the default), string aliases are generated as normal TypeScript aliases:

// generated code
export type MyString = string;
export type OtherString = string;

When useBrandedStringAliases is enabled, string aliases are generated as branded strings. This makes each alias feel like its own type and improves compile-time safety.

# fern definition
types:
MyString: string
OtherString: string
// generated code
export type MyString = string & { __MyString: void };
export const MyString = (value: string): MyString => value as MyString;
export type OtherString = string & { __OtherString: void };
export const OtherString = (value: string): OtherString => value as OtherString;
// consuming the generated type
function printMyString(s: MyString): void {
console.log("MyString: " + s);
}
// doesn't compile, "foo" is not assignable to MyString
printMyString("foo");
const otherString = OtherString("other-string");
// doesn't compile, otherString is not assignable to MyString
printMyString(otherString);
// compiles
const myString = MyString("my-string");
printMyString(myString);