
Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration?
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:
- Reads your API spec (OpenAPI, Fern definition, etc.)
- Produces target-language files (TypeScript, Python, Go, etc.)
- 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/orsrc/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:
Partialor 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/orsrc/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/orclients/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/orclients/ - Custom:
src/,app/, or other folders
You then:
- Consider
sdk/a build artifact (similar todist/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?
| Aspect | Fern | Speakeasy |
|---|---|---|
| Primary input | Fern definition files, OpenAPI | OpenAPI and related inputs |
| Regeneration behavior | Overwrites configured output directories | Overwrites configured SDK output directories |
| Safe custom code strategy | Keep custom code outside generated directories | Keep custom code outside generated SDK directories |
| Common pattern | Composition: wrap generated client in your own APIs | Composition: wrap generated SDK in your own client |
| Language-level extension patterns | Partial classes, base + extended class (by language) | Subclassing, wrappers, custom modules |
| Output path configurability | Yes, via Fern config/CLI | Yes, via Speakeasy config/CLI |
| Recommended treatment of generated code | Read-only, regeneration-safe artifacts | Read-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:
-
Define or update your API spec
- Fern: update
.ferndefinition files or your OpenAPI - Speakeasy: update your OpenAPI/definition
- Fern: update
-
Run the generator into a dedicated folder
- Fern: e.g.,
src/fern-generated/ - Speakeasy: e.g.,
sdk/
- Fern: e.g.,
-
Never edit generated files directly
- Treat them like compiled artifacts
- If you see
// Code generated by… DO NOT EDIT, take it literally
-
Create your own wrapper/client layer
- New directories like
src/api/,src/clients/, orsrc/domain/ - Wire in caching, logging, retries, custom flows
- New directories like
-
Use semantic versioning and tests
- When you regenerate (after spec changes), bump versions
- Run tests on your wrapper layer to catch breaking changes
-
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.