{"post":{"slug":"how-to-publish-an-npm-package-2026","title":"How to publish an npm package in 2026","excerpt":"Publishing an npm package correctly means handling versioning, provenance, and dual ESM/CJS output. Here is the complete 2026 workflow.","category":"code","date":"April 1, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">P</span>ublishing an npm package in 2026 involves more decisions than `npm publish` and a version bump. The ecosystem has settled on dual ESM/CJS output, exports maps, automated versioning, and provenance attestation. Get these right once and the ongoing publishing workflow is nearly automatic.\n\n## What changed in 2026\n\n- **npm provenance is expected** — `npm audit` and security tooling now flag packages published without provenance. It is a single CI flag.\n- **Pure ESM is still not universal** — enough tooling (Jest configs, CJS-only bundlers) still requires CJS output. Ship both unless your audience is exclusively modern bundlers.\n- **`exports` map replaced `main`** — the `exports` field in `package.json` controls exactly what consumers can import, preventing accidental deep imports.\n- **Changesets became the standard** for monorepo and multi-package versioning; it replaced manual `npm version` commands on most maintained projects.\n\n## Package structure\n\n```\nmy-package/\n├── src/\n│   └── index.ts\n├── dist/\n│   ├── index.js        # ESM\n│   ├── index.cjs       # CJS\n│   └── index.d.ts      # Types\n├── package.json\n├── tsconfig.json\n└── tsup.config.ts\n```\n\n## Build setup with tsup\n\n`tsup` is the standard build tool for TypeScript libraries in 2026:\n\n```bash\nnpm install --save-dev tsup typescript\n```\n\n```ts\n// tsup.config.ts\nimport { defineConfig } from 'tsup';\n\nexport default defineConfig({\n  entry: ['src/index.ts'],\n  format: ['esm', 'cjs'],\n  dts: true,\n  clean: true,\n  sourcemap: true,\n});\n```\n\nBuild:\n\n```bash\nnpx tsup\n```\n\n## package.json exports map\n\n```json\n{\n  \"name\": \"@myorg/my-package\",\n  \"version\": \"1.0.0\",\n  \"type\": \"module\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.cjs\",\n      \"types\": \"./dist/index.d.ts\"\n    }\n  },\n  \"main\": \"./dist/index.cjs\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"files\": [\"dist\"],\n  \"scripts\": {\n    \"build\": \"tsup\",\n    \"prepublishOnly\": \"npm run build\"\n  }\n}\n```\n\nThe `files` array ensures only `dist/` is published — not `src/`, test files, or config files.\n\n## Versioning with Changesets\n\n```bash\nnpm install --save-dev @changesets/cli\nnpx changeset init\n```\n\nWorkflow:\n\n1. When your PR adds a change, run `npx changeset` and select the bump type (patch/minor/major) with a description.\n2. This creates a file in `.changeset/`.\n3. Merge the PR. Run `npx changeset version` to apply bumps and update `CHANGELOG.md`.\n4. Commit, tag, push, and publish.\n\nThe GitHub bot (`changeset-bot`) can automate steps 3–4 via a release PR.\n\n## GitHub Action for automated publishing\n\n```yaml\n# .github/workflows/publish.yml\nname: Publish\non:\n  push:\n    tags: ['v*']\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      id-token: write  # required for provenance\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          registry-url: 'https://registry.npmjs.org'\n      - run: npm ci && npm run build\n      - run: npm publish --provenance --access public\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n```\n\n`--provenance` links the published package to the exact GitHub Actions run, commit SHA, and repository. Consumers can verify the package was not tampered with.\n\n## Checklist before first publish\n\n| Item | Command / check |\n| --- | --- |\n| Package name available | `npm view @myorg/my-package` returns 404 |\n| Only `dist/` in tarball | `npm pack --dry-run` |\n| Types resolve correctly | `npx arethetypeswrong .` |\n| No secrets in dist | Review `npm pack` output |\n| `README.md` present | Shown on npmjs.com |\n| License set | `\"license\": \"MIT\"` in package.json |\n\n`arethetypeswrong` (`attw`) is a 2026-standard tool that verifies your `exports` map resolves types correctly in all module resolution modes.\n\n## How to pick your versioning strategy\n\n1. **Single package repo** — `npm version patch/minor/major` + a git tag is sufficient.\n2. **Monorepo with multiple packages** — Changesets; it handles coordinated version bumps.\n3. **Automated release** — semantic-release if you want fully automated semver from commit messages. Changesets if you prefer human-described changes.\n\n## Common mistakes\n\n**Publishing with `--access public` missing for scoped packages.** Scoped packages default to private; add `--access public` on first publish.\n\n**Not checking the tarball contents.** Run `npm pack --dry-run` before publishing. You will occasionally find test files or `.env` in the output.\n\n**Missing `files` field.** Without it, everything in the repo is published. Always specify `\"files\": [\"dist\"]`.\n\n**Incorrect `exports` map.** Tools that respect exports will throw if the paths do not exist. Test with `attw` before publishing.\n\n## What to skip\n\n- **Publishing from your local machine.** A published package should come from a reproducible CI environment, not a developer machine with local patches.\n- **`npm version` commit messages without automation.** Once you have more than one package, do Changesets.\n- **Shipping TypeScript source without compiled output.** Some consumers cannot run TypeScript source; always ship compiled JS.\n\n## FAQ\n\n**Do I need to publish to npm, or can I use a private registry?**\nBoth. GitHub Packages, Verdaccio, and JFrog Artifactory support the npm protocol. Point your `.npmrc` to the registry URL.\n\n**How do I deprecate a version?**\n`npm deprecate @myorg/my-package@\"<1.2.0\" \"Upgrade to 1.2.0 — security fix\"`. The message shows in install output.\n\n**What is the difference between `main` and `exports`?**\n`main` is the legacy CJS entry point. `exports` is the modern map that supports conditions (import/require/types) and blocks deep imports. Both should be set for maximum compatibility.\n\n**How do I test a local package before publishing?**\nUse `npm link` or `pnpm add ../my-package` to install the local version in a test project. Or `npm pack` and install the tarball.\n\n## Where to go next\n\n- [How to set up a monorepo in 2026](/blog/how-to-set-up-a-monorepo-2026)\n- [How to write a GitHub Action in 2026](/blog/how-to-write-a-github-action-2026)\n- [How to set up pre-commit hooks in 2026](/blog/how-to-set-up-pre-commit-hooks-2026)","hue":310,"metaTitle":"How to Publish an npm Package in 2026: ESM, Provenance, CI","metaDescription":"Complete guide to publishing an npm package in 2026. Covers dual ESM/CJS output, Changesets versioning, npm provenance, and CI automation.","keywords":["how to publish an npm package 2026","npm publish 2026","npm package esm cjs dual output","npm provenance 2026","changesets versioning npm","npm package exports map","publish npm package github actions","typescript npm package 2026","scoped npm package","npm package best practices 2026"],"tldr":["**Set `\"type\": \"module\"` and ship both ESM and CJS** — most consumers still need CJS; dual output with `exports` map covers both.","**Use Changesets for versioning** — it generates changelogs and version bumps without manual edits.","**Enable npm provenance** — one flag in your GitHub Action links the published package to the exact commit and workflow that built it.","**Scope your package as `@yourorg/package-name`** — scoped packages avoid name collisions and signal ownership.","**Skip:** manually running `npm publish` from your laptop — automate publishing via a GitHub Action triggered on release tags; human-run publishes skip CI and provenance."],"readTime":"4 min read","wordCount":883},"related":[{"slug":"how-to-deploy-with-github-actions-2026","title":"How to deploy with GitHub Actions in 2026","excerpt":"GitHub Actions is the default CI/CD choice for most teams in 2026. This guide covers fast, secure deployment pipelines that actually reach production safely.","category":"code","date":"March 12, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":306},{"slug":"how-to-write-a-github-action-2026","title":"How to write a GitHub Action in 2026: CI workflows, secrets, and reuse","excerpt":"GitHub Actions is the de facto CI/CD platform for open-source and most private projects. This guide covers writing workflows, managing secrets, and reusing logic.","category":"code","date":"March 1, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":311},{"slug":"how-to-set-up-ci-cd-2026","title":"How to set up CI/CD in 2026: a practical pipeline from zero to deploy","excerpt":"A CI/CD pipeline that catches bugs before production is one of the highest-ROI investments a team can make. This 2026 guide walks through the full setup.","category":"code","date":"February 26, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":311}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-prepare-for-interview-2026","title":"How to prepare for a job interview in 2026: the complete playbook","excerpt":"Interviews are more structured and more researched than ever in 2026. This playbook covers prep, STAR stories, questions to ask, and the day-of checklist.","category":"guides","date":"April 1, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":295},"older":{"slug":"how-to-quit-a-job-gracefully-2026","title":"How to quit a job gracefully in 2026: leave well and protect your reputation","excerpt":"How you leave a job is remembered as clearly as how you performed it. Here is the 2026 playbook to resign professionally, protect your network, and exit cleanly.","category":"guides","date":"April 1, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":310}}