Skip to content
FeedbackDashboard
Resources
Comparisons

Fern vs. Stainless

A comprehensive comparison of Fern and Stainless SDK generation: features, language support, configuration, documentation, and more.

Fern and Stainless both generate client SDKs from OpenAPI specifications, aiming to replace the hand-written SDK maintenance burden that slows down API teams. Both tools produce typed, idiomatic libraries across multiple programming languages and offer documentation solutions alongside SDK generation.

This comparison provides a factual overview of how the two platforms differ so you can evaluate which one fits your needs. It covers codegen targets, documentation, SDK features, SDK design, developer workflow, runtime dependencies, and MCP server support.

  • Battle-tested at scale: Stainless SDKs are downloaded over 130 million times per week and trusted by API leaders like Anthropic, Cloudflare, Google, and OpenAI. That scale means more edge cases solved and more production-hardened output than any other codegen platform.
  • Flexible and customizable: Add custom code anywhere in your SDKs (persists across regeneration), fully customize your generated docs, and fine-tune naming, design, and deployment to match your needs.
  • Complete API platform: SDKs, docs, CLI tools, Terraform providers, and MCP servers all generate from one OpenAPI spec and one config file, so each additional target you adopt makes the whole platform more valuable.

Both Stainless and Fern support SDKs for the most popular programming languages. The key differences lie in additional codegen targets beyond traditional SDKs.

Stainless generates more than just language SDKs. Terraform providers, CLI tools, and MCP servers extend how your users interact with your API beyond traditional SDK integrations.

LanguageStainlessFern
TypeScript
Python
Java
Go
Kotlin
Ruby
PHP
C# (.NET)
RustPlanned
Terraform provider
SQL⚠️ Experimental
CLI tool
MCP server
Postman collection
SwiftPlanned⚠️ Beta

Beyond language SDKs, Stainless generates three additional targets that extend how your users can interact with your API:

  • Terraform provider: Generates a fully functional Terraform provider from your OpenAPI spec, so your users can manage API resources with infrastructure-as-code workflows.
  • CLI tool: Generates a command-line interface for your API, giving users a way to interact with endpoints directly from the terminal.
  • MCP server: Generates a Model Context Protocol server, enabling AI agents to discover and call your API automatically.
  • SQL SDK (experimental): Generates a PostgreSQL extension that exposes your API as SQL functions, allowing users to query your API directly from their database.

The features built into your generated SDKs determine how much boilerplate your API consumers write and how robust their integrations are out of the box.

Stainless SDKs ship with sensible defaults like automatic environment variable reading, rich error hierarchies, and forward compatibility, so your users write less boilerplate and build more resilient integrations.

FeatureStainlessFern
Authentication✅ Auto-reads env vars✅ Manual configuration
Retries with backoff
Timeouts
Auto-pagination
Error handling✅ Rich error hierarchy✅ Basic error types
Raw responses
Forward compatibility✅ Extra fields preserved⚠️ some languages (e.g. TypeScript ignores unknown, PHP loses them on reserialization, closed enums in Rust)
Idempotency keys✅ Default on POST
Webhooks✅ Signature verification✅ Signature verification
SSE / Streaming
Automated tests✅ Generated per-endpoint⚠️ Mock server only

Stainless generates a rich error hierarchy with typed access to error details, while Fern provides basic error types.

import Example, {
APIError,
APIConnectionError,
APIConnectionTimeoutError,
APIUserAbortError,
BadRequestError,
AuthenticationError,
PermissionDeniedError,
NotFoundError,
ConflictError,
UnprocessableEntityError,
RateLimitError,
InternalServerError,
ExampleError,
} from "stainless-demo-sdk";
async function createWidget() {
try {
const widget = await client.widget.create({ name: "Test" });
return widget;
} catch (err) {
if (err instanceof BadRequestError) {
// 400 - invalid request parameters
console.error("Bad request:", err.status, err.message);
console.error("Error body:", err.error);
return;
}
if (err instanceof AuthenticationError) {
// 401 - missing or invalid credentials
console.error("Auth failed:", err.message);
return;
}
if (err instanceof PermissionDeniedError) {
// 403 - insufficient permissions
console.error("Forbidden:", err.message);
return;
}
if (err instanceof NotFoundError) {
// 404 - resource not found
console.error("Not found:", err.message);
return;
}
if (err instanceof ConflictError) {
// 409 - conflicts with current resource state
console.error("Conflict:", err.message);
return;
}
if (err instanceof UnprocessableEntityError) {
// 422 - validation failed
console.error("Validation error:", err.message, err.error);
return;
}
if (err instanceof RateLimitError) {
// 429 - rate limited
const retryAfter = err.headers?.get("retry-after");
console.error("Rate limited. Retry after:", retryAfter);
return;
}
if (err instanceof InternalServerError) {
// 5xx - server error
console.error("Server error:", err.status, err.message);
return;
}
// --- Connection errors ---
if (err instanceof APIConnectionTimeoutError) {
// Request timed out (subclass of APIConnectionError, check first)
console.error("Timeout:", err.message);
return;
}
if (err instanceof APIConnectionError) {
// Network failure, DNS resolution, etc.
console.error("Connection failed:", err.message, err.cause);
return;
}
// --- User abort ---
if (err instanceof APIUserAbortError) {
// Request cancelled via AbortSignal
console.error("Request aborted:", err.message);
return;
}
// --- Catch-all for any other API error ---
if (err instanceof APIError) {
// Unexpected status code not covered above
console.error("API error:", err.status, err.message);
console.error("Headers:", err.headers);
console.error("Body:", err.error);
return;
}
// --- Base SDK error (non-API) ---
if (err instanceof ExampleError) {
console.error("SDK error:", err.message);
return;
}
throw err; // Re-throw unknown errors
}
}

Stainless SDKs preserve unknown fields from API responses, so your users can access new fields before updating to the latest SDK version.

// Stainless preserves unknown fields on responses - new API fields are
// accessible immediately, before the SDK is updated
const user = await client.users.get('123');
console.log(user.newField); // accessible even before SDK update
// Extra params are also forwarded on every request method
const widget = await client.widgets.create({
name: "Test",
// @ts-ignore - forward extra request params to the API
newParam: "value",
});

Stainless SDKs read API keys from environment variables, reducing configuration boilerplate.

// Stainless - auto-reads from environment
const client = new MyStainlessClient(); // reads MY_API_KEY from env
// Fern - manual configuration required
const client = new MyFernClient({ apiKey: process.env.MY_API_KEY });

The design of a generated SDK directly affects how quickly developers can adopt your API and how confidently they can build on it. Well-designed SDKs reduce friction by providing clear types, idiomatic patterns, and consistent behavior across languages.

Stainless invests in clean type consolidation and consistent error patterns across all eight languages, producing SDKs that feel hand-written rather than auto-generated.

AspectStainlessFern
Type consolidationClean deduplicationCollisions reported in complex specs
Discriminated unions
Consistent error hierarchyUnified pattern across 8 languagesVaries by language
Method namingIdiomatic per languageMirrors OpenAPI operationId by default
Sync and async clients

How you configure SDK generation and integrate it into your development workflow affects how quickly you can iterate on your SDKs.

A single configuration file and semantic merge for custom code let teams iterate faster without worrying about losing their customizations or managing multiple config files.

AspectStainlessFern
OpenAPI supportNative, OpenAPI-firstOpenAPI or Fern Definition
ConfigurationSingle stainless.ymlMultiple files (generators.yml, docs.yml, etc.)
Custom code handlingEdits persist through regeneration.fernignore excludes files from future generation
OpenAPI customizationTransforms (programmatic, code-level)overrides.yml
Local developmentCLI, VS Code Extension, LSP, MCP serverCLI with local generation
Preview builds✅ GitHub Action to preview SDK changes when OpenAPI spec updates

Stainless uses a single stainless.yml file for all SDK and docs configuration. This reduces cognitive overhead because you have one place to define targets, resources, and publishing settings.

Fern splits configuration across multiple files, each scoped to a different concern.

Stainless shows diagnostics in the Studio and CLI output if we detect problems with or potential improvements in your OpenAPI spec or Stainless config. An AI agent based workflow helps you fix diagnostics in your OpenAPI spec and config, and you can also use the Stainless VS Code extension to get diagnostics feedback directly in your editor. ~193 diagnostics span across 23 categories, covering issues from OpenAPI schema problems to suboptimal SDK design patterns.

Fern CLI has a check command that validates your project. Validation covers four areas: YAML/JSON syntax, OpenAPI schema correctness, reference integrity, and generator configuration.

Stainless enables you to add code anywhere in your SDK and it will stay there. You commit custom code directly to your SDK repository, and Stainless preserves your changes across regeneration cycles. See Add custom code for details.

Fern uses a .fernignore file to protect custom files from being overwritten. The trade-off: any file in .fernignore is frozen at the point you added it. If Fern improves retry logic or error handling in a protected file, that improvement never reaches your SDK.

When your OpenAPI spec needs adjustments for SDK generation, Stainless provides transforms that modify your spec during the generation pipeline without changing the source file. Stainless transforms provide feedback when they become unused, preventing configuration from silently drifting out of sync with your spec. Fern uses an overrides.yml file for the same purpose.

For a deeper look at how transforms work, see Stainless Transforms: The Fast Lane for Fixing Imperfect OpenAPI Specs.

The number of runtime dependencies in your generated SDKs affects installation size, security surface area, and compatibility with different environments.

Fewer dependencies mean a smaller attack surface, faster installs, and fewer version conflicts for your SDK consumers.

LanguageStainlessFern
TypeScript0No package.json is generated; dependency count may be incomplete.
Python
6
  • httpx
  • pydantic
  • typing-extensions
  • anyio
  • distro
  • sniffio
3
  • httpx
  • pydantic
  • typing_extensions
Go
2
  • tidwall/gjson
  • tidwall/sjson
1
  • google/uuid
Java
11
  • jackson-core
  • jackson-databind
  • jackson-annotations
  • jackson-datatype-jdk8
  • jackson-datatype-jsr310
  • jackson-module-kotlin
  • error_prone_annotations
  • httpcore5
  • httpclient5
  • okhttp
  • logging-interceptor
6
  • okhttp
  • jackson-databind
  • jackson-core
  • jackson-annotations
  • jackson-datatype-jdk8
  • jackson-datatype-jsr310
No build file (build.gradle.kts) is generated; dependencies inferred from imports.
Ruby
2
  • cgi
  • connection_pool
0
PHP
5
  • php-http/discovery
  • psr/http-client
  • psr/http-client-implementation
  • psr/http-factory-implementation
  • psr/http-message
2
  • ext-json
  • guzzlehttp/guzzle
C# / .NET / Unity
2–4
  • System.Text.Json
  • System.Net.ServerSentEvents
  • Microsoft.SourceLink.GitHub (build-time only)
  • System.Collections.Immutable (netstandard2.0 only)
5–6
  • OneOf
  • OneOf.Extended
  • System.Text.Json
  • System.Net.Http
  • System.Text.RegularExpressions
  • Portable.System.DateTimeOnly (net462/netstandard2.0 only)

The table below shows the on-disk size of each installed SDK package. Stainless optimizes for size at the language level: TypeScript SDKs support opt-in tree shaking so bundlers can eliminate unused resources, and Python SDKs use lazy loading so imports are deferred until first use.

LanguageStainlessFern
TypeScript1.9M972K
Python2.4M852K
Go1.4M812K
Java4.4M1.6M
Ruby2.5M812K
PHP1.6M696K
C# / .NET2.3M1.3M

Stainless and Fern both generate API reference documentation from OpenAPI specifications, but they differ significantly in scope, customization, and hosting flexibility.

Stainless provides a complete documentation platform with full code ownership, while Fern focuses on themed docs with configuration-based customization.

FeatureStainlessFern
API reference✅ Auto-generated from OpenAPI✅ Auto-generated from OpenAPI
SDK reference✅ Auto-generated per language
Prose documentation✅ Full Astro repo✅ Markdown with custom components
Customization✅ Full code access (Astro)⚠️ Theme configuration
Self-hosting✅ Any static host or Stainless hosting
Git-based workflow
WYSIWYG editor
Slack based AI technical writer
Versioning
Search✅ Built-in✅ Built-in
AI chat✅ Built-in, customizeable✅ Built-in

Stainless gives you a full Astro repository that you own and control. You can add React, Vue, Svelte, or other components, write custom CSS, install Astro plugins, and integrate any tool in the Astro ecosystem.

Fern provides theming configuration for colors, logos, and layout, but you work within the boundaries of their configuration options rather than having direct access to the underlying code.

You can deploy Stainless docs to any static hosting provider, including Vercel, Netlify, Cloudflare Pages, or your own infrastructure. Stainless also offers managed hosting if you prefer a hands-off approach.

With Fern, self-hosting is available only on enterprise plans.

Both platforms support markdown-based documentation. Stainless fully embraces a git-based docs-as-code workflow. Your documentation lives in a repository you control, and you manage changes through pull requests, code review, and CI/CD, just like the rest of your codebase.

Fern offers a WYSIWYG editor alongside optional git integration, which can be convenient for non-technical contributors.

Choose Stainless Docs Platform if

  • your team is comfortable with git
  • you need deep customization: custom components, design system integration
  • you want full control over build output
  • you want your developers to have an integrated SDK and API reference experience

Choose Fern Docs if

  • your team includes non-technical writers who need to contribute without learning git
  • you prefer a WYSIWYG editor
  • you want a Slack-based writing assistant for content creation

Model Context Protocol (MCP) servers allow AI agents to interact with your API. Stainless generates fully functional MCP servers directly from your OpenAPI spec, while Fern does not currently offer MCP server generation.

Stainless is the only platform in this comparison that generates MCP servers, giving your users a way to integrate your API with AI assistants out of the box.

FeatureStainlessFern
MCP server generation
Hosted deployment✅ (Cloudflare Workers)
Docker support
Tool-level evals

Stainless MCP servers come with multiple deployment options. You can run them as hosted services on Cloudflare Workers for zero-infrastructure deployment, or package them as Docker containers for self-hosted environments.

Both Fern and Stainless generate SDKs from OpenAPI specifications, but they differ in runtime features, design quality, developer workflow, and additional capabilities like documentation and MCP servers. The right choice depends on your priorities: whether you value zero-dependency output, hand-written SDK design patterns, or breadth of language support.