Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vale/styles/FernStyles/Headings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ exceptions:
- OG
- BCP
- ISO
- Replay
85 changes: 84 additions & 1 deletion fern/products/cli-api-reference/pages/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ hideOnThisPage: true
| [`fern generate`](#fern-generate) | Build & publish SDK updates |
| [`fern write-overrides`](#fern-write-overrides) | Create OpenAPI customizations |
| [`fern generator upgrade`](#fern-generator-upgrade) | Update SDK generators to latest versions |
| [`fern replay resolve`](#fern-replay-resolve) | Resolve patch conflicts left by [Fern Replay](/learn/sdks/overview/custom-code#replay) |
| [`fern replay forget`](#fern-replay-forget) | Remove tracked customization patches from `.fern/replay.lock` |

## Detailed command documentation

Expand Down Expand Up @@ -173,7 +175,7 @@ hideOnThisPage: true

<CodeBlock title="terminal">
```bash
fern generate [--group <group>] [--api <api>] [--version <version>] [--preview] [--fernignore <path>] [--local] [--force]
fern generate [--group <group>] [--api <api>] [--version <version>] [--preview] [--fernignore <path>] [--local] [--force] [--no-replay]
```
</CodeBlock>

Expand Down Expand Up @@ -270,6 +272,18 @@ hideOnThisPage: true
fern generate --group python-sdk --force
```

### no-replay

Use `--no-replay` to skip [Fern Replay](/learn/sdks/overview/custom-code#replay)'s patch detection and application phase for a single generation. The generation still produces the new SDK code, but tracked customization patches aren't applied on top. Useful for a clean regeneration — for example, to debug whether a conflict is caused by a specific patch.

```bash
fern generate --no-replay --local
```

<Note>
`--no-replay` works only for local generation (`--local`).
</Note>

</Accordion>

<Accordion title="fern check">
Expand Down Expand Up @@ -708,6 +722,75 @@ hideOnThisPage: true
Use `--include-major` to include major version upgrades. Major versions are skipped by default to prevent breaking changes.

</Accordion>

<Accordion title="fern replay resolve">

Use `fern replay resolve` to work through patch conflicts that [Fern Replay](/learn/sdks/overview/custom-code#replay) couldn't merge cleanly during `fern generate`. The command runs in two phases: the first run applies unresolved patches to your working tree with standard merge markers (`<<<<<<<` / `=======` / `>>>>>>>`) for you to edit; the second run verifies no markers remain and commits the resolved patches.

<CodeBlock title="terminal">
```bash
fern replay resolve [directory] [--no-check-markers]
```
</CodeBlock>

### directory

Path to the SDK directory containing `.fern/replay.lock`. Defaults to the current directory.

### no-check-markers

Skip the conflict-marker check before committing. Use with caution — committing files that still contain merge markers will break the next regeneration.

</Accordion>

<Accordion title="fern replay forget">

Use `fern replay forget` to remove tracked customization patches from `.fern/replay.lock`. Useful when the generator now supports a customization natively, or when you want to stop replaying a specific edit on future regenerations.

<CodeBlock title="terminal">
```bash
fern replay forget <patch-id-or-pattern>... [--all] [--dry-run] [--yes]
```
</CodeBlock>

The command accepts three modes:

- **By patch ID.** Pass one or more patch IDs to remove specific patches.

```bash
fern replay forget patch-abc123 patch-def456
```

- **By search pattern.** Pass a pattern to find matching patches. The command shows the matches with diff stats and prompts for confirmation before removing.

```bash
fern replay forget "src/utils"
```

- **All patches.** Pass `--all` to remove every tracked patch.

```bash
fern replay forget --all
```

### dry-run

Show what would be removed without actually removing.

```bash
fern replay forget "src/utils" --dry-run
```

### yes

Skip confirmation prompts. Required in non-interactive or CI environments.

```bash
fern replay forget "src/utils" --yes
```

</Accordion>

<Accordion title="fern api update">
Pulls the latest OpenAPI spec from the specified `origin` in `generators.yml` and
updates the local spec. Alternatively, you can [automate this process by setting up a GitHub Action](/api-definitions/openapi/sync-your-open-api-specification).
Expand Down
73 changes: 46 additions & 27 deletions fern/products/sdks/custom-code.mdx
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
---
title: Adding custom code
headline: Adding custom code (overview)
description: Extend Fern-generated SDKs with custom methods, logic, and dependencies. Use .fernignore to protect your code from being overwritten during regeneration.
description: Extend Fern-generated SDKs with custom methods, logic, and dependencies. Use Fern Replay for line-level edits and .fernignore for files you own end-to-end.
---


Fern-generated SDKs are designed to be extended with custom logic, methods, and dependencies. If you want your SDK to do more than just make basic API calls (like combining multiple calls, processing data, adding utilities), you can add custom code that lives in harmony with the generated code.

You can also add custom methods by inheriting the Fern generated client and extending it, plus add any dependencies that your custom methods depend on in your `generators.yml` file.
Fern provides two complementary tools for keeping your customizations safe across regenerations:

## Using `.fernignore` to preserve your customizations
- **`.fernignore`** — Full-file ownership. The generator stops touching listed files entirely. Use for fully hand-written modules, READMEs, or custom workflows. Trade-off: you also stop receiving generator updates to those files.
- **Replay** — Line-level edits. Keep generated files under generator control, and reapply your edits via 3-way merge on every regeneration.

Once you add files containing custom code, use `.fernignore` to protect your custom code from being overwritten when Fern regenerates your SDK.

Simply add your custom files to the SDK repository and list them in `.fernignore`. Fern won't override any files listed there. A `.fernignore` file is automatically created in your SDK repository when you use GitHub publishing.
## Full-file ownership with `.fernignore` [#fernignore]

<Note>`.fernignore` applies only to SDK generation. It has no effect on [Fern Docs](/learn/docs/getting-started/overview) builds.</Note>

Use `.fernignore` to protect files you own end-to-end from being overwritten during SDK regeneration.

Add your custom files to the SDK repository and list them in `.fernignore`. A `.fernignore` file is automatically created in your SDK repository when you use GitHub publishing.

Your `.fernignore` file might look something like this:

```gitignore title=".fernignore"
Expand All @@ -31,7 +34,8 @@ LICENSE
# Custom code
src/CustomClient.ts
```
<Note>For another example, see Cohere's [`.fernignore` file for their TypeScript SDK](https://github.com/cohere-ai/cohere-typescript/blob/ad583e3003bd51e80a82317f9e16beec85881b86/.fernignore).</Note>

For another real-world example, see Cohere's [`.fernignore` file for their TypeScript SDK](https://github.com/cohere-ai/cohere-typescript/blob/ad583e3003bd51e80a82317f9e16beec85881b86/.fernignore).

You'll have a separate `.fernignore` file for each of your SDKs:

Expand Down Expand Up @@ -61,24 +65,39 @@ You'll have a separate `.fernignore` file for each of your SDKs:
</Folder>
</Files>

## Line-level edits with Replay [#replay]

<Note title="Requirements">
Replay is on by default for SDKs using [`pull-request` output mode](/learn/sdks/reference/generators-yml#pull-request). To enable Replay on an SDK that's currently on `release` or `push` mode, switch to `pull-request` mode via the [migration guide](/learn/sdks/overview/replay-migration).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [vale] reported by reviewdog 🐶
[FernStyles.Current] Avoid time-relative terms like 'currently' that become outdated


Replay requires the latest Fern CLI and SDK generator versions — run `fern upgrade` to make sure you're current.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [vale] reported by reviewdog 🐶
[FernStyles.Current] Avoid time-relative terms like 'latest' that become outdated

</Note>

Replay automatically preserves the edits you make to your generated SDK across regenerations. When you commit a change to generated code, Replay scans your repo's history since the last `[fern-generated]` commit and stores each customer commit as a tracked patch in `.fern/replay.lock`. On the next `fern generate`, those patches are reapplied to the freshly generated SDK via 3-way merge, and the result lands as a `[fern-replay]` commit on top of `[fern-generated]` in the regeneration PR:

```text title="Regeneration PR"
* abc123 (HEAD -> main) [fern-replay] Apply customizations
* 789abc [fern-generated] Update SDK to spec rev 0451
* 234bcd Add helpers on top of User type
* ...
```

If the generator and your customization changed the same lines, Replay reports the conflict in the PR body. Run [`fern replay resolve`](/learn/cli-api-reference/cli-reference/commands#fern-replay-resolve) locally to walk through it.

### Disable Replay

To disable Replay for a specific generator, set `replay.enabled: false` in `generators.yml`. See the [`replay` reference](/learn/sdks/reference/generators-yml#replay) for global and per-generator configuration.

## Customization patterns by language

Each SDK generator has its own conventions for adding custom code: how to extend the generated client, where helpers go, how to declare dependencies. See the guide for your language:

## Augmenting your SDK with custom code

Get started adding custom code to a specific SDK:

<CardGroup cols={3}>
<Card title="TypeScript" icon={<img src="./images/icons/ts-light.svg" alt="TypeScript logo" className="h-6 w-6" noZoom />} href="/sdks/generators/typescript/custom-code">
</Card>
<Card title="Python" icon={<img src="./images/icons/python-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/python/custom-code">
</Card>
<Card title="Go" icon={<img src="./images/icons/go-light.svg" className="h-6 w-6" noZoom />} href="/learn/sdks/generators/go/custom-code">
</Card>
<Card title="Java" icon={<img src="./images/icons/java-light.svg" className="h-6 w-6" noZoom />} href="/learn/sdks/generators/java/custom-code">
</Card>
<Card title=".NET" icon={<img src="./images/icons/csharp-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/csharp/custom-code">
</Card>
<Card title="PHP" icon={<img src="./images/icons/php-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/php/custom-code">
</Card>
<Card title="Ruby" icon={<img src="./images/icons/ruby-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/ruby/custom-code">
</Card>
</CardGroup>
<CardGroup cols={4}>
<Card title="TypeScript" icon={<img src="./images/icons/ts-light.svg" alt="TypeScript logo" className="h-6 w-6" noZoom />} href="/sdks/generators/typescript/custom-code" />
<Card title="Python" icon={<img src="./images/icons/python-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/python/custom-code" />
<Card title="Go" icon={<img src="./images/icons/go-light.svg" className="h-6 w-6" noZoom />} href="/learn/sdks/generators/go/custom-code" />
<Card title="Java" icon={<img src="./images/icons/java-light.svg" className="h-6 w-6" noZoom />} href="/learn/sdks/generators/java/custom-code" />
<Card title=".NET" icon={<img src="./images/icons/csharp-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/csharp/custom-code" />
<Card title="PHP" icon={<img src="./images/icons/php-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/php/custom-code" />
<Card title="Ruby" icon={<img src="./images/icons/ruby-light.svg" className="h-6 w-6" noZoom />} href="/sdks/generators/ruby/custom-code" />
</CardGroup>
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/csharp/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Add custom logic and methods to your .NET SDK with Fern. Extend Bas

This page covers how to add custom logic and methods to your .NET SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>
<Step title="Create a new file and add your custom logic">
```csharp title="src/YourNamespace/Helper.cs"
Expand Down
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/go/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Learn how to add custom logic and methods to your Go SDK with Fern.

This page covers how to add custom logic and methods to your Go SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>

### Create a new file and add your custom logic
Expand Down
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/java/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Augment your Java SDK with custom utilities

This page covers how to add custom logic, methods, and dependencies to your TypeScript SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>

### Create a new file and add your custom logic
Expand Down
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/php/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Learn how to add custom logic, methods, and dependencies to your PH

This page covers how to add custom logic, methods, and dependencies to your PHP SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>
<Step title="Create a new file and add your custom logic">
```php title="src/Helper.php"
Expand Down
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/python/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Learn how to add custom logic, methods, and dependencies to your Py

This page covers how to add custom logic, methods, and dependencies to your Python SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>

### Create a new file and add your custom logic
Expand Down
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/ruby/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Augment your Ruby SDK with custom utilities

This page covers how to add custom logic, methods, and dependencies to your Ruby SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>
<Step title="Create a new file and add your custom logic">
```ruby title="lib/<gem_name>/helper.rb"
Expand Down
4 changes: 2 additions & 2 deletions fern/products/sdks/generators/typescript/custom-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ description: Learn how to add custom logic, methods, and dependencies to your Ty

This page covers how to add custom logic, methods, and dependencies to your TypeScript SDK.

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

## Adding custom logic

To get started adding custom code:

<Markdown src="/products/sdks/snippets/custom-code-callout.mdx"/>

<Steps>

### Create a new file and add your custom logic
Expand Down
31 changes: 31 additions & 0 deletions fern/products/sdks/reference/generators-yml-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,19 @@ autorelease: true
Set to `true` to enable Autorelease, `false` to disable it. [Per-generator settings](#autorelease-2) override this global configuration.
</ParamField>

## `replay`

Override [Fern Replay](/learn/sdks/overview/custom-code#replay) behavior globally. Replay runs based on your organization's configuration; use this setting to disable it across all generators in this `generators.yml`. Alternatively, you can [configure Replay for individual SDKs](#replay-1).

```yaml title="generators.yml"
replay:
enabled: false
```

<ParamField path="replay.enabled" type="boolean" required={false} toc={true}>
Set to `false` to skip Replay for all SDK generations, even if Replay is enabled for your organization. [Per-generator settings](#replay-1) override this global configuration.
</ParamField>

## `aliases`

Define shortcuts that map to multiple generator groups, allowing you to run several groups with a single command. When you run `fern generate --group <alias>`, all groups in the alias run in parallel. You can also set an alias as your `default-group`.
Expand Down Expand Up @@ -1019,6 +1032,24 @@ groups:
Set to `true` to enable Autorelease for this generator, `false` to disable it. Per-generator settings override the global [`autorelease`](#autorelease-1) configuration.
</ParamField>

#### `replay`

Override [Fern Replay](/learn/sdks/overview/custom-code#replay) for an individual SDK. Alternatively, you can [configure Replay globally for all SDKs](#replay).

```yml title="generators.yml" {6-7}
groups:
ts-sdk:
generators:
- name: fernapi/fern-typescript-sdk
...
replay:
enabled: false
```

<ParamField path="replay.enabled" type="boolean" required={false} toc={true}>
Set to `false` to skip Replay for this generator, even if Replay is enabled for your organization or globally in this `generators.yml`. Per-generator settings override the global [`replay`](#replay) configuration.
</ParamField>

#### `snippets`

Configures snippets for a particular generator.
Expand Down
Loading
Loading