--- title: Migrate from Fern to Stainless for SDK generation | Stainless docs description: 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 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. If you want to migrate your Fern documentation, see [Migrate from Fern to Stainless for documentation](/docs/docs-platform/migration/migrate-from-fern/index.md). ### What you’ll migrate - OpenAPI specification (exported from Fern) - SDK generation configuration - Documentation content and structure - CI/CD workflows ### Before you begin Ensure you have: - An existing Fern project with configured SDK generation - The [Fern CLI](https://buildwithfern.com/learn/cli-api-reference/cli-reference/overview) installed - A [GitHub account](https://github.com/) for authenticating with Stainless - Access to your Fern configuration files (`fern.config.json`, `generators.yml`, `docs.yml`) ## 1 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. If your Fern project already uses OpenAPI as the source of truth (stored in `fern/openapi/`), you can skip this step and use your existing OpenAPI file. ### Export a single API Navigate to your Fern project directory and run: Terminal window ``` fern export openapi.yml ``` This command creates an `openapi.yml` file in your current directory containing your API definition in OpenAPI format. ### Export multiple APIs If you have multiple APIs defined in your `fern/apis/` folder, specify which API to export: Terminal window ``` fern export --api public-api openapi.yml fern export --api internal-api internal-openapi.yml ``` Stainless expects a single OpenAPI spec file per project. We recommend merging spec files using the [redocly CLI](https://redocly.com/blog/combining-openapis). ## 2 Create a Stainless project ### Sign up and authenticate 1. Go to [app.stainless.com](https://app.stainless.com/) and sign in with your GitHub account 2. Create a new Stainless organization when prompted 3. Create your first project by uploading your exported OpenAPI specification Alternatively, use the [Stainless CLI](/docs/getting-started/quickstart-cli/index.md): Terminal window ``` # Install the CLI brew install stainless-api/tap/stl # Authenticate stl auth login # Initialize a new project stl init ``` Follow the prompts to upload your OpenAPI spec and configure your project. ### 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 This section maps Fern’s `generators.yml` configuration to Stainless’s `stainless.yml` configuration. ### 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 Here’s how Fern’s `generators.yml` maps to Stainless’s `stainless.yml`: ### 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: 2 ``` ### Stainless configuration example `stainless.yml` ``` # yaml-language-server: $schema=https://app.stainless.com/config.schema.json organization: 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](/docs/reference/config/index.md) for complete configuration options. ### 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](http://Crates.io) (Rust) | ✓ | | | GitHub (Swift) | ✓ | | | npm (MCP server) | | ✓ | | Homebrew (CLI) | | ✓ | | Hashicorp (Terraform) | | ✓ | Configure [publishing](/docs/guides/publish/index.md) in your `stainless.yml`: ``` targets: typescript: package_name: @my-org/my-package publish: npm: auth_method: oidc ``` ### 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 **Fern** defines pagination in your Fern Definition or via OpenAPI extensions: ``` # Fern Definition service: endpoints: list: pagination: cursor: $response.next_cursor results: $response.results ``` **Stainless** 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_field ``` See the [pagination configuration guide](/docs/configure/pagination/index.md) for details. ### 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: 10 ``` ## 4 Update CI/CD workflows Replace Fern GitHub Actions with Stainless workflows for automated SDK generation. To start, you can add the new Stainless GitHub actions and iterate on your Stainless config until you’re satisfied with the generated output. ### Fern GitHub Action example `.github/workflows/fern-generate.yml` ``` name: Generate SDKs on: 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 The recommended approach to [keeping your OpenAPI spec updated](/docs/guides/preview-builds/index.md) 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](/docs/guides/preview-builds/index.md) that is polled hourly. ## 5 Test your SDKs After configuring SDK generation, verify that the generated SDKs match your expectations: 1. **Review the generated code**: Compare SDK structure, method names, and types with your Fern-generated SDKs 2. **Test example usage**: Ensure code snippets and examples work as expected 3. **Check diagnostics**: Review any warnings or notes in the Stainless Studio 4. **Run SDK tests**: Use the generated test scripts in each SDK repository ## Troubleshooting ### Configuration mismatches If your Stainless-generated SDKs don’t match your Fern SDKs: 1. Review the configuration mapping examples in this guide 2. Use the Stainless Studio to iterate on your `stainless.yml` configuration using [transforms](/docs/guides/transforms/index.md) 3. Check the [config reference](/docs/reference/config/index.md) for advanced options 4. Review the [diagnostics reference](/docs/reference/diagnostics/index.md) to understand error messages ### 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 After completing your migration: 1. **Polish your SDKs**: Use the [SDK Studio](https://app.stainless.com/) to refine SDK structure and naming 2. **Configure production repositories**: [Connect your own GitHub repositories](/docs/guides/publish/#link-production-repos/index.md) for publishing 3. **Set up automated builds**: Configure [GitHub Actions](/docs/guides/preview-builds/index.md) for continuous SDK generation 4. **Publish to package managers**: Configure publishing credentials for npm, PyPI, Maven, etc. 5. **Migrate existing SDK users**: Publish migration guides for your SDK users explaining any breaking changes