Migrate from Fern
Learn how to migrate your SDK generation from Fern to Stainless, including exporting your OpenAPI spec and configuring SDK generation.
This guide walks you through migrating from Fern to Stainless for SDK generation. You will export your OpenAPI specification from Fern, create a new Stainless project and configure SDK generation to match your existing SDKs.
Overview
Section titled “Overview”Stainless and Fern both generate SDKs from OpenAPI specifications, but they differ in their approach to configuration and SDK output. This guide helps you migrate your existing Fern setup to Stainless while maintaining feature parity with your existing SDKs.
What you’ll migrate
Section titled “What you’ll migrate”- OpenAPI specification (exported from Fern)
- SDK generation configuration
- Documentation content and structure
- CI/CD workflows
Before you begin
Section titled “Before you begin”Ensure you have:
- An existing Fern project with configured SDK generation
- The Fern CLI installed
- A GitHub account for authenticating with Stainless
- Access to your Fern configuration files (
fern.config.json,generators.yml,docs.yml)
1 Export your OpenAPI spec from Fern
Section titled “ Export your OpenAPI spec from Fern”If your Fern project uses the Fern Definition format, export it to OpenAPI 3.1 format using the fern export command.
Export a single API
Section titled “Export a single API”Navigate to your Fern project directory and run:
fern export openapi.ymlThis command creates an openapi.yml file in your current directory containing your API definition in OpenAPI format.
Export multiple APIs
Section titled “Export multiple APIs”If you have multiple APIs defined in your fern/apis/ folder, specify which API to export:
fern export --api public-api openapi.ymlfern export --api internal-api internal-openapi.yml2 Create a Stainless project
Section titled “ Create a Stainless project”Sign up and authenticate
Section titled “Sign up and authenticate”- Go to app.stainless.com and sign in with your GitHub account
- Create a new Stainless organization when prompted
- Create your first project by uploading your exported OpenAPI specification
Alternatively, use the Stainless CLI:
# Install the CLIbrew install stainless-api/tap/stl
# Authenticatestl auth login
# Initialize a new projectstl initFollow the prompts to upload your OpenAPI spec and configure your project.
Initial configuration
Section titled “Initial configuration”When creating your project, Stainless generates an initial stainless.yml configuration file to interpret your OpenAPI spec. This config defines the SDK structure, pagination, error handling, and publishing settings.
3 Migrate SDK generation configuration
Section titled “ Migrate SDK generation configuration”This section maps Fern’s generators.yml configuration to Stainless’s stainless.yml configuration.
Language support
Section titled “Language support”Both platforms support the following languages for SDK generation:
- TypeScript
- Python
- Go
- Java
- C# (.NET)
- PHP
- Ruby
- Swift (Stainless - coming soon), Swift (Fern - beta)
- Rust (Stainless - coming soon), Rust (Fern - beta)
- Kotlin (Stainless only)
- Terraform (Stainless only)
- CLI (Stainless only)
Configuration mapping
Section titled “Configuration mapping”Here’s how Fern’s generators.yml maps to Stainless’s stainless.yml:
Fern configuration example
Section titled “Fern configuration example”generators.yml
api: specs: - openapi: "./openapi.yml" origin: "https://api.example.com/openapi.json" overrides: "./openapi-overrides.yml" namespace: "v1" settings: title-as-schema-name: true inline-path-parameters: false inline-all-of-schemas: true prefer-undiscriminated-unions-with-literals: true filter: endpoints: ["POST /users", "GET /users/{id}"] example-generation: request: max-depth: 2Stainless configuration example
Section titled “Stainless configuration example”stainless.yml
# yaml-language-server: $schema=https://app.stainless.com/config.schema.jsonorganization: name: example docs: 'docs.example.com' contact: 'support@example.com'
targets: typescript: package_name: example production_repo: example/example-typescript publish: npm: true
environments: production: https://example.com/api/v1
client_settings: opts: api_key: read_env: API_KEY auth: security_scheme: api_key
resources: activities: methods: list: get /activities # ...
pagination: - name: limit_start_pagination type: offset request: # ...See the Stainless config reference for complete configuration options.
Publishing configuration
Section titled “Publishing configuration”Both Fern and Stainless support publishing to package managers and GitHub:
| Feature | Fern | Stainless |
|---|---|---|
| npm and JSR (TypeScript) | ✓ | ✓ |
| PyPI (Python) | ✓ | ✓ |
| GitHub (Go) | ✓ | ✓ |
| Maven Central (Java) | ✓ | ✓ |
| Maven Central (Kotlin) | ✓ | |
| RubyGems (Ruby) | ✓ | ✓ |
| NuGet (C#) | ✓ | ✓ |
| Packagist (PHP) | ✓ | ✓ |
| Crates.io (Rust) | ✓ | |
| GitHub (Swift) | ✓ | |
| npm (MCP server) | ✓ | |
| Homebrew (CLI) | ✓ | |
| Hashicorp (Terraform) | ✓ |
Configure publishing in your stainless.yml:
targets: typescript: package_name: @my-org/my-package publish: npm: auth_method: oidcSDK features comparison
Section titled “SDK features comparison”Both platforms support:
- Type-safe request/response handling
- Automatic retries with exponential backoff
- Configurable timeouts
- Authentication (API keys, custom headers)
- Pagination helpers
Pagination
Section titled “Pagination”Fern defines pagination in your Fern Definition or via OpenAPI extensions:
# Fern Definitionservice: endpoints: list: pagination: cursor: $response.next_cursor results: $response.resultsStainless uses the pagination section in stainless.yml:
pagination: - name: my_cursor_page type: cursor request: next: type: string x-stainless-pagination-property: purpose: next_cursor_param limit: type: integer response: my_data: type: array items: type: object next: type: string x-stainless-pagination-property: purpose: next_cursor_fieldSee the pagination configuration guide for details.
Retries and timeouts
Section titled “Retries and timeouts”Fern configures retries at the SDK level with defaults:
const client = new APIClient({ maxRetries: 3, timeout: 60000,});Stainless generates similar retry configuration:
const client = new APIClient({ maxRetries: 3, timeout: 60 * 1000,});Configure default retry behavior in stainless.yml:
client_settings: default_retries: # 5 retries are made, with the interval [1, 2, 4, 8, 10 (capped by the max)] # not accounting for jitter max_retries: 5 initial_delay_seconds: 1 max_delay_seconds: 104 Update CI/CD workflows
Section titled “ Update CI/CD workflows”Replace Fern GitHub Actions with Stainless workflows for automated SDK generation.
Fern GitHub Action example
Section titled “Fern GitHub Action example”.github/workflows/fern-generate.yml
name: Generate SDKson: push: branches: [main]
jobs: generate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: fern-api/fern-action@v1 with: command: generate token: ${{ secrets.FERN_TOKEN }}Stainless GitHub Action example
Section titled “Stainless GitHub Action example”The recommended approach to keeping your OpenAPI spec updated in Stainless is a two step process that provides you with SDK preview builds. As your OpenAPI spec evolves, you can see how those changes are reflected in SDK diffs.
.github/workflows/stainless-generate.yml
name: Build SDKs for pull request
on: pull_request: types: - opened - synchronize - reopened - closed
concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true
env: STAINLESS_ORG: YOUR_ORG STAINLESS_PROJECT: YOUR_PROJECT OAS_PATH: YOUR_OAS_PATH
jobs: preview: if: github.event.action != 'closed' runs-on: ubuntu-latest permissions: contents: read pull-requests: write id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 2
- name: Run preview builds uses: stainless-api/upload-openapi-spec-action/preview@v1 with: org: ${{ env.STAINLESS_ORG }} project: ${{ env.STAINLESS_PROJECT }} oas_path: ${{ env.OAS_PATH }}
merge: if: github.event.action == 'closed' && github.event.pull_request.merged == true runs-on: ubuntu-latest permissions: contents: read pull-requests: write id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 2
- name: Run merge build uses: stainless-api/upload-openapi-spec-action/merge@v1 with: org: ${{ env.STAINLESS_ORG }} project: ${{ env.STAINLESS_PROJECT }} oas_path: ${{ env.OAS_PATH }}You can also trigger builds via the Stainless API or a persistent URL that is polled hourly.
5 Test your SDKs
Section titled “ Test your SDKs”After configuring SDK generation, verify that the generated SDKs match your expectations:
- Review the generated code: Compare SDK structure, method names, and types with your Fern-generated SDKs
- Test example usage: Ensure code snippets and examples work as expected
- Check diagnostics: Review any warnings or notes in the Stainless Studio
- Run SDK tests: Use the generated test scripts in each SDK repository
Troubleshooting
Section titled “Troubleshooting”Configuration mismatches
Section titled “Configuration mismatches”If your Stainless-generated SDKs don’t match your Fern SDKs:
- Review the configuration mapping examples in this guide
- Use the Stainless Studio to iterate on your
stainless.ymlconfiguration using transforms - Check the config reference for advanced options
- Review the diagnostics reference to understand error messages
Custom code migration
Section titled “Custom code migration”Both Fern and Stainless support custom code in generated SDKs:
Fern avoids overwriting your custom code changes by you listing out which files should not be generated in a .fernignore file.
Stainless allows you to edit code in your SDK repositories just like you would any other repository, and your edits persist on top of successive runs of codegen.
Next steps
Section titled “Next steps”After completing your migration:
- Polish your SDKs: Use the SDK Studio to refine SDK structure and naming
- Configure production repositories: Connect your own GitHub repositories for publishing
- Set up automated builds: Configure GitHub Actions for continuous SDK generation
- Publish to package managers: Configure publishing credentials for npm, PyPI, Maven, etc.
- Migrate existing SDK users: Publish migration guides for your SDK users explaining any breaking changes