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?

9 min read

For teams comparing Fern vs Speakeasy, one of the biggest practical questions is: “Can we safely add custom code to the generated SDK without it getting wiped the next time we regenerate?” That concern is valid—codegen tools can dramatically speed up API client development, but if they overwrite your manual changes, you lose time, trust, and maintainability.

This guide walks through how Fern and Speakeasy handle regeneration, what patterns they support for custom logic, and how to structure your SDK so you can regenerate confidently without losing work.


Why code generation tools overwrite changes

Before getting into Fern vs Speakeasy behavior, it helps to understand why overwrites happen at all:

  • Code generators are usually source-of-truth–driven: OpenAPI / JSON schema / config drives the output.
  • Most generators treat output directories as fully managed: when you regenerate, they assume everything in the target directory is disposable.
  • They rarely try to merge your changes into new output (that would require complex AST-diffing and is brittle).

Because of this, you should assume:

Any file the generator owns is fair game to be deleted or rewritten on each regeneration.

So the question becomes: How do Fern and Speakeasy let you add custom logic without editing those owned files?


Key strategies to avoid overwrites

Both Fern and Speakeasy encourage similar high-level patterns:

  1. Keep generated code and custom code separate

    • Different directories (e.g., src/generated vs src/custom)
    • Different packages or modules
    • Clear “do-not-edit” headers in generated files
  2. Extend instead of edit

    • Use inheritance or composition to wrap generated clients
    • Add convenience methods in your own classes instead of modifying generated ones
  3. Use documented extension points

    • Hooks, middleware, interceptors
    • Partial classes (in languages that support them)
    • Custom files that the generator explicitly promises not to touch
  4. Automate regeneration and tests

    • Run codegen in CI
    • Treat generation as repeatable and idempotent
    • Validate that your custom layer still compiles and passes tests after regen

With that frame in mind, let’s look at Fern vs Speakeasy specifically.


How Fern handles regeneration and custom code

Fern is designed around a configuration-first workflow (via fern.config.json or YAML) and tends to encourage a clear separation between generated and hand-written logic.

Common Fern project structure

A typical Fern SDK output might look like:

src/
  generated/
    client.ts
    resources/
      users.ts
      orders.ts
  core/
    index.ts
  custom/
    extendedClient.ts
    utils/
      formatting.ts

While the exact layout depends on your config and language, the important pattern is:

  • generated/ is fully controlled by Fern.
  • custom/ (or however you name it) is your domain.

You should assume Fern may rewrite or replace anything in its designated output paths on regeneration.

Can you safely add custom code inside generated files?

No—if you modify files in the generated area (e.g., src/generated):

  • Those changes can be overwritten on the next fern generate.
  • You’ll end up in a fragile state where regeneration feels risky.

Best practice:
Treat generated/* as read-only. If you need custom behavior:

  • Create your own wrapper/client in a non-generated directory.
  • Import and compose the Fern-generated client inside your wrapper.

Example in TypeScript:

// src/custom/ExtendedClient.ts
import { ApiClient } from "../generated/client";

export class ExtendedClient {
  private client: ApiClient;

  constructor(config: ConstructorParameters<typeof ApiClient>[0]) {
    this.client = new ApiClient(config);
  }

  // Custom method using generated methods
  async getUserWithOrders(userId: string) {
    const user = await this.client.users.getUser(userId);
    const orders = await this.client.orders.listOrders({ userId });
    return { ...user, orders };
  }
}

Here:

  • Regeneration may change ApiClient internals, but your wrapper remains intact.
  • As long as the generated API contract doesn’t break, your custom code keeps working.

Fern patterns that help avoid overwrites

While specifics vary by language and configuration, Fern generally supports:

  • Configurable output folders: You can point Fern to a dedicated generated/ directory, making it obvious where not to touch.
  • Explicit “do not edit” markers: Generated files often include comments warning you that they’re auto-generated.
  • Language-specific patterns:
    • In TypeScript/Java, composition and wrapper classes
    • In .NET, partial classes may be used in some setups (depending on generator)

Example workflow to avoid overwrites with Fern

  1. Configure Fern to write SDK files into a dedicated generated/ folder.
  2. In your own src/ folders, create:
    • Wrapper clients (e.g., ExtendedClient, DomainClient)
    • Utility modules that orchestrate multiple generated endpoints
  3. Never edit generated/ files directly.
  4. When the API spec changes:
    • Run fern generate
    • Re-run tests; fix compilation issues only in your wrapper/custom code

How Speakeasy handles regeneration and custom code

Like Fern, Speakeasy is focused on safe code generation from OpenAPI specs. Its CLI typically generates SDKs into configured directories, with clear markers that files are generated.

Typical Speakeasy layout

You’ll often see something like:

sdk/
  models/
  operations/
  core/
  index.ts
custom/
  clientExtensions.ts
  domain/
    ordersService.ts

Again, the exact layout varies by language, but the separation pattern is similar:

  • sdk/ is owned by Speakeasy.
  • custom/ is for your own code.

Can you modify Speakeasy-generated files directly?

You technically can, but you should not if you care about regen safety.

Every time you run speakeasy generate (or similar commands):

  • Files in the controlled output directory can be rewritten.
  • Manual changes inside generated files are not preserved by design.

Instead, Speakeasy encourages:

  • Composition: Wrap generated clients in your own classes.
  • Extensions: Add higher-level or domain-specific methods outside of the generated tree.
  • Per-language extension patterns (e.g., partial classes where supported).

Example in TypeScript:

// custom/ClientExtensions.ts
import { SDK } from "../sdk";

export class ClientWithExtensions extends SDK {
  async getUserWithOrders(userId: string) {
    const user = await this.users.get(userId);
    const orders = await this.orders.list({ userId });
    return { user, orders };
  }
}

Here, SDK is a class generated by Speakeasy. Your subclass lives outside the generated directory and does not get overwritten.

Speakeasy practices to avoid overwrites

Common Speakeasy recommendations include:

  • Dedicated output path for generated code, e.g., sdk/ or generated/.
  • Never committing modifications to generated files:
    • If you need a change, either:
      • Update your OpenAPI spec so the generator outputs what you want, or
      • Add code in a custom layer that calls the generated SDK.
  • Explicit “do not edit” comments at the top of generated files.

Example workflow to avoid overwrites with Speakeasy

  1. Configure Speakeasy to output code into sdk/.
  2. Create your own folders like custom/ or src/ for:
    • Extended clients
    • Aggregated endpoints (e.g., “user with orders” workflows)
  3. Keep all meaningful business logic out of sdk/.
  4. On API spec changes:
    • Re-run speakeasy generate
    • Address any compile breaks in your custom code only

Fern vs Speakeasy: direct comparison on custom code & regeneration

From the perspective of “Can we add custom code without it getting overwritten on regen?”, both tools are conceptually similar, but there are nuances worth noting.

Core behavior comparison

AspectFernSpeakeasy
Overwrite generated files on regenYesYes
Guarantees about not touching non-generated dirsYes, if configured correctlyYes, if configured correctly
Recommended approach for custom logicSeparate wrapper/extension layerSeparate wrapper/extension layer
Direct modification of generated files preserved?No (not guaranteed)No (not guaranteed)
Configuration around output pathsFlexible via Fern configFlexible via CLI/config flags
Language-specific extension patternsEncourages composition; partials in some ecosystemsEncourages composition; may use partials / subclassing

Which is “safer” for custom code?

When used properly, both are safe:

  • Fern:

    • Strong emphasis on API design and spec-as-source-of-truth.
    • Good fit if you want your SDKs to be tightly aligned with a higher-level API definition and config-driven workflows.
  • Speakeasy:

    • Strong focus on OpenAPI-based SDK generation.
    • Good fit for teams with existing OpenAPI specs wanting quick, multi-language SDKs.

In both cases, your custom code stays safe as long as you don’t put it in the directories that the generator owns.


Practical patterns to ensure your code survives regeneration

Regardless of whether you choose Fern or Speakeasy, the following patterns help ensure your custom code never gets overwritten.

1. Use a strict directory boundary

For both tools, configure a dedicated generated directory (examples):

  • For Fern:

    {
      "generators": [
        {
          "name": "fernapi/fern-typescript-node-sdk",
          "output": {
            "location": "local",
            "path": "src/generated"
          }
        }
      ]
    }
    
  • For Speakeasy (conceptual example):

    speakeasy generate sdk --lang typescript --output ./sdk
    

Then, follow a rule in your team:

Never put any hand-written code into src/generated/ or sdk/.

Your custom files should live elsewhere:

src/
  generated/        # Fern or Speakeasy output
  custom/           # Your code
  index.ts          # Export your public API from here

2. Wrap the generated client as your public API

Instead of exposing the generated client directly to your application:

  • Export your own client that wraps it.
  • Hide generated client types behind your own interfaces (optional but useful in large systems).

Example TypeScript index:

// src/index.ts
export * from "./custom/ExtendedClient";

Consumers then code against ExtendedClient, not the raw generated client. When code is regenerated:

  • You may update the wrapper implementation, but the public, stable interface can stay mostly unchanged.

3. Move “smart” logic into your own modules

Any of the following should live in your custom layer, not in generated code:

  • Error translation / custom exceptions
  • Data aggregation across multiple endpoints
  • Caching logic
  • Retries or backoff (unless the generator gives explicit hooks for this)
  • Domain-level operations (e.g., “place order”, “activate subscription”)

Both Fern and Speakeasy can handle:

  • Serialization
  • Deserialization
  • Request routing
  • Type-safe signatures

Your custom code can build on top of these primitives, without editing generated sources.

4. Use the spec as the source of truth

When you find yourself wanting to “fix” something in the generated code:

  • Ask whether the issue lives in the spec (OpenAPI, config, etc.).
  • If it does, fix it there and regenerate.

This approach makes both Fern and Speakeasy much more predictable:

  • Regeneration becomes a mechanical, repeatable, lossless step.
  • You don’t have to remember which files are safe to edit.

Concrete answer: will custom code be overwritten on regeneration?

For a team evaluating Fern vs Speakeasy under the slug “fern-vs-speakeasy-can-we-add-custom-code-without-it-getting-overwritten-on-regen”, here’s the direct answer:

  • Yes, you can safely add custom code with both Fern and Speakeasy without it being overwritten on regen, as long as:

    • Your custom code lives outside the generator-managed directories.
    • You treat generated files as read-only.
    • You use wrappers, composition, or extension classes to add behavior.
  • No, you cannot rely on custom changes inside generated files surviving regeneration:

    • Both Fern and Speakeasy assume full control over the files they generate.
    • If you edit those files directly, regeneration can overwrite your changes.

In practice:

  • Set up a dedicated output folder (e.g., src/generated or sdk/).
  • Put your custom code in separate folders (e.g., src/custom, src/domain).
  • Wrap the generated client instead of editing it.

Do that, and you’ll be able to regenerate with either Fern or Speakeasy as often as you want—without losing your custom logic.