> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt.

# Changelog

## September 24, 2026

## 3.97.1

**`(fix):`** Fix generated WebSocket socket classes registering their `open`/`message`/`close`/`error`
forwarding listeners twice when `connect()` is called after construction (the standard
`new Socket(...).connect()` usage). `ReconnectingWebSocket` gains a `hasEventListener(type, listener)`
method and `connect()` only registers each forwarding listener if it is not already attached, so
every consumer `.on()` callback fires exactly once per event while `close()` followed by
`connect()` continues to work.

## 3.97.0

**`(feat):`** Generate the shared `WebhooksHelper` from the API-wide `api.settings.webhook-signature`
setting in `generators.yml` (IR `sdkConfig.webhookSignatureVerification`). The helper is
emitted even when the API definition models no webhooks; webhook-specific signature
overrides still produce their own named helpers.

## September 23, 2026

## 3.96.3

**`(fix):`** Wire tests for paginated endpoints whose page property is nested in the request body (e.g.
`$request.options.offset` or `$request.options.pagination.cursor`) no longer fail on `getNextPage()`.
The generated pager creates the parent object (`options`) when the example omits it, and the mock
server's `jsonBody` matcher treated that new parent as an unexpected field even though the nested leaf
was listed in `ignoredFields`, so the second request never matched the mock and failed with a
connection-refused error. The matcher now ignores a parent object that is absent from the expected body
when every leaf inside it is an ignored field.

## September 22, 2026

## 3.96.2

**`(fix):`** The generated OAuth wire-test mock (`tests/wire/mockAuth.ts`) now expects the `grant_type` the generated
`OAuthAuthProvider` actually sends. When the token endpoint models `grant_type` as a plain (optional) string
and the example omits it, the provider sends `grant_type: "client_credentials"` but the mock previously only
matched the example body, so the token request never matched and every authenticated wire test failed with
a connection-refused error.

## 3.96.1

**`(chore):`** Bump bundled @fern-api/generator-cli to 0.10.1. Auto-versioned MAJOR/MINOR bumps no longer
produce a version-only `changelog.md` block when the AI analysis returns an empty changelog
entry; the PR description, version bump reason, or commit message body is used instead.

## September 21, 2026

## 3.96.0

**`(feat):`** Object types with XML encoding metadata (e.g. TwiML schemas imported from an OpenAPI `xml` object) are now
generated as classes with `toXml()`, a static `fromXml()` parser, and a fluent `Builder` (including
`Builder.fromXml(xml).build()` and per-child methods such as `response.say(...)`). Element names,
attributes, text bodies, nested/union children, wrapped lists, list separators, namespaces and prefixes
are honored; undeclared attributes and child elements are preserved as `additionalAttributes` /
`additionalChildren` so documents round-trip. Parsing is strict (root name, scalar/enum validation) and
rejects DOCTYPE declarations. Non-XML types are unaffected and the XML runtime is only emitted when needed.

## September 16, 2026

## 3.95.1

**`(fix):`** TypeScript dynamic snippets honor `flattenRequestParameters` by spreading referenced object bodies instead of emitting `body: {...}`, and nest multi-auth constructor options under the camelCase auth scheme key regardless of `noSerdeLayer` or `retainOriginalCasing`.

## 3.95.0

**`(feat):`** SDKs now read the base URL from the environment variable configured via base-url-env / x-fern-base-url-env when no baseUrl or environment option is passed, respecting guardProcessEnvAccess for the environment-variable read.

## September 15, 2026

## 3.94.2

**`(fix):`** A union member whose discriminant value collides with a JavaScript built-in
no longer has the disambiguation prefix applied to the string literal.
A branch with `condition_type: "Date"` emitted
`conditionType: "globalThis.Date"` — so the property could not be typed
without a cast, since the API expects `"Date"`, and with `noSerdeLayer:
false` the serializer's inferred discriminant no longer matched the declared
literal and the package failed to compile. Qualification exists to
disambiguate a shadowed *type reference*; a literal is a value, and
rewriting one changes what the SDK sends. String literals are now skipped,
which also covers builder expressions and visitor signatures, where the same
rewrite could reach an object literal. Type references are unaffected: a
sibling property typed `Date` still becomes `globalThis.Date`.

## 3.94.1

**`(fix):`** Fix generated wire tests for cursor, URI, and path pagination when the example
response uses an empty string to indicate there is no next page. Terminal URI
and path continuations are preserved in the mocked response, and the tests skip
`hasNextPage()` and `getNextPage()` assertions, matching the generated pager.

## September 14, 2026

## 3.94.0

**`(feat):`** Add a `websocketHandlerMode` config option (`"replace" | "accumulate"`, defaults to `"replace"`)
for generated WebSocket `Socket` classes. With `accumulate`, multiple `on()` handlers for the same
event are all kept and run in registration order instead of the last one replacing the previous.
Generated sockets now also expose `off(event, callback)` to detach a handler in both modes.
Defaults to `replace`, so generated output is unchanged unless the option is enabled.

**`(fix):`** Fix the generated README/reference pagination snippet, which assigned `page.getNextPage()`
without `await` and therefore did not type-check.

**`(feat):`** Add an optional `shouldReconnect(event: CloseEvent) => boolean` option to `ReconnectingWebSocket` and to the
generated WebSocket `connect()` args. Return `false` to treat a close as terminal and suppress the automatic
reconnect (e.g. after the client sent an end-of-stream message and the server closed with a non-1000 code).
Defaults to the existing behaviour of reconnecting on any close code other than 1000. Also clarifies the
`reconnectAttempts` docstring (defaults to 30; set to 0 to disable reconnecting).

## 3.93.1

**`(fix):`** The generated README now always includes a Pagination section (and pagination usage snippets)
when the API has paginated endpoints, matching the client which always returns `core.Page`.
Previously the section was dropped when the CLI ran without org information (e.g. `fern generate --local` without a `FERN_TOKEN`).

## September 11, 2026

## 3.93.0

**`(feat):`** Add an opt-in `esmOnly` configuration flag. When enabled, the generated package ships
only the existing ESM build: `package.json` is marked `type: "module"`, the exports map
only exposes the `.mjs`/`.d.mts` entry points, and the CommonJS build (`tsconfig.cjs.json`,
`build:cjs`) is no longer generated. This avoids the dual package hazard for consumers who
want a pure-ESM package. The default behavior (dual CJS + ESM output, including with
`outputEsm: true`) is unchanged. `esmOnly` is incompatible with `useLegacyExports` and
`bundle`.

**`(fix):`** Fix the `build:esm` script in packages generated with `outputEsm: true`. These packages
are marked `type: "module"`, so Node interpreted the CommonJS helper
`scripts/rename-to-esm-files.js` as ESM and the build failed with
`ReferenceError: require is not defined in ES module scope`. The helper is now emitted
as `scripts/rename-to-esm-files.cjs` whenever the package is `type: "module"`.

## September 9, 2026

## 3.92.0

**`(feat):`** Add a new `exactOptionalPropertyTypes` config option (default `false`) that makes the
generated SDK compile cleanly under TypeScript's `exactOptionalPropertyTypes`
compiler option. When enabled, every optional property is emitted as
`prop?: T | undefined` across model types, inlined request wrappers, client
options, errors, and the `core/` utilities, and the generated `tsconfig`
files enable `exactOptionalPropertyTypes` so the generated package typechecks
itself under the stricter mode. Previously this behavior was partially
available only with `noSerdeLayer: true`; the new flag applies it uniformly,
including when the serde layer is enabled.

## September 5, 2026

## 3.91.0

**`(feat):`** Add the opt-in `deepObjectMapQueryParameters` configuration option. When enabled, query parameters typed
as a map (e.g. `map<string, string>`, which is what an OpenAPI `type: object` + `additionalProperties`
schema imports as) are handed to the query builder and serialized in deepObject form
(`?metadata[env]=prod`) instead of being JSON-stringified (`?metadata=%7B%22env%22...`). Maps whose
values are objects, nested maps, lists, sets, or date-times are supported too: with the serde layer
enabled they are run through the generated schema first, so nested keys use wire names, sets become
arrays, and dates are ISO strings (`?metadata[env][created_at]=2026-09-04T00:00:00.000Z`). Defaults to
`false`, so existing SDKs are unaffected.

```yaml
- name: fernapi/fern-typescript-sdk
  config:
    deepObjectMapQueryParameters: true
```

Maps with `unknown` values (an OpenAPI `additionalProperties: true` import) are rejected at
generation time. There is no schema to normalize such a value against, so a non-plain object like a
`Date` or a `Set` would contribute no enumerable keys and be dropped from the query string with no
error. Rather than emit an SDK that silently loses data, generation fails:

```
Query parameter 'metadata' on GET /search is a map with `unknown` values, which
`deepObjectMapQueryParameters` cannot safely encode: a non-plain object such as a Date or a Set
would be silently dropped from the query string.
Either give the map a concrete value type (so the serde layer can normalize it), or disable
`deepObjectMapQueryParameters`.
```

One other behaviour to be aware of, inherent to deepObject encoding: an empty map is omitted from the
query string entirely, where it previously serialized as `?key=%7B%7D`. There are no keys to explode,
so nothing is emitted.

Query parameters explicitly declared `explode: false` keep their existing encoding; deepObject form is
only applied where `explode` is true or unset.

## September 2, 2026

## 3.90.0

**`(feat):`** Add the `packageJsonMergeStrategy` option (`shallow` | `deep`, default `shallow`) controlling how
`packageJson` overrides in `generators.yml` are merged into the generated `package.json`.

`shallow` is the existing behavior and remains the default: nested objects such as `exports["."]`
are replaced wholesale by the override, so adding a single custom export condition drops the
generated `import`/`require`/`default` conditions.

Opt into `deep` when you want to add to a nested object without redefining it, for example adding
a custom `exports` condition while keeping the generated ones. Under `deep`, user keys win at
every level, user key order is preserved (user-specified keys are emitted first, in the order
written, so a custom export condition precedes the generated ones and Node can match it), and
keys the user does not mention are inherited from the generated output. Arrays are unioned with
user entries first under both strategies.

Removing a generated key (for example dropping the `require` condition) is not expressible via
`packageJson` overrides under either strategy.

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

## 3.89.0

**`(feat):`** Add a `guardProcessEnvAccess` config option (boolean, defaults to `false`).
When enabled, the generated header, bearer, basic and OAuth auth providers read environment
variables through a `typeof process !== "undefined"` guard, so a missing credential surfaces the
normal error instead of `ReferenceError: process is not defined` in runtimes without a Node
`process` global (browsers/Vite, Cloudflare Workers, Deno). Defaults to `false`, so generated
output is unchanged unless the option is enabled.

## August 26, 2026

## 3.88.3

**`(fix):`** Respect custom OAuth token headers and prefixes for client credentials authentication.

## August 24, 2026

## 3.88.2

**`(fix):`** Stop dropping the examples of endpoints whose request body is bytes or a file upload. The IR's
`ExampleRequestBody` cannot carry those bodies, so such examples read as bodyless and the
endpoint lost its `reference.md` section, snippets and docs comments.

**`(fix):`** Positional path parameters that an example does not supply are now rendered with their
`client-default` value instead of `undefined`, so generated snippets typecheck.

## August 20, 2026

## 3.88.1

**`(fix):`** Endpoints that disable retries (`x-fern-retries: { disabled: true }` in OpenAPI, or
`retries: { disabled: true }` in a Fern definition) now always emit `maxRetries: 0`,
overriding both client-level and request-level `maxRetries`. Previously this
configuration was only honored by the Python SDK generator and was silently ignored
in TypeScript.

## August 14, 2026

## 3.88.0

**`(fix):`** With `respectOptionalRequestBody` enabled, examples that omit an optional request body are no
longer dropped from the generated reference, snippets, and docs comments.

**`(feat):`** Add `respectOptionalRequestBody`. With it enabled, an endpoint whose request body the API does
not require takes an optional body parameter — `refund(id: string, request?: RefundRequest)` —
keeping the body's own type rather than widening it to `RefundRequest | undefined`, and sending
no body when the caller omits it. Examples that supply no body render as `client.refund(id)`,
and the generated wire test for such an example no longer asserts a request body. Defaults to
`false`, so signatures and snippets are unchanged until you opt in.

## August 13, 2026

## 3.87.4

**`(fix):`** Skip endpoint examples that omit a request body the endpoint declares. The generated request
type requires the body, so rendering such an example produced a call missing a required
argument in the reference, the docstrings, the snippets and the generated tests.

## August 7, 2026

## 3.87.3

**`(fix):`** Fix dynamic snippets emitting a dangling argument delimiter (e.g. `client.refunds.refund("id", )`)
when an example omits the request body.

## August 5, 2026

## 3.87.2

**`(fix):`** Send the caller's request when fetching the first page of a `next_uri`/`next_path` paginated
endpoint. Previously the first page was requested through the same headers-only helper used for
subsequent pages, so query parameters, request bodies and `requestOptions.queryParams` were
silently dropped (and the unused `request` parameter was emitted as `_request`). Subsequent pages
keep using the next URL as-is.

## August 4, 2026

## 3.87.1

**`(fix):`** The `appInfo` User-Agent helper emitted when `allowUserAgentAppInfo` is enabled no longer
uses a control-character regex range, so generated SDKs lint cleanly under rules such as
Biome's `noControlCharactersInRegex` without a suppression comment. Escaping behavior is
unchanged.

## August 3, 2026

## 3.87.0

**`(feat):`** Add a `requireBaseUrl` config option. When enabled, `baseUrl` is a required client option and
`environment` is optional, for APIs whose users always pass an explicit server URL. Generated
snippets, README, and wire tests use `baseUrl` accordingly. The option is ignored for APIs with
multiple base URLs, whose clients resolve each URL from `environment`. Defaults to `false`, so
existing SDKs are unchanged.

_Showing the 20 most recent of 323 entries. Append `/llms.txt` to the changelog URL for the complete index._