Skip to content
comparison confidence: medium

Trusted Publishing on JSR vs npm: a 2026 Comparison for Library Authors

Where JSR and npm trusted publishing agree, where they diverge, and how to publish the same TypeScript library to both with one workflow and two trusted publishers.

Published May 16, 2026 · kw: JSR vs npm trusted publishing

Sources: S-001 S-002 S-004 S-005

JSR (jsr.io) launched with OIDC trusted publishing built in from day one. npm got there via the trusted-publisher feature in 2024. The two converged on the same idea — short-lived OIDC tokens from GitHub Actions in place of long-lived registry tokens — but the registration UX, the audience claim, and the provenance attestation differ.

This post is a side-by-side for library authors deciding whether to publish to one, both, or migrate.

TLDR

ConcernnpmJSR
Trusted publishing GA20242024 (launch default)
OIDC audiencehttps://registry.npmjs.orghttps://jsr.io
Permissions neededid-token: writeid-token: write
ProvenanceSigstore (via --provenance flag)Sigstore-equivalent built into the publish flow
Publish CLInpm publishnpx jsr publish (or deno publish)
Scoped namingOptional (@scope/name)Required (@scope/name)
Repository bindingManifest repository.url matchedjsr.json repo field matched
NPM_TOKEN-style legacy fallbackYes (deprecated in practice)No long-lived tokens by design

Both registries verify the GitHub OIDC token, both require the workflow to opt in with permissions: id-token: write, and both publish with provenance enabled by default.

Why this comparison exists

A maintainer who already publishes to npm has the question: should we mirror to JSR, replace npm with JSR, or ignore it? The answer depends on the consumer side — does anyone using your library run Deno, Bun, or import from JSR’s HTTP URLs?

Confidence note: the JSR side of this post relies on JSR’s own publishing docs and the Deno blog announcements — both vendor sources. Where independent third-party evidence is thin, the wording below is cautious. Sources used: S-001 npm docs, S-002 GitHub OIDC docs, S-004 Sigstore docs, S-005 cross-referenced GitHub Issues.

Where they agree

  • Workflow shape. Both registries want a GitHub-hosted runner, permissions: id-token: write, an actions/setup-node@v4 (npm) or a Deno setup action (JSR), and a publish step.
  • No long-lived tokens for the recommended path. Both registries treat legacy tokens as deprecated-in-practice for new packages.
  • Provenance. Both registries publish with a Sigstore-backed signed attestation tying the tarball/source to the workflow run.
  • Repo binding. Both registries check that the publishing workflow runs in the same repo declared in the package manifest. Stale repository.url (npm) or stale repo (JSR) values break the check.

Where they diverge

Audience

npm’s OIDC audience is https://registry.npmjs.org. JSR’s is https://jsr.io. actions/setup-node sets the audience for npm via the registry-url input. JSR sets the audience inside the JSR CLI when it requests the OIDC token. You cannot reuse one audience for both — the workflow must request two tokens, one per registry, if you publish to both.

Scoping

npm packages can be unscoped (super-lib) or scoped (@you/super-lib). JSR is scope-only — every JSR package lives under a scope you control. If you currently ship an unscoped npm package and want to mirror to JSR, you must pick a JSR scope and accept a different name there.

Manifest

npm uses package.json. JSR uses jsr.json (or deno.json with a jsr block). The two manifests must be kept in sync — version bumps in both files.

Provenance flag

npm requires --provenance on the publish command. JSR’s publish includes provenance by default with no flag. The result is the same — a signed attestation — but the surface is different.

Concrete example: one workflow, two registries

name: Publish

on:
  release:
    types: [published]

permissions:
  contents: read
  id-token: write

jobs:
  publish-npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: "https://registry.npmjs.org"
      - run: npm ci
      - run: npm publish --provenance --access public

  publish-jsr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: denoland/setup-deno@v1
        with:
          deno-version: v1.x
      - run: npx jsr publish

Each job has its own OIDC token, scoped to its registry. Both registries must have a trusted publisher configured for this exact workflow file. The two registries do not share registration — you have to register twice.

Common mistakes when mirroring

  • Version drift. Bumping package.json and forgetting to bump jsr.json. JSR’s publish refuses to overwrite a version, so this shows up as “version already exists” on JSR while npm publishes fine.
  • Different repository.url and JSR repo. Both must point at the same GitHub repo, with the same owner/repo path. Forks or org renames break both at once.
  • One job for both. Trying to publish to both registries in one job can leak the audience claim. Use separate jobs as above.
  • Forgetting permissions: id-token: write is per-job. If you only put it on the npm job, the JSR job’s OIDC mint fails.

Cautious wording: provenance is not a security guarantee

Both npm and JSR provenance prove “this tarball came from this workflow run on this commit.” Neither proves the workflow itself was not compromised. A malicious step earlier in the workflow can build a poisoned tarball that gets faithfully attested. Provenance is necessary, not sufficient. See Sigstore’s threat model (S-004) for the scope.

FAQ

Should I publish to both?

If your library targets Node + Deno + Bun, yes. JSR’s primary consumer story is Deno and Bun via import URLs; npm’s is npm install. The cost is one extra job and one extra manifest file.

Will JSR replace npm?

No announced plan. JSR is positioned as a complement, not a replacement, and several large libraries publish to both.

Does JSR support self-hosted runners?

Same constraint as npm — GitHub-hosted runners are the supported OIDC path.

Next step

If you already have npm trusted publishing working, mirroring to JSR is one job. Run the npm preflight checklist on your current setup first to confirm the npm side is clean, then add the JSR job. For the npm-only deep dives, read trusted publishing setup and the provenance failure modes.