Generating a TypeScript Axios Client from OpenAPI

A typed HTTP client that stays in lockstep with your spec is the fastest way to stop frontend teams from hand-writing fetch calls. This guide shows the exact OpenAPI Generator invocation to produce a typescript-axios SDK, which --additional-properties actually matter, how to wire it into a build, and how to publish it to npm. It is part of SDK Generation & Changelog Automation and assumes you already have a valid OpenAPI 3.x document.

OpenAPI spec to published npm SDK An OpenAPI spec is generated into a typescript-axios client, compiled with npm build, and published to the npm registry. openapi.yaml OpenAPI 3.x generate -g typescript-axios api.ts + models npm run build .js + .d.ts npm publish @acme/api-client One spec change re-runs the whole pipeline and ships a new SDK version

Problem & Context

Frontend developers consuming your API by hand-writing axios calls produce three recurring problems: request and response types drift from the real API, a renamed field breaks silently at runtime, and every team re-implements auth and error handling. Generating a typescript-axios client solves all three — it ships fully typed request methods, model interfaces, and a single Configuration object for base URL and credentials.

The friction is in the details. The typescript-axios generator has a long list of --additional-properties, and the wrong combination produces a package that will not compile, will not tree-shake, or cannot be published. The three that change the shape of the output most are:

  • npmName — the package name; without it, the generated package.json is incomplete and not publishable.
  • supportsES6 — emits ES6 (import/export, const) instead of ES5, required for modern bundlers and tree-shaking.
  • withSeparateModelsAndApi — splits models and APIs into separate directories so a model change does not invalidate every API import.

Step-by-Step Solution

1. Pin the generator version

npm install -g @openapitools/openapi-generator-cli
openapi-generator-cli version-manager set 7.6.0
openapi-generator-cli version

Expected output:

Did set the version to 7.6.0
7.6.0

Confirm the property names for the version you pinned — they occasionally change between majors.

openapi-generator-cli config-help -g typescript-axios | grep -E "npmName|supportsES6|withSeparateModelsAndApi"

Expected output (truncated):

	npmName
	    The name under which you want to publish generated npm package.
	supportsES6
	    Generate code that conforms to ES6. (Default: false)
	withSeparateModelsAndApi
	    Put the model and api in separate folders ... (Default: false)

3. Generate the client

openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-axios \
  -o ./sdk \
  --additional-properties=npmName=@acme/api-client,npmVersion=1.0.0,supportsES6=true,withSeparateModelsAndApi=true,useSingleRequestParameter=true

Expected output (truncated):

[main] INFO  o.o.codegen.DefaultGenerator - Generated server response code: ...
[main] INFO  o.o.codegen.TemplateManager - writing file ./sdk/api/default-api.ts
[main] INFO  o.o.codegen.TemplateManager - writing file ./sdk/models/index.ts
[main] INFO  o.o.codegen.TemplateManager - writing file ./sdk/package.json
[main] INFO  o.o.codegen.DefaultGenerator - Generation took: ... ms

Confirm the package name and layout:

grep '"name"' ./sdk/package.json && ls ./sdk

Expected output:

  "name": "@acme/api-client",
api  base.ts  common.ts  configuration.ts  index.ts  models  package.json  tsconfig.json

4. Build the package

The generator emits TypeScript and a package.json whose build script runs tsc. Compile to JavaScript plus declaration files before doing anything else.

cd sdk
npm install
npm run build
ls dist

Expected output:

api.js  api.d.ts  configuration.js  configuration.d.ts  index.js  index.d.ts  models

5. Wire it into a consuming app

// usage.ts — in the app that depends on @acme/api-client
import { Configuration, DefaultApi } from '@acme/api-client';

const api = new DefaultApi(
  new Configuration({
    basePath: 'https://api.acme.com',
    accessToken: () => localStorage.getItem('token') ?? '',
  }),
);

const { data } = await api.listOrders();
console.log(data.length);

6. Publish to npm

cd sdk
npm publish --access public

Expected output:

+ @acme/[email protected]

Complete Working Example

A GitHub Actions workflow that regenerates, builds, and publishes the SDK whenever the spec changes — the full pipeline in one self-contained file.

# .github/workflows/publish-sdk.yml
name: Publish TypeScript SDK
on:
  push:
    branches: [main]
    paths: ['openapi.yaml']
jobs:
  generate-build-publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'

      # Pin the generator so CI output is deterministic.
      - name: Pin OpenAPI Generator
        run: npx --yes @openapitools/openapi-generator-cli version-manager set 7.6.0

      # Generate the typescript-axios client with the properties that shape the output.
      - name: Generate SDK
        run: |
          npx @openapitools/openapi-generator-cli generate \
            -i openapi.yaml \
            -g typescript-axios \
            -o ./sdk \
            --additional-properties=npmName=@acme/api-client,npmVersion=1.0.${{ github.run_number }},supportsES6=true,withSeparateModelsAndApi=true,useSingleRequestParameter=true

      # Compile TypeScript to JS + .d.ts. Publishing source-only would break consumers.
      - name: Build
        working-directory: ./sdk
        run: |
          npm install
          npm run build

      # Publish. NPM_TOKEN must be a repository secret with publish rights.
      - name: Publish to npm
        working-directory: ./sdk
        run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Gotchas & Edge Cases

supportsES6=false silently breaks tree-shaking. The default emits ES5 with module.exports, which modern bundlers cannot tree-shake, so consumers ship the entire client even if they call one method. Always set supportsES6=true for a browser-bound SDK.

Publishing source instead of dist forces consumers to compile your SDK. If you skip npm run build and publish the raw .ts files, every project that installs the package has to transpile it. Confirm package.json has "main": "dist/index.js" and "types": "dist/index.d.ts", and that dist is included in files or not excluded by .npmignore.

operationId collisions produce duplicate method names. Two operations sharing an operationId generate the same TypeScript method, and the second overwrites the first. Lint the spec for unique operationId values before generating, or the SDK will silently drop a method.

Pin the generator, not just the specification. A generated client is a function of two inputs, and teams routinely version-control only one of them. Running an unpinned generator means the emitted code can change without any change to your contract — new method names after a major bump, a different date type, a reshaped error class — and the resulting consumer breakage is genuinely confusing to debug because nothing in the specification’s history explains it. Record the generator version alongside the contract version in the published package so that any output is reproducible from two recorded facts.

Regenerate into a clean directory. Generating on top of an existing output leaves orphaned files behind when an operation is removed or renamed: the old client file stays, still compiles, still exports a method, and consumers keep calling an endpoint that no longer exists. Deleting the output directory before each run makes the generated tree an exact function of the inputs, which is the property that makes it safe to treat as disposable.

Decide what the SDK does about errors before the first release. The generated client’s error handling is the part consumers interact with most and the part generators handle least consistently — some throw on non-2xx, some return a result object, some expose the raw response. Whatever your generator does by default, document it in the SDK README and keep it stable, because changing it later is a breaking change for every catch block your consumers have written.

Two inputs, one output Generated client code is a function of the contract version and the generator version, so both must be pinned and recorded for the output to be reproducible. contract 2.7.0 generator 7.9.0 generate reproducible output from two recorded facts

Publish the client as a package, not as a copied folder. Vendoring generated code into each consuming application means every consumer regenerates independently, at different contract versions, with different options — and nobody can answer which version of the API a given application is talking to. Publishing to a registry makes the client one artifact with one version, which is what lets a spec change reach consumers as a dependency update they can see and schedule.

Give the generated package a thin hand-written wrapper only if you truly need one. A wrapper is where teams put retries, auth refresh, and telemetry — genuinely useful things the generator will not produce. It is also a layer that must be maintained by hand forever and re-checked against every regeneration. Keep it minimal, keep it in its own file, and push anything expressible in the specification or the generator config back into those instead.

Published package versus vendored copies A published client is one versioned artifact every consumer shares, while vendored copies drift to different contract versions with no way to tell them apart. vendored per application each regenerates independently different versions and options nobody knows who is on what published package one artifact, one version upgrades are visible and schedulable the contract version is recorded

FAQ

Which OpenAPI Generator should I use for a TypeScript Axios client?

Use the typescript-axios generator, which produces a fetch-free client built on the axios HTTP library with full TypeScript types. It is the right choice when your app already uses axios or you want interceptors for auth and retries.

How do I set the npm package name when generating?

Pass npmName through --additional-properties, for example --additional-properties=npmName=@acme/api-client. The generator writes a package.json with that name so the output is ready to build and publish.

Do I need to compile the generated client before publishing?

Yes. The generator emits TypeScript source plus a package.json with a build script, so run npm install and npm run build to produce the .js and .d.ts files before npm publish. Publishing the raw .ts files would force every consumer to compile your SDK.

One habit ties all of this together: regenerate on every contract change, in CI, and compile the result. It is the cheapest possible check and it catches the whole class of problems where a specification edit is valid OpenAPI and produces code that will not build. Discovering that on a branch costs minutes; discovering it during a release costs the release.

Keep the generated package boring. Its value is that it is an exact, reproducible function of two pinned inputs, and every hand-added convenience erodes that property a little.