Publishing from a Monorepo
Publishing multiple packages from a monorepo adds complexity: coordinating versions, managing interdependencies, and automating releases across packages.
The Challengeโ
In a monorepo with packages A, B, and C where B depends on A:
- When A changes, B's dependency on A must be updated
- Both A and B need new versions published
- Consumers need consistent, compatible versions
- CI must publish packages in the correct order
Workspace Protocolsโ
pnpm and Yarn (Berry) use the workspace: protocol to link local packages during development:
{
"dependencies": {
"@my-org/package-a": "workspace:^"
}
}
At publish time, the workspace protocol is replaced with the actual version:
| Protocol | Resolves to |
|---|---|
workspace:* | Exact version (e.g., 1.2.3) |
workspace:^ | Caret range (e.g., ^1.2.3) |
workspace:~ | Tilde range (e.g., ~1.2.3) |
pnpm and Yarn (Berry) support the workspace: protocol; npm does not โ npm workspaces save plain semver ranges and publish them as-is, with no publish-time rewriting. pnpm has the most mature publish-time rewriting. See npm Workspaces.
Changesets (Recommended)โ
Changesets is the most popular tool for monorepo versioning and publishing. It uses a file-based approach where developers declare the impact of their changes.
Setupโ
npm install -D @changesets/cli
npx changeset init
This creates a .changeset/ directory in your repo root.
Workflowโ
1. Add a changeset (per PR)โ
npx changeset
This interactive prompt asks:
- Which packages changed?
- What type of bump? (major/minor/patch)
- A summary of the change
It creates a markdown file in .changeset/:
---
"@my-org/package-a": minor
"@my-org/package-b": patch
---
Added streaming support to package-a. Updated package-b to use the new API.
2. Version packagesโ
npx changeset version
This consumes all pending changesets and:
- Bumps package versions
- Updates interdependency versions
- Generates/updates CHANGELOG.md files
3. Publishโ
npx changeset publish
Publishes all packages with new versions to the registry.
Changesets with GitHub Actionsโ
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: npm ci
- name: Create Release PR or Publish
uses: changesets/action@v1
with:
publish: npx changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
The changesets/action creates a "Version Packages" PR that accumulates changesets. Merging it triggers publishing.
Lernaโ
Lerna was the original monorepo publishing tool. While less common for new projects, it's still maintained (now by Nx):
npx lerna publish
Lerna can use either fixed versioning (all packages share a version) or independent versioning.
Nx Releaseโ
Nx includes a built-in release command:
npx nx release
Features:
- Automatic version detection from conventional commits
- Dependency graph-aware publishing (correct order)
- Changelog generation
- Dry-run mode
# Preview what would happen
npx nx release --dry-run
# Release only affected packages
npx nx release --projects=packages/*
CI/CD Pipeline Patternsโ
Publish on merge to mainโ
on:
push:
branches: [main]
jobs:
publish:
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx changeset publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Canary releases from PRsโ
Publish preview versions from pull requests for testing:
npx changeset version --snapshot canary
npx changeset publish --tag canary
This produces versions like 1.2.4-canary-20260611.0 that won't affect the latest tag.
Pre-release channelsโ
For alpha/beta releases alongside stable:
npx changeset pre enter beta
# ... continue adding changesets ...
npx changeset version # Produces 2.0.0-beta.0, etc.
npx changeset publish --tag beta
npx changeset pre exit # Return to stable releases
Tipsโ
- Always build before publishing โ Add
prepublishOnlyscripts to each package - Use
--dry-runโ Test publishing without actually hitting the registry:npm publish --dry-run - Publish in topological order โ Tools like Changesets and Nx handle this automatically based on the dependency graph
- Pin workspace dependencies for publishing โ With pnpm or Yarn Berry, use
workspace:^rather thanworkspace:*so consumers get proper semver ranges (npm has noworkspace:protocol โ its ranges are published as-is) - Automate changelogs โ Changesets generates per-package CHANGELOGs automatically from changeset descriptions