TypeScript Configuration

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

1default-group: local
2groups:
3 local:
4 generators:
5 - name: fernapi/fern-typescript-sdk
6 version: 2.6.2
7 config:
8 namespaceExport: Acme

SDK configuration options

allowExtraFields
boolean

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

bundle
boolean
defaultTimeoutInSeconds
number | 'infinity'

The default timeout for network requests. 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:

1// MyRootType.ts
2import * as MySdk from "...";
3
4export interface MyRootType {
5 foo: MySdk.MyRootTypeFoo;
6}
7
8// MyRootTypeFoo.ts
9import * as MySdk from "...";
10
11export interface MyRootTypeFoo {
12 bar: MySdk.MyRootTypeFooBar;
13}
14
15// MyRootTypeFooBar.ts
16import * as MySdk from "...";
17
18export interface MyRootTypeFooBar {}

enableInlineTypes: true:

1// MyRootType.ts
2import * as MySdk from "...";
3
4export interface MyRootType {
5 foo: MyRootType.Foo;
6}
7
8export namespace MyRootType {
9 export interface Foo {
10 bar: Foo.Bar;
11 }
12
13 export namespace Foo {
14 export interface Bar {}
15 }
16}

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

1import { MyRootType } from MySdk;
2
3const bar: MyRootType.Foo.Bar = {};
extraDependencies
objectDefaults to {}

Specify extra dependencies in the generated package.json. This is useful when you utilize .fernignore to supplement the generated client with custom code.

1# generators.yml
2config:
3 extraDependencies:
4 lodash: "3.0.2"
extraDevDependencies
objectDefaults to {}

Specify extra dev dependencies in the generated package.json.

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

Specify extra peer dependencies in the generated package.json:

1# generators.yml
2config:
3 extraPeerDependencies:
4 react: ">=16.8.0 <19.0.0"
5 "react-dom": ">=16.8.0 <19.0.0"
extraPeerDependenciesMeta
object

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

1# generators.yml
2config:
3 extraPeerDependencies:
4 react: ">=16.8.0 <19.0.0"
5 "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:
1const response = await client.getFile(...);
2const stream = response.stream();
3// const arrayBuffer = await response.arrayBuffer();
4// const blob = await response.blob();
5// const bytes = await response.bytes();
6const 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
includeContentHeadersOnFileDownloadResponse
boolean

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

1{
2 data: <BINARY_RESPONSE_TYPE>;
3 contentLengthInBytes?: number;
4 contentType?: string;
5}

<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:

1/**
2 * @param {File | fs.ReadStream} file
3 * @param {File[] | fs.ReadStream[]} fileList
4 * @param {File | fs.ReadStream | undefined} maybeFile
5 * @param {File[] | fs.ReadStream[] | undefined} maybeFileList
6 * @param {Acme.MyRequest} request
7 * @param {Service.RequestOptions} requestOptions - Request-specific configuration.
8 *
9 * @example
10 * 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")], {})
11 */
12public async post(
13 file: File | fs.ReadStream,
14 fileList: File[] | fs.ReadStream[],
15 maybeFile: File | fs.ReadStream | undefined,
16 maybeFileList: File[] | fs.ReadStream[] | undefined,
17 request: Acme.MyRequest,
18 requestOptions?: Acme.RequestOptions
19): Promise<void> {
20 ...
21}

inlineFileProperties: true:

1/**
2 * @param {Acme.MyRequest} request
3 * @param {Service.RequestOptions} requestOptions - Request-specific configuration.
4 *
5 * @example
6 * await client.service.post({
7 * file: fs.createReadStream("/path/to/your/file"),
8 * fileList: [fs.createReadStream("/path/to/your/file")]
9 * })
10 */
11public async post(
12 request: Acme.MyRequest,
13 requestOptions?: Service.RequestOptions
14): Promise<void> {
15 ...
16}
inlinePathParameters
booleanDefaults to true

Inline path parameters into request types.

inlinePathParameters: false:

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

inlinePathParameters: true:

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

By default, names are based on the organization and API names in the Fern Definition:

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

namespaceExport customizes the exported namespace and client names:

1# generators.yml
2config:
3 namespaceExport: Acme
1import { Acme, AcmeClient } from "@acme/node";
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.

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

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

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

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.

1interface Person {
2 name: string;
3 age: number | undefined;
4}
noScripts
boolean

Prevent the generator from running any scripts such as yarn format or yarn install. If any of the scripts cause errors, toggling this option will allow you to receive the generated code.

noSerdeLayer
booleanDefaults to true

No serialization/deserialization code is generated by default. The client uses JSON.parse() and JSON.stringify() instead of the default Serde layer.

When noSerdeLayer: false, the generated client includes a layer for serializing requests and deserializing responses. This has three benefits:

  1. The client validates requests and response at runtime (client-side).

  2. The client can support complex types like Date and Set.

  3. The generated types can stray from the wire/JSON representation to be more idiomatic. For example, when the Serde layer is enabled (noSerdeLayer: false), all properties are camelCase, even if the server is expecting snake_case.

outputSourceFiles
booleanDefaults to true

When enabled, the generator outputs raw TypeScript files. When disabled (the default), outputs .js and d.ts files.

This only applies when dumping code locally. This configuration is ignored when publishing to Github or npm.
packageJson
object

When you specify an object in packageJson, it will be merged into the package.json file.

1# generators.yml
2config:
3 packageJson:
4 description: The SDK for Acme Corp's API.
5 author:
6 name: Acme Corp
7 url: https://developer.acmecorp.com
8 email: developers@acmecorp.com
9 bugs:
10 url: https://developer.acmecorp.com
11 email: developers@acmecorp.com
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.

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.

1# generators.yml
2config:
3 retainOriginalCasing: true

Example with OpenAPI input:

1# OpenAPI schema
2components:
3 schemas:
4 User:
5 type: object
6 properties:
7 user_id:
8 type: string
9 display_name:
10 type: string

Generated TypeScript with retainOriginalCasing: true:

1export interface User {
2 user_id: string;
3 display_name: string;
4}

Generated TypeScript with default settings (retainOriginalCasing: false):

1export interface User {
2 userId: string;
3 displayName: string;
4}
shouldGenerateWebsocketClients
boolean

Generate WebSocket clients from your AsyncAPI specs.

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 the Fern Definition).

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
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 (no-serde: 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 (no-serde: true), they will be typed as number | bigint.

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

Fern definition:

1types:
2 ObjectWithOptionalField:
3 properties:
4 longProp: long
5 bigIntProp: bigint

TypeScript output:

1// useBigInt: true
2// noSerde: false
3interface ObjectWithLongAndBigInt {
4 longProp: bigint;
5 bigIntProp: bigint;
6}
7
8// useBigInt: true
9// noSerde: true
10interface ObjectWithLongAndBigInt {
11 longProp: bigint | number;
12 bigIntProp: bigint | number;
13}
14
15// useBigInt: false
16// noSerde: false
17interface ObjectWithLongAndBigInt {
18 longProp: number;
19 bigIntProp: string;
20}
21
22// useBigInt: false
23// noSerde: true
24interface ObjectWithLongAndBigInt {
25 longProp: number;
26 bigIntProp: string;
27}
useBrandedStringAliases
booleanDefaults to false

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

1// generated code
2
3export type MyString = string;
4
5export 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.

1# fern definition
2
3types:
4 MyString: string
5 OtherString: string
1// generated code
2
3export type MyString = string & { __MyString: void };
4export const MyString = (value: string): MyString => value as MyString;
5
6export type OtherString = string & { __OtherString: void };
7export const OtherString = (value: string): OtherString => value as OtherString;
1// consuming the generated type
2
3function printMyString(s: MyString): void {
4 console.log("MyString: " + s);
5}
6
7// doesn't compile, "foo" is not assignable to MyString
8printMyString("foo");
9
10const otherString = OtherString("other-string");
11// doesn't compile, otherString is not assignable to MyString
12printMyString(otherString);
13
14// compiles
15const myString = MyString("my-string");
16printMyString(myString);