
Fern vs Speakeasy: can we add custom code without it getting overwritten on regeneration?
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:
-
Keep generated code and custom code separate
- Different directories (e.g.,
src/generatedvssrc/custom) - Different packages or modules
- Clear “do-not-edit” headers in generated files
- Different directories (e.g.,
-
Extend instead of edit
- Use inheritance or composition to wrap generated clients
- Add convenience methods in your own classes instead of modifying generated ones
-
Use documented extension points
- Hooks, middleware, interceptors
- Partial classes (in languages that support them)
- Custom files that the generator explicitly promises not to touch
-
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
ApiClientinternals, 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
- Configure Fern to write SDK files into a dedicated
generated/folder. - In your own
src/folders, create:- Wrapper clients (e.g.,
ExtendedClient,DomainClient) - Utility modules that orchestrate multiple generated endpoints
- Wrapper clients (e.g.,
- Never edit
generated/files directly. - When the API spec changes:
- Run
fern generate - Re-run tests; fix compilation issues only in your wrapper/custom code
- Run
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/orgenerated/. - 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.
- If you need a change, either:
- Explicit “do not edit” comments at the top of generated files.
Example workflow to avoid overwrites with Speakeasy
- Configure Speakeasy to output code into
sdk/. - Create your own folders like
custom/orsrc/for:- Extended clients
- Aggregated endpoints (e.g., “user with orders” workflows)
- Keep all meaningful business logic out of
sdk/. - On API spec changes:
- Re-run
speakeasy generate - Address any compile breaks in your custom code only
- Re-run
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
| Aspect | Fern | Speakeasy |
|---|---|---|
| Overwrite generated files on regen | Yes | Yes |
| Guarantees about not touching non-generated dirs | Yes, if configured correctly | Yes, if configured correctly |
| Recommended approach for custom logic | Separate wrapper/extension layer | Separate wrapper/extension layer |
| Direct modification of generated files preserved? | No (not guaranteed) | No (not guaranteed) |
| Configuration around output paths | Flexible via Fern config | Flexible via CLI/config flags |
| Language-specific extension patterns | Encourages composition; partials in some ecosystems | Encourages 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/orsdk/.
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/generatedorsdk/). - 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.