Generating Client SDKs from OpenAPI
An API that ships an OpenAPI spec but no client leaves every consumer to hand-roll their own HTTP calls, drift out of sync when the API changes, and rediscover the same bugs independently. Generating typed client SDKs from the spec — and surfacing them in the portal next to the API — turns “read the docs and write a fetch” into “install the package”. This how-to generates, versions, and publishes client SDKs from your OpenAPI definitions. It extends Internal API Portals & Discovery within Developer Experience & Self-Service Platforms.
Prerequisites
- Backstage 1.20+ with APIs registered in the catalog and their OpenAPI specs attached.
- A valid, linted OpenAPI 3.x spec per API — generation is only as good as the spec.
- A generator such as
openapi-generatororopenapi-typescript, runnable in CI. - A package registry to publish SDKs to, and a versioning convention tied to the API version.
Exact Configuration
-
Generate the SDK from the spec in CI.
# Requires openapi-generator-cli openapi-generator-cli generate \ -i specs/payments.openapi.yaml \ -g typescript-fetch \ -o clients/payments \ --additional-properties=npmName=@internal/payments-client,supportsES6=true -
Version the SDK to match the API, so a consumer can pin a client to an API version.
{ "name": "@internal/payments-client", "version": "1.4.0", "peerDependencies": {} } -
Link the SDK from the API entity so developers find it where they find the API.
# catalog-info.yaml (the API entity) metadata: name: payments-api links: - url: https://npm.internal/@internal/payments-client title: TypeScript client SDK icon: code
Validation
# Requires the generated client, node, curl
# 1. The generated client compiles
cd clients/payments && npm install && npx tsc --noEmit
# Expected: no type errors
# 2. The SDK version tracks the API version
jq -r '.version' clients/payments/package.json
grep -m1 'version:' specs/payments.openapi.yaml
# Expected: aligned major.minor
# 3. The API entity links to the SDK
curl -s "${PORTAL_URL}/api/catalog/entities/by-name/api/default/payments-api" \
| jq '[.metadata.links[] | select(.title | test("client"))] | length'
# Expected: >= 1
Edge Cases & Troubleshooting
| Symptom | Root Cause | Resolution |
|---|---|---|
| Generated client won’t compile | Spec has invalid or ambiguous types | Lint and fix the OpenAPI before generating |
| SDK out of sync with API | Manual generation, not in CI | Regenerate on every spec change in CI |
| Breaking change ships silently | No version bump discipline | Bump the SDK major when the API breaks |
| Consumers pin to nothing | Unversioned “latest” only | Publish semver and encourage pinning |
| SDK undiscoverable | Not linked from the API entity | Add a links entry to catalog-info.yaml |
Frequently Asked Questions
Which languages should I generate for?
The ones your consumers actually write in — usually one or two, not the full matrix the generator supports. A TypeScript client and a Go client that people use beat six languages nobody installs. Start from real consumer demand, and add a language when a team asks, not speculatively.
Should the SDK be generated or hand-written?
Generated, kept thin, with any ergonomics layered in a hand-written wrapper on top. A generated core stays honest with the spec automatically; a hand-written client drifts the first time someone forgets to update it after an API change. If the generated output is awkward, wrap it — do not fork it.