NodeJSPublishing a Package to npm

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 (check npm view <name> — if it errors with 404, the name is free).

  • A correct version following semver.

  • Proper entry points — main, exports, and type (see package.json Explained).

  • A files allow-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 license field.

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

JSON
{
  "files": ["dist", "README.md"],
  "main": "./dist/index.js",
  "exports": { ".": "./dist/index.js" }
}

Bash
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
`npm pack --dry-run` is non-negotiable
Run it before every publish. It is the only reliable way to catch a leaked `.env`, an accidentally-included `src/` with API keys, or a 200 MB stray file *before* it is permanently on the public registry. Items in `files` are an allow-list; `package.json`, `README`, and `LICENSE` are always included regardless.
Version, then publish

Never hand-edit the version. npm version bumps it correctly, commits, and tags git in one step:

Bash
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

Bash
npm publish --access public     # required the first time for @scope/name
Scoped packages need `--access public`
A scoped name like `@acme/widget` defaults to *private* (a paid feature). To publish it free and public, you must pass `--access public` on the first publish — otherwise npm rejects it with a 402 Payment Required. Unscoped names are public automatically.
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:

JSON
{
  "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:

Bash
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
Unpublishing public packages is severely restricted
To prevent breaking the ecosystem (the infamous "left-pad" incident), npm only lets you `npm unpublish` within **72 hours** of publishing, and only if nothing depends on it. After that, you cannot remove it. The correct fix for a bad release is `npm deprecate` (warn installers) followed by a new patched version — never deletion. **A published version number can never be reused**, even after unpublishing.

Bash
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 --provenance from CI) to cryptographically link the package to its source commit and build.

  • Watch your dependencies too — see Dependency & Secret Security.

Next
Manage many packages in one repository without the publish-and-link dance: [Monorepos & Workspaces](/nodejs/monorepos).