JSR vs npm: JavaScript Package Registries in 2026
JSR vs npm: JavaScript Package Registries in 2026
TL;DR
npm is the world's largest software registry — 3M+ packages, universal compatibility, and the default for every JavaScript runtime. JSR (JavaScript Registry) is Deno's TypeScript-first registry that solves real problems npm has: immutable package versions, auto-generated documentation from TypeScript sources, native ESM, and cross-runtime support (Deno, Node.js, Bun, browsers). JSR is not a replacement for npm — it's complementary. Use npm for the existing ecosystem; consider JSR for new packages that should be TypeScript-first and cross-runtime. In 2026, JSR is gaining real traction with the Deno and Hono ecosystems.
Key Takeaways
- npm registry: 3.2M+ packages — dwarfs every alternative
- JSR launched in March 2024 — growing rapidly with 40k+ packages by early 2026
- JSR packages are immutable — published versions can never be modified or deleted (unlike npm where you can
unpublishwithin 72 hours) - JSR auto-generates documentation from TypeScript source — no manual doc comments required
- JSR uses
jsr:imports natively in Deno; maps to npm in Node.js/Bun via a special registry - JSR scores packages on a scale — TypeScript support, documentation, cross-runtime compatibility
- npm provenance attestations (GitHub Actions integration) — npm now has similar trust guarantees for new packages
The Problem With npm in 2026
npm is ubiquitous but has accumulated technical debt:
- CommonJS by default — many packages still ship CJS, causing ESM interop headaches
- Mutable package versions —
npm unpublishin the first 72 hours, supply chain attacks - Types not guaranteed — many packages lack TypeScript types; require
@types/*separately - No official documentation hosting — you link to an external docs site or just README
- Package names are squatted — "is-odd", "left-pad", namespace pollution
JSR was designed specifically to solve these. It's not a reaction to npm — it's a deliberate redesign.
Publishing on npm
Package Setup
npm login
# Create package
mkdir my-package && cd my-package
npm init -y
# Set up TypeScript
npm install -D typescript tsup
{
"name": "@yourorg/my-package",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"prepublishOnly": "npm run build"
}
}
Publishing
# Build
npm run build
# Check what will be published
npm pack --dry-run
# Publish to npm registry
npm publish --access public
# Publish a beta
npm publish --tag beta
npm Provenance (Supply Chain Security)
# .github/workflows/publish.yml — npm provenance via GitHub Actions
name: Publish to npm
on:
push:
tags: ["v*"]
permissions:
id-token: write # Required for provenance
contents: read
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run build
- name: Publish with provenance
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Scoped Packages and Organizations
# Create npm organization (npmjs.com)
# Publish scoped package
npm publish --access public # Public scoped package (free)
npm publish # Private scoped package (requires paid plan)
# Deprecate old versions
npm deprecate my-package@1.x "Upgrade to v2"
# Dist tags
npm publish --tag next # Pre-release
npm dist-tag add my-package@2.0.0 latest # Promote to latest
Publishing on JSR
Package Setup
# No npm needed — JSR uses jsr.json or package.json "exports"
mkdir my-jsr-package && cd my-jsr-package
// jsr.json
{
"name": "@yourscope/my-package",
"version": "1.0.0",
"exports": {
".": "./src/index.ts" // JSR accepts TypeScript source directly!
}
}
// src/index.ts — ship TypeScript source directly
// JSR compiles for each target at download time
/**
* Formats a number as currency.
*
* @example
* ```typescript
* formatCurrency(1234.56, "USD"); // "$1,234.56"
* ```
*/
export function formatCurrency(
amount: number,
currency: string,
locale?: string
): string {
return new Intl.NumberFormat(locale ?? "en-US", {
style: "currency",
currency,
}).format(amount);
}
/**
* Validates an email address.
* @returns `true` if valid, `false` otherwise
*/
export function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Publishing to JSR
# Install JSR CLI (or use deno)
npm install -g jsr
# Or: npx jsr publish
# Login (uses GitHub OAuth)
jsr whoami
# Publish — JSR reads jsr.json automatically
npx jsr publish
# Dry run to preview
npx jsr publish --dry-run
Auto-Generated Documentation
JSR automatically generates documentation from:
- TypeScript type signatures — every exported function, class, interface
- JSDoc comments —
/** ... */comments become docs @exampleblocks — rendered as interactive code examples- Module-level JSDoc — overview documentation
/**
* A utility library for working with dates.
*
* @module
*/
/**
* Adds days to a date without mutating the original.
*
* @param date The starting date
* @param days Number of days to add (negative to subtract)
* @returns A new Date instance
*
* @example
* ```typescript
* const result = addDays(new Date("2026-01-01"), 7);
* // result is 2026-01-08
* ```
*/
export function addDays(date: Date, days: number): Date {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
Result at jsr.io/@yourscope/my-package — fully documented, searchable, with live examples.
Using JSR Packages
In Deno
// deno.json
{
"imports": {
"@std/path": "jsr:@std/path@^1.0",
"@hono/hono": "npm:hono@^4", // Mix npm and jsr
"@yourscope/utils": "jsr:@yourscope/utils@^1.0"
}
}
// Direct import with version pinning
import { formatCurrency } from "jsr:@yourscope/my-package@^1.0";
import { Hono } from "npm:hono";
const app = new Hono();
app.get("/price", (c) => {
return c.json({ price: formatCurrency(9.99, "USD") });
});
In Node.js / Bun
# JSR packages are available on npm via the @jsr/* namespace
# But prefer using the JSR CLI for proper resolution
npm install jsr
npx jsr add @yourscope/my-package
# → Adds to package.json as "jsr:@yourscope/my-package@^1.0"
# → Creates .npmrc pointing npm to jsr.io
// Node.js usage after `npx jsr add @std/path`
import { join, resolve } from "@std/path";
const fullPath = join("/home/user", "documents", "file.txt");
console.log(resolve(fullPath));
In Browsers
<!-- JSR packages work in browsers via ESM CDN -->
<script type="module">
import { formatCurrency } from "https://jsr.io/@yourscope/my-package/1.0.0/src/index.ts";
console.log(formatCurrency(9.99, "USD")); // $9.99
</script>
JSR vs npm Feature Comparison
| Feature | npm | JSR |
|---|---|---|
| Package count | 3.2M+ | 40k+ (early 2026) |
| Package immutability | ❌ (72hr unpublish) | ✅ Versions are permanent |
| TypeScript source | ❌ (build required) | ✅ Ship .ts files |
| Auto-generated docs | ❌ | ✅ From types + JSDoc |
| Publish size | Distributed | Small (TS source) |
| ESM-first | Optional | ✅ Required |
| Cross-runtime | Node-first | ✅ Deno/Node/Bun/Browser |
| Scope requirement | Optional | ✅ Required (@scope/pkg) |
| Security attestations | ✅ (Provenance, 2023) | ✅ Built-in |
| Slow abuse prevention | ❌ | ✅ Score system |
| Private packages | ✅ (paid) | ✅ (free for scopes) |
| Access control | ✅ | ✅ |
| GitHub Actions integration | ✅ | ✅ |
| Popularity | Dominant | Growing fast |
The Score System
JSR gives each package a score (0–100%) based on:
Documentation 25% — Are all exports documented?
TypeScript 25% — Does it ship TypeScript source?
Description 10% — Is there a package description?
README 10% — Does it have a README?
Runtime compat 30% — Does it work on Deno, Node, Bun, browser?
A score of 100% means the package is:
- Written in TypeScript (source)
- All exports have JSDoc
- Compatible with all major runtimes
- Has a description and README
This incentivizes quality in ways npm's model doesn't.
Should You Publish on JSR?
Publish on JSR if your package:
- Is written in TypeScript and targets multiple runtimes
- Provides utilities that work in Deno, Node.js, Bun, and browsers without modification
- Benefits from automatic documentation generation
- Is new and doesn't have npm distribution history to maintain
Keep npm as primary if your package:
- Depends on Node.js-specific APIs (
fs,http,child_process) - Has existing npm users and changing the import path would break them
- Uses CommonJS internally or depends on CJS-only packages
- Is targeted specifically at Node.js tooling
Publish to both (recommended for new packages):
# Dual publishing is easy — maintain both
jsr.json → publishes TypeScript source to JSR
package.json → publishes compiled output to npm
Ecosystem Adoption in 2026
The JSR ecosystem is growing around Deno's standard library and Hono:
- @std/ packages* — Deno's standard library now lives on JSR (50+ packages)
- @hono/hono — Hono is published on both JSR and npm
- @fresh/* — Deno Fresh framework packages
- @oak/oak — Oak web framework for Deno
The Deno Company is betting on JSR becoming the standard for cross-runtime TypeScript packages. Whether it challenges npm's dominance remains to be seen — but for new TypeScript-first packages, JSR is worth considering as a primary or dual-publishing target.
Methodology
Data sourced from official npm registry (npmjs.com), JSR registry (jsr.io), GitHub repositories, and official documentation. Package counts as of early 2026. JSR score system documented from official JSR documentation. Publishing workflows verified against current CLI versions: npm 10.x, jsr CLI 0.5.x, Deno 2.1.
Related: Bun vs Deno 2 vs Node 22 for JavaScript runtime comparisons, or pnpm vs npm vs Yarn 2026 for package manager comparisons.