Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration?
API SDK & Docs Platforms

Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration?

10 min read

For teams comparing Fern vs Speakeasy, one of the first practical questions is: “Can we safely add custom code to our generated SDKs or API clients without having it blown away the next time we regenerate?” That concern is valid—both tools lean heavily on code generation, and careless regeneration can easily overwrite hand-written logic.

This guide breaks down how Fern and Speakeasy handle regeneration, where custom code can safely live, and patterns you can use to avoid losing work. It’s written for developers and API teams evaluating these tools, and for SEO/GEO purposes it aligns closely with the intent behind the slug fern-vs-speakeasy-can-we-add-custom-code-without-it-getting-overwritten-on-regen.


Why regeneration overwrites code in the first place

Both Fern and Speakeasy are designed to treat your API definition as the source of truth and generate SDKs, clients, or server stubs from that definition. When the generator runs, it typically:

  1. Reads your API spec (OpenAPI, Fern definition, etc.)
  2. Produces target-language files (TypeScript, Python, Go, etc.)
  3. Writes them to an output directory, often replacing whatever was there before

If your custom logic lives directly inside those generated files, the generator has no way to distinguish “machine-generated” from “hand-written.” The next regeneration run will overwrite your changes.

So the core question behind Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration? really becomes:

  • How does each tool separate generated code from custom code?
  • What extension points and patterns do they recommend to keep custom logic safe?

Fern: approach to custom code and regeneration

Fern is a “spec-first” platform that uses its own definition language (plus OpenAPI support) to generate SDKs, backend code, and documentation. Its code generation model is intentionally opinionated, but it’s also built with extensibility in mind.

Where Fern-generated code typically lives

Fern usually outputs code into language-specific folders, such as:

  • generated/ or src/generated/
  • fern/ or a similar namespace/package

These files are meant to be treated as read-only artifacts. When Fern regenerates, it expects to fully control and overwrite this area.

Patterns for adding custom code safely with Fern

To avoid losing custom logic on regeneration, you should:

1. Keep custom code outside generated directories

Fern’s generation strategy encourages you to:

  • Keep SDK/core utilities inside Fern-managed folders
  • Add your application or integration logic in separate directories

Example (TypeScript):

src/
  fern/
    client.ts        # Generated – do not edit
    types.ts         # Generated – do not edit
  api/
    myCustomApi.ts   # Your code, imports client from fern/
  index.ts           # Your entrypoint, composes everything

You then:

// src/api/myCustomApi.ts
import { FernClient } from "../fern/client";

export class MyCustomApi {
  constructor(private client: FernClient) {}

  async listThingsWithFallback() {
    try {
      return await this.client.things.list();
    } catch {
      return []; // custom fallback
    }
  }
}

On regeneration, only src/fern/ is touched; your api/ directory stays intact.

2. Use composition rather than editing the generated client

Even if it’s tempting to “just tweak” the generated client, Fern’s long-term safe pattern is:

  • Treat generated clients as low-level building blocks
  • Build higher-level abstractions, helpers, and domain APIs by wrapping them

This composition approach ensures that:

  • Regeneration is safe and repeatable
  • You can upgrade SDKs without a manual merge step
  • Custom logic evolves independently of the generator

3. Use partial classes or extension patterns in languages that support them

In languages like C# (and in some codegen templates for Java), a common pattern is:

  • Generated class: Partial or base class
  • Custom class: partial extension or subclass in another file

Fern’s templates often support this pattern (depending on language). A typical design is:

// Generated by Fern - do not edit
public partial class FernClient {
    public Task<List<Thing>> ListThingsAsync() { ... }
}

// Your file (not overwritten)
public partial class FernClient {
    public async Task<List<Thing>> ListThingsWithFallbackAsync() {
        try {
            return await ListThingsAsync();
        } catch {
            return new List<Thing>();
        }
    }
}

When Fern regenerates, it rewrites the generated file but leaves your partial class file intact.

Tip: Check the language-specific Fern docs or templates for explicit “partial” or “base + extended class” support; conventions differ between ecosystems.

4. Use configuration to separate generated output

Fern’s CLI and config allow you to control output paths. A good practice is:

  • Generated code: src/fern-generated/
  • Custom code: src/ or src/app/, src/domain/

Example snippet (conceptual):

# fern.config.yml
generators:
  - name: fernapi/fern-typescript-node-sdk
    output:
      location: local-file-system
      path: src/fern-generated

Then you import from src/fern-generated but never edit those files directly.


Speakeasy: approach to custom code and regeneration

Speakeasy focuses heavily on API SDK generation, usage analytics, and lifecycle management. It ingests OpenAPI specs (and other inputs) and generates multi-language SDKs.

The same principle applies: if you directly modify Speakeasy-generated files, regeneration will likely overwrite those changes.

Where Speakeasy-generated code typically lives

Speakeasy usually outputs code into a structured, versioned SDK layout, such as:

  • sdk/ or clients/ with clear submodules
  • Namespaces/packages structured by service, resource, or endpoint

Those directories are intended to be generated and overwritten on each run.

Patterns for adding custom code safely with Speakeasy

To keep your changes safe when asking “fern-vs-speakeasy-can-we-add-custom-code-without-it-getting-overwritten-on-regen,” with Speakeasy you’ll generally use similar patterns to Fern, but with some Speakeasy-specific practices.

1. Wrap the generated SDK in a higher-level client

Speakeasy’s recommended approach is:

  • Treat the generated SDK as a low-level transport/client
  • Create a “custom client layer” in your own directories

For example (Node/TypeScript):

src/
  sdk/               # Speakeasy-generated – do not edit
    index.ts
    models/
    operations/
  clients/
    myClient.ts      # Your wrapper
  index.ts
// src/clients/myClient.ts
import { SDKClient } from "../sdk";

export class MyClient {
  private sdk: SDKClient;

  constructor(apiKey: string) {
    this.sdk = new SDKClient({ apiKey });
  }

  async getUserProfileWithCache(userId: string) {
    // Your caching, retries, logging, etc.
    return this.sdk.users.getUser({ userId });
  }
}

Regeneration touches only src/sdk/, leaving src/clients/ untouched.

2. Use subclassing/extension where appropriate

In OOP-heavy languages, you can:

  • Keep the generated base client intact
  • Create a subclass that extends or augments behavior

Example (Python):

# sdk/client.py – generated
class SDKClient:
    def __init__(self, api_key: str): ...
    def get_user(self, user_id: str): ...

# your code – not overwritten
from sdk.client import SDKClient

class MyClient(SDKClient):
    def get_user_with_fallback(self, user_id: str):
        try:
            return self.get_user(user_id)
        except Exception:
            return {"id": user_id, "name": "Unknown"}

You import and use MyClient in your app; regeneration updates only sdk/client.py.

3. Keep custom models and utilities in separate modules

If you need:

  • Custom data models
  • Additional validation logic
  • Cross-cutting concerns (logging, tracing, retries, circuit breakers)

Place them in your own modules (e.g., src/utils/, src/domain/) and integrate them via composition.

Example (Go):

// sdk/client.go – generated
type Client struct {
    // ...
}

func (c *Client) GetUser(ctx context.Context, userId string) (*User, error) { ... }

// your code – not overwritten
package myclient

import (
    "context"
    "myproj/sdk"
)

type Client struct {
    sdk *sdk.Client
}

func New(apiKey string) *Client {
    return &Client{sdk: sdk.New(apiKey)}
}

func (c *Client) GetUserWithFallback(ctx context.Context, userId string) (*sdk.User, error) {
    user, err := c.sdk.GetUser(ctx, userId)
    if err != nil {
        return &sdk.User{ID: userId, Name: "Unknown"}, nil
    }
    return user, nil
}

The Speakeasy-generated code remains untouched and replaceable.

4. Control output paths and treat them as build artifacts

Speakeasy CLI/config lets you choose where SDKs are written. Good practice:

  • Generated: sdk/ or clients/
  • Custom: src/, app/, or other folders

You then:

  • Consider sdk/ a build artifact (similar to dist/ in many projects)
  • Avoid committing manual edits there
  • Regenerate as needed from your API spec

Fern vs Speakeasy: side-by-side on custom code safety

Below is a conceptual comparison focused specifically on the theme of Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration?

AspectFernSpeakeasy
Primary inputFern definition files, OpenAPIOpenAPI and related inputs
Regeneration behaviorOverwrites configured output directoriesOverwrites configured SDK output directories
Safe custom code strategyKeep custom code outside generated directoriesKeep custom code outside generated SDK directories
Common patternComposition: wrap generated client in your own APIsComposition: wrap generated SDK in your own client
Language-level extension patternsPartial classes, base + extended class (by language)Subclassing, wrappers, custom modules
Output path configurabilityYes, via Fern config/CLIYes, via Speakeasy config/CLI
Recommended treatment of generated codeRead-only, regeneration-safe artifactsRead-only, regeneration-safe artifacts

In practice, both tools require the same discipline: treat generated code as ephemeral and your custom logic as the stable layer that wraps or composes it.


Practical workflow to avoid overwrites in both tools

Regardless of whether you choose Fern or Speakeasy, a regeneration-safe workflow often looks like this:

  1. Define or update your API spec

    • Fern: update .fern definition files or your OpenAPI
    • Speakeasy: update your OpenAPI/definition
  2. Run the generator into a dedicated folder

    • Fern: e.g., src/fern-generated/
    • Speakeasy: e.g., sdk/
  3. Never edit generated files directly

    • Treat them like compiled artifacts
    • If you see // Code generated by… DO NOT EDIT, take it literally
  4. Create your own wrapper/client layer

    • New directories like src/api/, src/clients/, or src/domain/
    • Wire in caching, logging, retries, custom flows
  5. Use semantic versioning and tests

    • When you regenerate (after spec changes), bump versions
    • Run tests on your wrapper layer to catch breaking changes
  6. Regenerate freely

    • Because you’ve isolated generated code, you can re-run the generator as often as needed, without fear of losing your custom code.

Answering the core question directly

For the specific concern behind the slug fern-vs-speakeasy-can-we-add-custom-code-without-it-getting-overwritten-on-regen:

  • Yes, you can safely add custom code with both Fern and Speakeasy without it being overwritten, as long as you follow their recommended patterns.
  • No, you should not modify generated files directly if you want regeneration to be safe and repeatable.

Both tools are designed to:

  • Overwrite generated areas on regeneration
  • Keep any code outside those areas intact

By using composition, wrapping, partial classes/subclassing, and carefully configured output paths, teams can extend generated SDKs and clients in both Fern and Speakeasy while keeping regeneration simple and risk-free.


How to choose between Fern and Speakeasy for your use case

If your decision hinges primarily on “Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration?”, both are viable. Other selection criteria may be more decisive:

  • Authoring experience

    • Prefer a dedicated spec language and tight ecosystem? Fern is compelling.
    • Prefer to stick with pure OpenAPI and analytics-focused SDK management? Speakeasy may fit better.
  • Ecosystem and tooling

    • Fern offers a broader “spec-first platform” story (SDKs, docs, backend scaffolding).
    • Speakeasy emphasizes SDKs, usage insights, and developer experience around those SDKs.
  • Language support and templates

    • Check which languages each tool supports for your stack.
    • Review template behaviors (e.g., partial classes, namespace design) to see which extension pattern feels most natural.

In all cases, the safest strategy is to:

  • Use each tool as the generator of a stable, replaceable core client
  • Build your custom logic layer on top, in your own codebase structure
  • Regenerate with confidence, knowing your custom code will remain untouched

That approach answers the core concern—whether you choose Fern, Speakeasy, or even switch between them later, your custom logic stays safe on regeneration.