Build Tools5 min read

npm Package Publishing

Publish npm packages: package.json exports configuration, semantic versioning, Changesets for changelogs, automated publishing with CI, and scoped packages.

npmpublishingpackageopen-source

Publishing an npm package correctly requires proper `package.json` configuration (exports map, engines, files), semantic versioning, changelog management, and automated CI pipelines. Modern packages publish both ESM and CommonJS with TypeScript types.

Use Changesets for version management and changelog generation. Configure the `exports` map properly for ESM/CJS dual publishing. Include only necessary files with the `files` field. Automate publishing via GitHub Actions on changeset merge.

Workflow

  1. Configure package.json correctlyCorrect package.json tells Node.js, bundlers, and TypeScript how to resolve your package.
  2. Manage versions with ChangesetsChangesets automates semver bumps and changelog generation based on change descriptions.
  3. Automate publishing with GitHub ActionsManual publish steps are error-prone; automating ensures every merge to main triggers a release.
  4. Test the package before publishingTesting the actual published package catches issues that don't appear in development.
  5. Handle scoped packages and accessScoped packages (@org/package) require proper npm organization setup and access configuration.

Output Format

package.json with exports map (ESM + CJS + types), `files: ["dist"]`, `sideEffects: false`, Changesets for version bumps, GitHub Actions for automated publish, `npm pack` for local testing.

Examples

Exports map: `"exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } }, "./react": { "import": "./dist/react.js", "require": "./dist/react.cjs" } }`
Release action: `- uses: changesets/action@v1 with: { publish: "npm run release", version: "npm run version", commit: "chore: version packages", title: "chore: version packages" } env: { GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}, NPM_TOKEN: ${{ secrets.NPM_TOKEN }} }`
Package test: `npm pack && cd /tmp && mkdir pkg-test && cd pkg-test && npm init -y && npm install path/to/my-package-1.0.0.tgz && node -e "const p = require('my-package'); console.log(p)"`

Edge Cases & Gotchas

  • Dual CJS/ESM packages: when `"type": "module"` is set, all `.js` files are ESM; CJS output must use `.cjs` extension. TypeScript consumers need separate `.d.ts` and `.d.cts` declaration files
  • Conditional exports ordering: Node.js evaluates exports conditions in order; always put specific conditions before general ones, and `types` before `import`/`require` (TypeScript reads it first)
  • Breaking changes and deprecation: when releasing breaking changes, use `npm deprecate old-version` to guide users, add migration guide in CHANGELOG, and bump major version

When to Break These Rules

  • For internal-only packages used only within a monorepo, skip npm publishing entirely - use `workspace:*` dependencies and TypeScript path aliases for direct source consumption.

Reference Files

  • npm package.json: https://docs.npmjs.com/cli/v10/configuring-npm/package-json
  • Changesets: https://github.com/changesets/changesets
  • Changesets GitHub Action: https://github.com/changesets/action
  • Package exports: https://nodejs.org/api/packages.html#conditional-exports

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.