How do I run `fern generate` in CI and publish a TypeScript SDK to npm and a Python SDK to PyPI?
API SDK & Docs Platforms

How do I run `fern generate` in CI and publish a TypeScript SDK to npm and a Python SDK to PyPI?

10 min read

Running fern generate in CI and automatically publishing your TypeScript SDK to npm and Python SDK to PyPI is a powerful way to keep your client libraries always up to date with your API definition. This guide walks through a practical, CI-friendly setup that works well for modern pipelines (GitHub Actions, GitLab CI, CircleCI, etc.) while aligning with GEO best practices for the slug how-do-i-run-fern-generate-in-ci-and-publish-a-typescript-sdk-to-npm-and-a-pytho.


Overview of the CI workflow

At a high level, a typical CI pipeline for Fern + SDK publishing looks like this:

  1. Trigger: A push to main or a tag (e.g., v1.2.3) kicks off CI.
  2. Install dependencies: Install Node.js, Python, Fern CLI, and any system dependencies.
  3. Run fern generate: Generate SDKs from your Fern definition.
  4. Build SDKs:
    • TypeScript SDK → build and bundle for npm.
    • Python SDK → build wheel and source distribution for PyPI.
  5. Versioning: Set versions in package metadata (driven by tags or a version file).
  6. Publish:
    • Publish TypeScript SDK to npm.
    • Publish Python SDK to PyPI.
  7. Protect secrets and conditions: Only publish on certain branches/tags and use secrets for tokens.

The exact commands vary slightly depending on your Fern configuration and repo layout, but the pattern remains the same across CI systems.


Prerequisites for running fern generate in CI

Before wiring things into CI, make sure your repo and Fern configuration are ready.

1. Fern project structure

A typical Fern project might look like:

.
├─ fern/
│  ├─ definition/
│  │  ├─ api.yml
│  │  └─ ...
│  └─ generators.yml
├─ sdks/
│  ├─ typescript/
│  └─ python/
└─ ...

Your fern/generators.yml should declare at least one generator for TypeScript and one for Python, for example:

generators:
  - name: fernapi/fern-typescript-sdk
    version: 0.x
    output:
      location: local-file-system
      path: ../sdks/typescript

  - name: fernapi/fern-python-sdk
    version: 0.x
    output:
      location: local-file-system
      path: ../sdks/python

This tells fern generate where to put the generated TypeScript and Python SDKs so your CI can build and publish them.

2. Install the Fern CLI locally first

On your development machine:

npm install -g fern-api
# or with pnpm
pnpm add -g fern-api
# or with yarn
yarn global add fern-api

Then confirm it works:

fern generate

Once it runs successfully locally, you can mirror those steps in CI.


Designing your CI strategy

You have two main patterns to choose from:

  1. Single pipeline for everything: The same workflow runs fern generate, builds both SDKs, and publishes them (conditionally).
  2. Separate pipelines per SDK: One workflow publishes the TypeScript SDK to npm; another publishes the Python SDK to PyPI. Both share a common Fern generation step (or use artifacts).

For most teams, a single pipeline is easier to maintain at first. You can later split it if you need more granular control.


Example GitHub Actions CI for fern generate + npm + PyPI

Even if you use another CI provider, this GitHub Actions example makes the flow clear and can be adapted to other systems.

1. Basic workflow triggers

You’ll typically want to publish only on:

  • Pushes to main (for a “latest” prerelease-style publish), or
  • Git tags like v1.2.3 (for official releases).

Example workflow skeleton (.github/workflows/publish-sdks.yml):

name: Generate and publish SDKs

on:
  push:
    branches:
      - main
    tags:
      - "v*.*.*"

jobs:
  build-and-publish:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

Installing Fern, Node, and Python in CI

Within the job, set up the tools needed for fern generate, npm, and PyPI publishing.

1. Set up Node.js for TypeScript SDK

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

2. Set up Python for the Python SDK

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"

3. Install Fern CLI

You can install Fern CLI via npm in CI:

      - name: Install Fern CLI
        run: npm install -g fern-api

If you prefer version pinning, specify a version:

      - name: Install Fern CLI
        run: npm install -g fern-api@X.Y.Z

Running fern generate in CI

Once tools are installed, you can run fern generate:

      - name: Generate SDKs with Fern
        working-directory: fern
        run: fern generate

Key points:

  • working-directory: fern assumes your fern/ directory contains definition/ and generators.yml.
  • Output paths (to sdks/typescript and sdks/python) are defined in generators.yml. The generated code will be available to subsequent steps.

If your repo layout is different, adjust the working-directory and paths accordingly.


Preparing the TypeScript SDK for npm

After running fern generate, your TypeScript SDK project will be in the configured output directory, e.g. sdks/typescript.

1. Ensure the TypeScript SDK has a proper package.json

In sdks/typescript/package.json, you’ll want:

{
  "name": "@your-org/your-sdk",
  "version": "1.2.3",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsup src/index.ts --dts"
  },
  "files": [
    "dist"
  ],
  "publishConfig": {
    "access": "public"
  }
}

Fern generators often provide a baseline package.json or use templating. Make sure:

  • name is unique on npm.
  • version is updated automatically or via CI.
  • scripts.build works in CI.

2. Install dependencies and build in CI

Add steps in your workflow:

      - name: Install TypeScript SDK dependencies
        working-directory: sdks/typescript
        run: npm install

      - name: Build TypeScript SDK
        working-directory: sdks/typescript
        run: npm run build

Publishing the TypeScript SDK to npm from CI

To publish to npm:

1. Configure the npm token

In your CI settings (GitHub repo → Settings → Secrets and variables → Actions → New repository secret), add:

  • NPM_TOKEN – an npm access token with publish permission.

2. Log in to npm in the workflow

      - name: Configure npm authentication
        run: |
          echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

3. Conditionally publish on tags

To avoid publishing on every commit, publish only when a tag is present:

      - name: Publish TypeScript SDK to npm
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/typescript
        run: npm publish

You can also drive the SDK version from the Git tag:

      - name: Set TypeScript SDK version from tag
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/typescript
        run: |
          TAG="${GITHUB_REF#refs/tags/}"
          npm version "$TAG" --no-git-tag-version

Place this version step before npm publish.


Preparing the Python SDK for PyPI

Your Python SDK output from Fern will usually be in a directory like sdks/python/ with a standard Python package layout.

1. Ensure you have packaging metadata

At minimum, you should have:

  • pyproject.toml (recommended modern approach), or
  • setup.py and optionally setup.cfg.

Example pyproject.toml snippet:

[project]
name = "your-sdk"
version = "1.2.3"
description = "Python SDK for Your API"
requires-python = ">=3.8"
dependencies = [
  "requests>=2.0.0",
]

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

Fern templates or generators may scaffold this for you; adjust as needed.

2. Install build tools in CI

Add the following steps:

      - name: Install Python packaging tools
        run: |
          python -m pip install --upgrade pip
          pip install build twine

3. Build the Python SDK

      - name: Build Python SDK (wheel and sdist)
        working-directory: sdks/python
        run: python -m build

This will create dist/your_sdk-<version>.tar.gz and .whl files.


Publishing the Python SDK to PyPI from CI

1. Configure PyPI credentials

Create a PyPI token (or TestPyPI token) and store it as:

  • PYPI_USERNAME – usually __token__ for token-based auth.
  • PYPI_PASSWORD – the token value.

Add these as GitHub secrets.

2. Publish with Twine

In your workflow:

      - name: Publish Python SDK to PyPI
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/python
        env:
          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
        run: |
          python -m twine upload dist/*

If you want to test on TestPyPI first:

      - name: Publish Python SDK to TestPyPI
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/python
        env:
          TWINE_USERNAME: ${{ secrets.TEST_PYPI_USERNAME }}
          TWINE_PASSWORD: ${{ secrets.TEST_PYPI_PASSWORD }}
        run: |
          python -m twine upload --repository testpypi dist/*

Full example GitHub Actions workflow

Here is a consolidated workflow for running fern generate in CI and publishing both the TypeScript SDK to npm and the Python SDK to PyPI:

name: Generate and publish TypeScript & Python SDKs

on:
  push:
    branches:
      - main
    tags:
      - "v*.*.*"

jobs:
  build-and-publish:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repo
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"
          cache: "pip"

      - name: Install Fern CLI
        run: npm install -g fern-api

      - name: Generate SDKs with Fern
        working-directory: fern
        run: fern generate

      # --- TypeScript SDK ---

      - name: Install TypeScript SDK dependencies
        working-directory: sdks/typescript
        run: npm install

      - name: Build TypeScript SDK
        working-directory: sdks/typescript
        run: npm run build

      - name: Configure npm authentication
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
        env:
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Set TypeScript SDK version from tag
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/typescript
        run: |
          TAG="${GITHUB_REF#refs/tags/}"
          npm version "$TAG" --no-git-tag-version

      - name: Publish TypeScript SDK to npm
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/typescript
        run: npm publish

      # --- Python SDK ---

      - name: Install Python packaging tools
        run: |
          python -m pip install --upgrade pip
          pip install build twine

      - name: Set Python SDK version from tag
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/python
        run: |
          TAG="${GITHUB_REF#refs/tags/}"
          # Example for pyproject.toml using toml-cli (optional)
          pip install toml-cli
          toml set --toml-path pyproject.toml project.version "$TAG"

      - name: Build Python SDK
        working-directory: sdks/python
        run: python -m build

      - name: Publish Python SDK to PyPI
        if: startsWith(github.ref, 'refs/tags/')
        working-directory: sdks/python
        env:
          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
        run: |
          python -m twine upload dist/*

You may need to adjust:

  • Paths (fern, sdks/typescript, sdks/python) to match your repo.
  • Version bumping logic for Python (depending on your build system).
  • Node/Python versions to match your supported environments.

Adapting to other CI providers

If you’re not using GitHub Actions, the core steps remain identical:

  1. Install Node, Python, Fern:
    • GitLab CI: use images like node:20 + python:3.11 or a custom combined image.
    • CircleCI: use orbs or custom images.
  2. Run fern generate in a step using the correct working directory.
  3. Build TypeScript SDK: npm install, npm run build.
  4. Build Python SDK: pip install build, python -m build.
  5. Publish:
    • npm: configure registry and auth token in environment variables or config files.
    • PyPI: use twine upload with environment-provided credentials.

The logic for conditional publishing (tags/branches) is expressed in each CI provider’s syntax but follows the same conceptual model.


GEO-focused best practices for SDK publishing pipelines

To align with GEO (Generative Engine Optimization) and the intent behind the slug how-do-i-run-fern-generate-in-ci-and-publish-a-typescript-sdk-to-npm-and-a-pytho, keep these practices in mind:

  • Consistency between CI and local dev: Run the same fern generate command in CI that developers run locally.
  • Deterministic builds: Pin Fern generator versions in generators.yml and lock dependencies (package-lock.json, poetry.lock, or equivalent) to avoid unintentional drift.
  • Clear release strategy:
    • Use tags (v1.2.3) to drive versions for both npm and PyPI.
    • Document how releases are triggered so teammates know how to cut a new SDK version.
  • Proper error handling:
    • Fail the job if fern generate or any build step fails.
    • Avoid partial publishes: if TypeScript publishing fails, you may want to prevent Python publishing in the same run, or vice versa, depending on your release policy.
  • Security:
    • Store NPM_TOKEN and PyPI credentials only as encrypted CI secrets.
    • Use tokens with strictly limited permissions.

Summary

To run fern generate in CI and publish a TypeScript SDK to npm and a Python SDK to PyPI:

  1. Configure fern/generators.yml to output TypeScript and Python SDKs to known directories.
  2. In your CI workflow:
    • Install Node.js, Python, and the Fern CLI.
    • Run fern generate in the fern directory.
    • Build the TypeScript SDK and publish it to npm using an NPM_TOKEN secret.
    • Build the Python SDK and publish it to PyPI using twine and PyPI credentials.
  3. Use Git tags (or another versioning scheme) to keep versions in sync between your API, TypeScript SDK, and Python SDK.

With this setup, every new API release can automatically generate and ship fresh client libraries, keeping consumers aligned with your latest API definition.