|$ curl https://forge-ai.dev/api/markdown?path=docs/npm/publishing
$cat docs/npm-—-publishing-packages.md
updated Recently·14 min read·published

npm — Publishing Packages

npmIntermediate
Introduction

Publishing a package to the npm registry makes it available to millions of developers worldwide. Whether you are sharing a reusable utility, a UI component library, or a CLI tool, understanding the publishing workflow is essential.

This guide covers preparing a package for publishing, scoped packages, access control, two-factor authentication, CI/CD publishing, and semantic-release automation.

Preparing for Publishing

Before publishing, ensure your package.json has all the required fields and your package is properly configured.

package.json
JSON
1// Essential package.json fields for publishing:
2{
3 "name": "my-utility",
4 "version": "1.0.0",
5 "description": "A useful utility for doing things",
6 "main": "dist/index.js",
7 "module": "dist/index.mjs",
8 "types": "dist/index.d.ts",
9 "exports": {
10 ".": {
11 "import": "./dist/index.mjs",
12 "require": "./dist/index.js",
13 "types": "./dist/index.d.ts"
14 }
15 },
16 "files": ["dist"],
17 "license": "MIT",
18 "keywords": ["utility", "helpers"],
19 "author": "Your Name <email@example.com>",
20 "repository": {
21 "type": "git",
22 "url": "git+https://github.com/username/my-utility.git"
23 },
24 "bugs": {
25 "url": "https://github.com/username/my-utility/issues"
26 },
27 "homepage": "https://github.com/username/my-utility#readme",
28 "engines": {
29 "node": ">=18.0.0"
30 }
31}
32
33// Field descriptions:
34// name: Package name (must be unique on registry)
35// version: Current version (SemVer)
36// main: Entry point for CommonJS
37// module: Entry point for ES modules
38// types: TypeScript declaration file
39// exports: Modern entry points (Node.js 12+)
40// files: Which files to include in the tarball
41// license: SPDX license identifier

info

Always include the exports field for dual CJS/ESM packages. It ensures consumers get the correct module format based on their import method. Without it, Node.js may resolve incorrectly.
Scoped Packages (@scope/name)

Scoped packages are namespaced with an @ prefix, such as @my-org/my-package. Scopes are associated with npm user or organization accounts.

package.json
JSON
1// Scoped package example:
2{
3 "name": "@my-org/utility-lib",
4 "version": "1.0.0",
5 "publishConfig": {
6 "access": "public"
7 }
8}
9
10// Scopes serve multiple purposes:
11// - Namespace packages under an organization
12// - Prevent naming conflicts
13// - Enable private package management
14// - Group related packages logically
15
16// Publishing scoped packages:
17# By default, scoped packages are PRIVATE
18# To publish a public scoped package:
19npm publish --access public
20
21# Or configure once in package.json:
22# "publishConfig": { "access": "public" }
23# Then just: npm publish
24
25// Installing scoped packages:
26npm install @my-org/utility-lib
27import { something } from "@my-org/utility-lib"

warning

Scoped packages are private by default (requires paid npm account). Set "publishConfig": { "access": "public" } in package.json to publish public scoped packages without passing --access public every time.
.npmignore vs package.json files

Control which files are included in the published tarball using .npmignore or the files field in package.json. This keeps your package small and avoids publishing sensitive files.

.npmignore
TEXT
1# .npmignore — exclude files from package
2# If .npmignore exists, it overrides .gitignore
3
4node_modules/
5src/
6tests/
7.git/
8.github/
9*.map
10*.ts (if you publish compiled JS only)
11.env
12.DS_Store
13coverage/
14
15# If you don't have .npmignore, npm uses .gitignore
16# Plus these are ALWAYS excluded:
17# .git, CVS, .svn, .hg, .lock-wscript, .wafpickle-*,
18# config.gypi, CVS, .svn, .DS_Store
19
20# These are ALWAYS included (even with .npmignore):
21# package.json, README, LICENSE, CHANGELOG
22# The "main" file and its dependencies
package.json
JSON
1// Better approach: use "files" field (whitelist)
2{
3 "files": [
4 "dist",
5 "README.md",
6 "LICENSE",
7 "CHANGELOG.md"
8 ]
9}
10
11// With the "files" field, npm ONLY includes:
12// 1. Files/directories listed in "files"
13// 2. package.json, README, LICENSE (always)
14// 3. The main/module entry point
15
16// Check what will be published:
17npm pack --dry-run
18# Lists all files that will be in the tarball
19
20# Check package size:
21npm pack --dry-run | wc -c
22# Or use: npx pkg-size
23
24// Size matters for install speed:
25// - Keep packages under 100KB if possible
26// - Avoid bundling unnecessary files
27// - Use .npmignore to exclude tests, docs

best practice

Prefer the files field (whitelist) over .npmignore (blacklist). It is declarative and less error-prone. Always run npm pack --dry-run to verify what will be published.
npm publish Command

The npm publish command uploads your package to the registry. Several flags and configurations control the publishing behavior.

terminal
Bash
1# Basic publish
2npm publish
3
4# Publish with a specific tag
5npm publish --tag beta
6# Install with: npm install my-package@beta
7
8# Publish scoped package as public
9npm publish --access public
10
11# Dry run (don't actually publish)
12npm publish --dry-run
13
14# Publish from a specific directory
15npm publish ./dist
16
17# Publish with OTP (2FA code)
18npm publish --otp 123456
19
20# Initialize package before publish:
21npm init
22# Creates package.json interactively
23
24# Login before publishing:
25npm login
26# Username: your-username
27# Password: ********
28# Email: your-email@example.com
29# Enter OTP: 123456
30
31# Who is logged in?
32npm whoami
33
34# Check if package name is available:
35npm view my-package-name
36# 404 if available, shows info if taken

info

Use npm version patch && npm publish to bump the version and publish in one step. Or better, use npm version patch && npm publish with a prepublishOnly script that runs tests first.
Access Control

npm supports both public and private packages. Private packages require a paid npm account and allow you to restrict access to specific collaborators or teams.

terminal
Bash
1# Set package access:
2# Public (visible to everyone):
3npm publish --access public
4
5# Private (requires paid npm org account):
6npm publish --access restricted
7
8# Change access after publishing:
9npm access public @my-org/my-package
10npm access restricted @my-org/my-package
11
12# Grant access to collaborators:
13npm access grant read-write user:collab-user @my-org/my-package
14npm access grant read-only team:@my-org/developers @my-org/my-package
15
16# Revoke access:
17npm access revoke user:collab-user @my-org/my-package
18
19# View package access:
20npm access ls-collaborators @my-org/my-package
21npm access ls-packages
22
23# Two-Factor Authentication (2FA):
24# Require 2FA for publishing:
25npm profile enable-2fa auth-and-writes
26# Or: npm profile enable-2fa auth-only (login only)
27
28# Set package to require 2FA for contributors:
29npm access set-mfa write-required @my-org/my-package
Two-Factor Authentication

Two-factor authentication (2FA) adds a critical security layer to your npm account. It prevents unauthorized publishing even if your password is compromised.

terminal
Bash
1# Enable 2FA on npm
2npm profile enable-2fa auth-and-writes
3# You'll be prompted to scan a QR code with your authenticator app
4
5# 2FA modes:
6# auth-only: 2FA for login only
7# auth-and-writes: 2FA for login AND publishing
8
9# Publishing with 2FA:
10npm publish --otp 123456
11# Or use an OTP device:
12npm publish
13
14# If you don't pass --otp, npm prompts for it
15
16# Configure npm for automated publishing (CI):
17# Create an automation token (not your password):
18npm token create --read-only
19npm token create --publish # for CI/CD
20
21# List tokens:
22npm token list
23
24# Revoke a token:
25npm token revoke <token-id>
26
27# Automation tokens bypass 2FA for CI/CD
28# Store tokens as secrets in your CI provider:
29# GitHub Actions: secrets.NPM_TOKEN

warning

Never store npm tokens in your repository or expose them in logs. Use CI/CD secret management. If a token is compromised, revoke it immediately with npm token revoke <id>.
Versioning & Changelogs

Consistent versioning and changelog generation help users understand what changed between releases. Follow SemVer strictly and maintain a changelog.

terminal
Bash
1# Bump version and create git tag:
2npm version patch # 1.0.0 -> 1.0.1
3npm version minor # 1.0.0 -> 1.1.0
4npm version major # 1.0.0 -> 2.0.0
5
6# Each creates a git commit and tag:
7# git add package.json && git commit -m "1.0.1"
8# git tag v1.0.1
9
10# With a pre-release identifier:
11npm version prepatch --preid=alpha
12# 1.0.0 -> 1.0.1-alpha.0
13
14# Changelog with standard-version:
15npx standard-version
16# Automatically generates CHANGELOG.md from
17# Conventional Commits, bumps version, and tags
18
19# Or generate changelog manually:
20git log --oneline --no-merges v1.0.0..HEAD
semantic-release Automation

semantic-release fully automates the package release workflow — version bumping, changelog generation, git tagging, and npm publishing — based on Conventional Commits.

package.json
JSON
1// Install semantic-release
2// package.json configuration:
3{
4 "scripts": {
5 "semantic-release": "semantic-release"
6 }
7}
8
9// .releaserc.json:
10{
11 "branches": ["main"],
12 "plugins": [
13 "@semantic-release/commit-analyzer",
14 "@semantic-release/release-notes-generator",
15 "@semantic-release/changelog",
16 "@semantic-release/npm",
17 "@semantic-release/github"
18 ]
19}
20
21// Commit messages determine version bump:
22// fix: -> PATCH (1.0.0 -> 1.0.1)
23// feat: -> MINOR (1.0.0 -> 1.1.0)
24// feat!: or BREAKING CHANGE: -> MAJOR (1.0.0 -> 2.0.0)
25
26// GitHub Actions workflow:
27# .github/workflows/release.yml
28name: Release
29on:
30 push:
31 branches: [main]
32jobs:
33 release:
34 runs-on: ubuntu-latest
35 steps:
36 - uses: actions/checkout@v4
37 - uses: actions/setup-node@v4
38 with:
39 node-version: 20
40 - run: npm ci
41 - run: npm run build
42 - run: npx semantic-release
43 env:
44 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
🔥

pro tip

semantic-release eliminates human error from the release process. Once set up, every push to main with Conventional Commit messages can automatically trigger a release. The NPM_TOKEN should be an automation token (not your personal token) with publish permissions.
CI/CD Publishing

Automating package publishing in CI/CD ensures consistency and reduces manual errors. GitHub Actions is the most common CI provider for npm publishing.

terminal
Bash
1# GitHub Actions — manual publish workflow
2# .github/workflows/publish.yml
3name: Publish to npm
4on:
5 release:
6 types: [published]
7jobs:
8 publish:
9 runs-on: ubuntu-latest
10 steps:
11 - uses: actions/checkout@v4
12 - uses: actions/setup-node@v4
13 with:
14 node-version: 20
15 registry-url: https://registry.npmjs.org
16 - run: npm ci
17 - run: npm test
18 - run: npm run build
19 - run: npm publish
20 env:
21 NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
22
23# For scoped packages with npm provenance:
24# (requires npm 9+, Node.js 16+)
25# npm publish --provenance
26# Proves the package was built on GitHub Actions
27# and links to the exact workflow run

info

Enable npm provenance (npm publish --provenance) for open-source packages. It cryptographically links the published package to its source repository and CI build, increasing trust and transparency for consumers.
Best Practices

Test Before Publishing

Use a prepublishOnly script to run tests and linting before every publish. This prevents publishing broken packages.

Use semantic-release for Automation

Automate versioning, changelog generation, and publishing with semantic-release. It enforces Conventional Commits and eliminates manual release errors.

Enable 2FA for Publishing

Require two-factor authentication for publishing to your packages. Use automation tokens for CI/CD to bypass 2FA in automated workflows.

Keep Packages Small

Use the files field to publish only what is necessary. Run npm pack --dry-run to verify. Smaller packages install faster and are less likely to contain sensitive files.

Use Provenance for Open Source

Publish with --provenance to link your package to the exact CI build that produced it. This helps consumers verify authenticity.

$Blueprint — Engineering Documentation·Section ID: NPM-PUB·Revision: 1.0