Configuring Fern for Multiple Languages

This guide configures TypeScript, Python, Java, and Go SDK generators in a single Fern generators.yml, all reading one shared API definition, each with its own language-specific options. It is part of the Fern SDK generation section and the broader SDK Generation & Changelog Automation framework. The goal is one source of truth for your API and one command, fern generate --group sdks, that produces four idiomatic clients.

Shared definition to four SDKs A single API definition feeds the Fern intermediate representation, which fans out to TypeScript, Python, Java, and Go generators. fern/definition shared API Fern IR one model TypeScript Python Java Go

Problem & Context

Maintaining a separate SDK repository per language drifts fast: a field added to the API ships in the TypeScript client weeks before it reaches Go, and the auth handling diverges because four codegen configs live in four places. Fern solves this by reading one API definition into a single intermediate representation and running every generator against it, so all four SDKs reflect the same API the moment you generate.

The configuration challenge is that each language wants different metadata — npm scope and namespace for TypeScript, a PEP 8 package name for Python, a Maven group/artifact coordinate for Java, an import path for Go. Fern keeps these isolated in a per-generator config block while sharing everything that matters (the API itself). This guide assumes you have run fern init and have a definition in fern/definition. Once you can generate every language, publishing them is covered in publishing SDKs to npm and PyPI with Fern.

Step-by-Step Solution

1. Install the Fern CLI

npm install -g [email protected]
fern --version

Expected output:

0.64.21

2. Confirm the shared API definition

All generators read the same definition. Verify it resolves before adding generators.

fern check

Expected output:

fern/definition: All checks passed

If you started from an OpenAPI spec, fern init --openapi ./openapi.yaml imports it into fern/openapi and Fern uses it as the shared source for every generator.

3. Add all four generators to generators.yml

Edit fern/generators.yml. List each generator under one group named sdks, with pinned versions and a local output.location so you can inspect results before publishing.

# fern/generators.yml
groups:
  sdks:
    generators:
      - name: fernapi/fern-typescript-node-sdk
        version: 0.50.0
        output:
          location: local-file-system
          path: ../sdks/typescript
      - name: fernapi/fern-python-sdk
        version: 4.18.0
        output:
          location: local-file-system
          path: ../sdks/python
      - name: fernapi/fern-java-sdk
        version: 2.30.0
        output:
          location: local-file-system
          path: ../sdks/java
      - name: fernapi/fern-go-sdk
        version: 0.34.0
        output:
          location: local-file-system
          path: ../sdks/go

4. Set per-generator options

Add a config block to each generator for language-specific metadata. These keys are scoped to their own generator and do not affect the others.

# fern/generators.yml (config blocks added)
groups:
  sdks:
    generators:
      - name: fernapi/fern-typescript-node-sdk
        version: 0.50.0
        output:
          location: local-file-system
          path: ../sdks/typescript
        config:
          namespaceExport: AcmeApi
          packageJson:
            name: "@acme/acme-sdk"
      - name: fernapi/fern-python-sdk
        version: 4.18.0
        output:
          location: local-file-system
          path: ../sdks/python
        config:
          package_name: acme_sdk
          pydantic_config:
            version: v2
      - name: fernapi/fern-java-sdk
        version: 2.30.0
        output:
          location: local-file-system
          path: ../sdks/java
        config:
          group: com.acme
          artifact: acme-sdk-java
      - name: fernapi/fern-go-sdk
        version: 0.34.0
        output:
          location: local-file-system
          path: ../sdks/go
        config:
          packageName: acme
          module:
            path: github.com/acme/acme-go

5. Generate all languages

Run the group to produce every SDK from the one definition.

fern generate --group sdks

Expected output:

┌─
│ ✓ fernapi/fern-typescript-node-sdk  ../sdks/typescript
│ ✓ fernapi/fern-python-sdk           ../sdks/python
│ ✓ fernapi/fern-java-sdk             ../sdks/java
│ ✓ fernapi/fern-go-sdk               ../sdks/go
└─

Confirm the four output folders were written:

ls sdks

Expected output:

go  java  python  typescript

Complete Working Example

A complete fern/generators.yml that generates all four languages locally, plus a CI step that fails the build if any generator errors. This is the file you commit.

# fern/generators.yml — full multi-language configuration
default-group: sdks
groups:
  sdks:
    generators:
      - name: fernapi/fern-typescript-node-sdk
        version: 0.50.0
        output:
          location: local-file-system
          path: ../sdks/typescript
        config:
          namespaceExport: AcmeApi
          packageJson:
            name: "@acme/acme-sdk"

      - name: fernapi/fern-python-sdk
        version: 4.18.0
        output:
          location: local-file-system
          path: ../sdks/python
        config:
          package_name: acme_sdk
          pydantic_config:
            version: v2

      - name: fernapi/fern-java-sdk
        version: 2.30.0
        output:
          location: local-file-system
          path: ../sdks/java
        config:
          group: com.acme
          artifact: acme-sdk-java

      - name: fernapi/fern-go-sdk
        version: 0.34.0
        output:
          location: local-file-system
          path: ../sdks/go
        config:
          packageName: acme
          module:
            path: github.com/acme/acme-go
# .github/workflows/generate-sdks.yml
name: Generate SDKs
on:
  pull_request:
    paths:
      - "fern/**"

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
      - run: npm install -g [email protected]
      - run: fern check
      - run: fern generate --group sdks

With default-group: sdks set, contributors can also run a bare fern generate locally and get all four SDKs without remembering the group name.

Gotchas & Edge Cases

Generator versions are not interchangeable. Each fernapi/fern-*-sdk generator has its own release cadence, so fern-python-sdk 4.18.0 and fern-go-sdk 0.34.0 are unrelated numbers. Pin each one independently and bump them deliberately; copying one version number across generators will fail with generator version not found.

Go modules need a real import path. The Go generator’s module.path must match where the SDK will actually be imported from (github.com/acme/acme-go), because it is written verbatim into go.mod. A placeholder path compiles locally but breaks go get for your users.

Per-generator config keys are case- and style-specific. The TypeScript generator uses namespaceExport (camelCase), the Python generator uses package_name (snake_case), and the Java generator uses group/artifact. Fern silently ignores an unknown key inside a generator block, so a mistyped option fails quietly rather than loudly — run fern check and inspect the generated output to confirm an option took effect.

FAQ

Can all four generators share one API definition?

Yes. Fern reads a single API definition from the fern/definition folder (or an imported OpenAPI spec) and every generator listed in generators.yml consumes the same intermediate representation. You never duplicate the API per language.

Do per-generator config options conflict across languages?

No. Each generator has its own config block under its entry in generators.yml, so a TypeScript option like namespaceExport and a Java option like group are independent. Fern ignores any option that a given generator does not recognize within its own block.

How do I generate only some languages during local development?

Put the languages you want into a named group and run fern generate --group local. Generators in other groups are skipped, so you can keep a fast local group with one or two languages and a full release group with all four.