Guide
Publish a TypeScript SDK from your OpenAPI document to GitHub Packages, re-published automatically every time the schema changes.
Last reviewed: April 23, 2026
Teams that already live on GitHub tend to reach for GitHub Packages the first time they need to ship a private SDK. The reasons are reasonable: the identity model is the same one the rest of the org already uses, billing is already set up, and the package URL sits next to the repository it came from. For most internal-SDK use cases the choice is over before it starts — unless you actually try to wire it up.
What you learn wiring it up is that GitHub Packages has more moving parts than a normal npm publish. The scope of the package must match the organisation. The `publishConfig` in `package.json` must point at `https://npm.pkg.github.com`. The publishing side needs a token with `write:packages`; `GITHUB_TOKEN` works inside Actions but expires per job and can't be used from anywhere else. Consumers still need an `.npmrc` with their own token and the right `@scope:registry` pin, and Dependabot silently doesn't see updates unless you add a second config block for GitHub Packages.
SDK Factory treats GitHub Packages as a first-class registry target. The encrypted token, the correct `publishConfig`, the scope alignment, and the consumer-side copy-paste `.npmrc` are all handled in one form. The SDK rebuilds and republishes on every schema change without anyone touching an Action.
What you actually spend time on when you wire this by hand — and what eventually drifts.
The publisher needs one `.npmrc` to authenticate pushes. Every consumer needs a different `.npmrc` to authenticate reads. Both drift. Somebody rotates a token, the publisher fixes theirs, three consumer repos break silently a day later.
`GITHUB_TOKEN` is the clean option inside Actions — but it can't publish from anything else, expires per job, and loses permissions when workflows reorganise. Classic PATs work everywhere but outlive people. Fine-grained PATs work but have quotas and surprise expiries. Each has its own failure mode and nobody picks correctly the first time.
`@acme/api-client` can only be published to GitHub Packages if the package scope `@acme` matches the organisation that owns the token's repo. A mismatch is a cryptic 403. Getting this right at setup is fine; remembering it a year later when the org renames isn't.
Dependabot ignores GitHub Packages until you add a second config block per repo with a token reference. Teams add it once and forget — and then the private SDK becomes the one dependency that silently doesn't upgrade.
The hand-rolled version versus the pasted-URL version — same end state, very different footprint.
# package.json must carry a publishConfig block
{
"name": "@acme/api-client",
"publishConfig": {
"registry": "https://npm.pkg.github.com",
"access": "restricted"
}
}
# .github/workflows/publish.yml
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # mandatory, often forgotten
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://npm.pkg.github.com
scope: '@acme'
- run: npm ci && npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# + consumer-side .npmrc in every downstream repo
# + dependabot.yml second registry block per repo
# + token-rotation SOP across the orgPaste schema URL: https://api.example.com/openapi.yaml Registry: [ GitHub Packages ▼ ] Scope: @acme Package name: @acme/api-client Token (PAT): •••••••••••• (encrypted) ┌──────────────────────────┐ │ Create & auto-publish │ └──────────────────────────┘ → publishConfig emitted for you. → Consumer .npmrc + Dependabot block shown on the app page. → Token stored encrypted, rotatable from the dashboard.
The same pain points, handled by the pipeline — not by whoever is on call this week.
Registry target is a dropdown. The scope, token, and `publishConfig` are derived from the form and emitted in the published package automatically. No hand-written Action, no `publishConfig` fixed up in a PR.
Paste a fine-grained PAT once, encrypted AES256 at rest. Rotation is a form-submit — the next publish picks up the new token automatically. No secret refactoring across three workflows.
Scope mismatches between package name and org are caught at setup, not at the first 403 in production. The `publishConfig` section in the published `package.json` is always right for GitHub Packages' rules.
The dashboard surfaces the exact `.npmrc` block consumers need, with the scope pre-filled. Dependabot config block is surfaced next to it — readers get both in one place instead of two Stack Overflow searches.
GitHub Packages uses the npm scope (the `@…` prefix) as a routing key. `@acme/api-client` is published under the `acme` organisation — and GitHub only lets you publish it if the token belongs to that organisation with `write:packages`. There is no way to publish `@acme/thing` from a personal account, even with admin access on the target org, unless the token explicitly carries the org's permissions.
This is usually fine, except when it quietly isn't. If your GitHub org renames, every `@old-name/…` package keeps existing under the old scope; newly published versions can only use the new name. If you're preparing an acquisition or a rebrand, that matters. SDK Factory lets you re-point the scope + package name without touching the schema URL, so a rename is one form submit.
`GITHUB_TOKEN` is the default token inside GitHub Actions. It works for publish, the permissions are automatic, and it disappears at the end of the job. That's a feature for security and a problem for anything outside Actions — a CLI, a SaaS like ours, or a script on a server. You cannot use `GITHUB_TOKEN` with SDK Factory.
Classic PATs work everywhere and live until you revoke them. That flexibility is also the risk — they tend to outlive the developer who created them, accrete permissions over time, and rotate only when someone notices. We support them but recommend fine-grained PATs for anything long-lived.
Fine-grained PATs are our default recommendation. You can scope them to a single organisation and a single permission (`write:packages`), give them a fixed expiry, and rotate from the UI. Every failure mode (expired, over-quota, revoked) surfaces as a 401 at publish time, which lands in the dashboard as REQUIRE_ACTION — not silently in a CI log.
Consumers read from GitHub Packages the same way publishers write to it: a scoped `.npmrc` line plus an auth token. The SDK Factory app page shows the exact block to paste into the consumer repo's `.npmrc` file, with the scope pre-substituted.
Dependabot consumption is separate. By default Dependabot only watches the public npm registry; to pick up private SDK updates, consumers need a registry block in their `dependabot.yml` with a token reference. That config is also shown on the app page, ready to copy. It's still their repo — we don't touch it — but they don't have to figure out the shape on their own.
Yes — a two-line `.npmrc` with the scope pointed at `npm.pkg.github.com` plus an auth token. The exact block to paste is shown on the SDK Factory app page. That's unavoidable; GitHub Packages is private by default.
A fine-grained PAT, scoped to a single organisation with `write:packages` and a 90-day expiry. It's the narrowest permission surface, the failure mode is loud (401 on publish → REQUIRE_ACTION in the dashboard), and rotation is a form submit.
Today each app publishes to a single registry. The common workaround is two apps pointed at the same schema URL — one publishing to public npm under a customer-facing name, one publishing to GitHub Packages under the internal name. Both rebuild on every schema change.
Yes, once consumers add a registry block to their `dependabot.yml` with a token reference. We show the exact block on the app page. Without it, Dependabot silently skips GitHub Packages updates — that's a GitHub default, not something we can change from the publisher side.
Anything speaking the standard npm protocol (a URL + a token) works via the `custom` registry option. That covers GitLab Package Registry, JFrog Artifactory's npm repos, Sonatype Nexus, AWS CodeArtifact, Verdaccio, and Cloudsmith. GitHub Packages has enough quirks to deserve its own dropdown; the rest share one.
OpenAPI to TypeScript SDK
Turn any OpenAPI 3.x document into a published, auto-rebuilding TypeScript SDK — without adding a single file to your API repo.
OpenAPI to npm package
Go from an OpenAPI URL to a versioned npm package on your registry — public, private, or custom — every time the schema changes.
OpenAPI to Zod-validated client
An OpenAPI-generated TypeScript client with Zod schemas validating every request and response at the runtime boundary.
OpenAPI to private npm registry
Publish your TypeScript SDK to a private npm registry — Verdaccio, JFrog Artifactory, Sonatype Nexus, AWS CodeArtifact, Cloudsmith — without writing the glue yourself.
One app on the Free tier, no card required. Paste your OpenAPI URL and see the generated GitHub Packages in minutes.