Engineering

Generate a TypeScript SDK from OpenAPI: end-to-end in 2026

There are three ways to ship a TypeScript SDK from OpenAPI in 2026: write it by hand, run a generator locally, or use a hosted pipeline that handles spec → SDK → npm publishing as one workflow. This post walks the practical version of all three so you can pick the one that matches your team's volume.

Before you touch a generator: spec hygiene

The single largest factor in TypeScript SDK quality is the spec itself. The generator can't recover from a bad spec, no matter how clever.

The non-negotiables:

  • Every operation has an operationId. It becomes the method name. createUser is good; missing means you get something like postUsers_users that nobody wants in their codebase.
  • Schemas live in components/schemas and are referenced, not inlined. Inline schemas produce one-off anonymous types per endpoint. Customers can't import { User } from "@your-co/sdk" if User was inlined.
  • Every operation has at least one example. Generators that produce inline examples need them somewhere; the spec is the right place.
  • Security schemes are defined and applied. A spec with no securitySchemes produces an SDK with no auth helper.

If your spec doesn't meet those four, fix that first. See OpenAPI best practices for SDK-friendly specs for the long list.

Path 1: hand-written TypeScript client

Best when you have ten endpoints, you know exactly how customers will use it, and you want zero generator dependency.

// client.ts
export interface ClientOptions {
  apiKey: string;
  baseUrl?: string;
  fetch?: typeof fetch;
}

export class Client {
  constructor(private opts: ClientOptions) {}

  private async request<T>(path: string, init?: RequestInit): Promise<T> {
    const res = await (this.opts.fetch ?? fetch)(`${this.opts.baseUrl ?? "https://api.example.com"}${path}`, {
      ...init,
      headers: {
        Authorization: `Bearer ${this.opts.apiKey}`,
        "Content-Type": "application/json",
        ...init?.headers,
      },
    });
    if (!res.ok) throw new APIError(res.status, await res.text());
    return res.json() as Promise<T>;
  }

  users = {
    list: () => this.request<User[]>("/users"),
    create: (body: CreateUserBody) => this.request<User>("/users", { method: "POST", body: JSON.stringify(body) }),
  };
}

This is fine for small APIs. It does not scale — adding 50 endpoints turns into a maintenance burden, and the moment your spec changes the SDK is silently wrong.

Path 2: run a generator locally

Best when you control the spec, want full ownership of the output, and have engineering capacity to maintain templates.

The two real options:

  • openapi-typescript + openapi-fetch — generates strict types from the spec, paired with a small runtime that uses them. Lightweight, no codegen of the runtime itself. Good for APIs where the consumers are also TypeScript-native.
  • OpenAPI Generator (typescript-axios, typescript-fetch) — full codegen of types + a client class. Heavier, but produces the kind of OO surface that buyers expect from a "real SDK".
# Approach A — types only
npx openapi-typescript ./openapi.yaml -o ./src/api.d.ts

# Approach B — full client
npx @openapitools/openapi-generator-cli generate \
  -i ./openapi.yaml \
  -g typescript-fetch \
  -o ./generated

Both produce something usable in five minutes. Both leave you owning the templates, the publishing pipeline, the docs, and the question of what to do when the spec changes between releases.

Path 3: hosted pipeline

Best when you want to ship SDKs and stop maintaining codegen.

The shape: upload your OpenAPI spec, get a working TypeScript SDK back, preview it before publishing, push to npm. Bloom does this; Stainless does this; Speakeasy does this. Pricing varies; the workflow shape is similar across providers.

With Bloom specifically:

  1. Upload your OpenAPI spec at signup. The dashboard shows the parsed structure and any spec issues that would produce poor TypeScript output.
  2. Preview the generated SDK — Bloom emits the TypeScript package with method names, type definitions, error classes, and examples, and shows it in the dashboard before any npm push.
  3. Compare against your last published SDK — Bloom's compatibility report diffs public surface (method names, parameter shapes, error types) so you know what's a breaking change before you bump the version.
  4. Publish to npm. Bloom can dry-run the publish, or hand off the package for you to publish from your own npm account.

Pricing model: $0 for 30 days (no credit card), then $199/mo per API project for hosted docs + 2 live SDKs. Free trial is enough to generate, preview, and publish your first version.

What "good" TypeScript SDK output looks like

Whichever path you pick, these are the signs the output is healthy:

  • Method names match operationId casing (camelCase in TS).
  • Types live in index.d.ts (or per-resource files), not inline at call sites.
  • Errors are typed: APIError with status, code, message, ideally body.
  • A single Client (or per-resource) constructor takes an options object, not positional args.
  • Pagination has helpers, not raw cursor handling.
  • The package exports work via both ESM and CJS, with proper exports map in package.json.

If the generator output is missing any of these, you'll pay for it in customer issues. The first three are non-negotiable for a public SDK.

Next: publishing to npm

Once the SDK is generated and previewed, getting it to customers means publishing. The mechanics — package.json shape, versioning, README, types, dual ESM/CJS exports, npm scoped vs unscoped — are covered in Publishing a TypeScript SDK to npm.

Want this end-to-end without maintaining templates?

Try Bloom free for 30 days — upload your OpenAPI spec, preview the generated TypeScript SDK in the dashboard, get a compatibility report against your previous version, and publish to npm from the same workflow. Completely free for 30 days. No credit card required.