Publishing Packages
Publishing turns your code into something anyone can npm install. The mechanics are simple — npm publish uploads a tarball — but doing it well means controlling exactly what ships, versioning honestly, and protecting your account. This page walks the full lifecycle, including the mistakes that are hard to undo.
Before you publish: the checklist
A unique
name(checknpm view <name>— if it errors with 404, the name is free).A correct
versionfollowing semver.Proper entry points —
main,exports, andtype(see package.json Explained).A
filesallow-list (or.npmignore) so only build output ships — never source secrets or tests.A
README.md— it becomes the package page on npmjs.com.A
licensefield.
Control what ships
The published tarball is almost never your whole repo. Use the files allow-list to ship only what consumers need, and always preview it first:
package.json
{
"files": ["dist", "README.md"],
"main": "./dist/index.js",
"exports": { ".": "./dist/index.js" }
}npm pack --dry-run # list EXACTLY what would be published — no upload
npm notice 📦 @acme/widget@1.0.0 npm notice === Tarball Contents === npm notice 1.2kB dist/index.js npm notice 0.4kB dist/index.d.ts npm notice 0.8kB README.md npm notice 0.3kB package.json npm notice === Tarball Details === npm notice total files: 4
Version, then publish
Never hand-edit the version. npm version bumps it correctly, commits, and tags git in one step:
npm version patch # 1.0.0 → 1.0.1 (bug fix) npm version minor # 1.0.1 → 1.1.0 (new feature) npm version major # 1.1.0 → 2.0.0 (breaking change) npm publish # upload to the registry
Scoped packages are private by default
npm publish --access public # required the first time for @scope/name
The prepublish build hook
The prepare script runs automatically right before npm publish (and after npm install), which is the canonical place to build a library so you never publish stale dist:
{
"scripts": { "prepare": "npm run build", "build": "tsc -p ." }
}Pre-releases with dist-tags
Publish a beta without disturbing the latest that everyone installs by default — use a dist-tag:
npm publish --tag beta # users get it only via pkg@beta npm install widget@beta # opt in explicitly npm dist-tag add widget@2.0.0 latest # later, promote it to the default
Undoing a publish — and why you mostly can't
npm deprecate widget@1.0.1 "Critical bug — upgrade to 1.0.2" npm unpublish widget@1.0.1 # only works inside the 72h window
Protect your account and supply chain
Enable 2FA for auth and publishing (
npm profile enable-2fa auth-and-writes) — compromised maintainer accounts are a top attack vector.Use granular automation tokens in CI, never your password; scope them to the minimum.
Publish with provenance (
npm publish --provenancefrom CI) to cryptographically link the package to its source commit and build.Watch your dependencies too — see Dependency & Secret Security.