You shipped a single PR that touched the API, the Node SDK, and the Python SDK. Three packages changed, two need a minor bump, one needs a patch. The API changelog should mention a new endpoint. The SDK changelogs should mention a new method. The customer-facing release notes should mention none of this and instead say "you can now filter reports by date range."
Now multiply that by twenty PRs a week across twelve packages and tell me your CHANGELOG.md files are accurate.
Monorepo changelogs are a specific kind of pain. Not because monorepos are bad (they're often the right architecture), but because every changelog tool was designed for a world where one repository means one package means one version means one changelog. The moment you break that assumption, everything gets harder.
I've spent the last year talking to engineering leads running monorepos at every scale, from three-package Turborepo setups to hundred-package Nx workspaces. The pattern is the same everywhere: monorepo release notes start strong, degrade within a quarter, and eventually someone proposes "let's just not write changelogs" as a serious solution. Here's why that happens and what to do instead.
Why Monorepo Changelogs Are Uniquely Painful
A single-repo, single-package project has a linear version history. Every commit contributes to one changelog. Every tag marks one release. The mapping from "what changed" to "what to tell people" is straightforward, even if nobody does it well.
Monorepos break this in three ways.
Independent versioning creates combinatorial complexity. If @acme/api is on v3.2.1 and @acme/sdk-node is on v2.8.0 and @acme/dashboard is on v1.14.3, a single PR that touches all three packages needs to produce three distinct changelog entries with three distinct version bumps. The person writing that PR is thinking about the feature they built, not about version arithmetic across three dependency trees.
Cross-cutting changes defy per-package categorization. Some changes are genuinely about one package. A bugfix in the dashboard's date picker belongs in the dashboard changelog and nowhere else. But a schema change in the API that requires SDK updates and triggers a dashboard redesign is one logical change that spans three packages. Where does it go? All three changelogs with slightly different descriptions, or one root-level changelog that aggregates everything? Both options create work that nobody wants to do.
Release cadences diverge. The API ships weekly. The SDKs ship on demand when customers need new endpoints. The dashboard ships continuously. Three different release rhythms mean three different changelog update schedules, and the single PR that touched all three packages needs its changelog entries to land in the right releases for each package. If your SDK hasn't shipped since the PR merged, the changelog entry is sitting in limbo, invisible to customers who are reading the API changelog and wondering why the SDK doesn't support the new endpoint yet.
These aren't theoretical problems. They're the reason your monorepo has a root CHANGELOG.md that was last updated four months ago and per-package changelogs that were never created in the first place.
The Three Common Approaches (and Where Each Breaks)
Per-Package CHANGELOG.md
The simplest model: every package gets its own CHANGELOG.md, maintained manually or via tooling. This is what Keep a Changelog recommends, and it works if you have two or three packages with clear boundaries and infrequent cross-cutting changes.
It falls apart around package five or six. The problem isn't the files themselves, it's the cognitive load on the developer. When a PR touches four packages, the developer is now expected to write four changelog entries, decide on four version bumps, and ensure consistency across four files. Most developers will write one entry (in the package they consider "primary"), forget the others, and move on. The resulting changelogs have gaps that compound over time until nobody trusts them.
If you want to understand what makes a good changelog entry when you do write them, our complete guide to release notes best practices covers the formatting and audience awareness fundamentals.
Root-Level Aggregated Changelog
Some teams give up on per-package changelogs and maintain a single CHANGELOG.md at the monorepo root. Every release gets one entry that lists all changes across all packages.
This solves the "developer forgot to update four files" problem, but it creates a new one: the changelog is useless to anyone who cares about a specific package. If you're a customer using @acme/sdk-node and you want to know what changed in the SDK, you don't want to wade through dashboard UI tweaks and internal tooling updates. A root-level changelog is a firehose. Nobody drinks from a firehose.
Changesets Workflow
Changesets is the most thoughtful solution to the monorepo changelog problem. It asks developers to declare what changed, which packages are affected, and what the semver impact is, all at PR time when the context is fresh. A bot aggregates the changeset files and produces per-package changelogs with coordinated version bumps.
If you're publishing npm packages from a monorepo, Changesets is the right tool for the job. Full stop. The workflow is well-designed, the per-package version management is excellent, and the generated changelogs are higher quality than anything inferred from commit messages because a human wrote the entry.
The friction is real, though. Every PR needs a changeset file. Every developer needs to understand the workflow. When someone forgets (and the CI check blocks the merge), the result is either a hasty, low-quality changeset written to unblock the PR, or a frustrated developer who asks why they're writing documentation for a one-line bugfix. Multiply that friction across 20 developers and 100 PRs a month, and the overhead is meaningful.
There's a bigger limitation that fewer people talk about. Changesets produces developer-facing changelogs. They're written by developers, in developer language, for developer consumption. That's perfect if your monorepo ships libraries. It's not enough if your monorepo also ships a product with non-technical users who need to understand what changed.
Get release management tips in your inbox
Practical guides on release notes, changelogs, and shipping better software. No spam, unsubscribe anytime.
The Monorepo Changelog Tooling Landscape: Changesets, Lerna, Turborepo, and Nx
The monorepo ecosystem has converged around a few tools, each with different strengths for changelog generation.
Changesets is the gold standard for npm-centric monorepos. If you use it with Turborepo or standalone, you get per-package versioning, coordinated publishes, and clean changelogs. Our step-by-step guide to changelog automation walks through the GitHub Actions setup if you want working YAML.
Lerna was the original monorepo tool and still handles changelog generation through lerna version and lerna publish. The --conventional-commits flag produces a Lerna changelog from commit messages using the Angular convention. It works, but the output quality depends entirely on commit message quality, and the changelogs read like formatted git logs. Lerna's changelog generation also assumes every package releases together, which breaks down when you need independent versioning.
Turborepo doesn't include built-in changelog generation, which means Turborepo release notes require bolting on another tool. Most teams add Changesets for versioning or write custom scripts that run conventional-changelog per package. The custom script approach works until someone leaves the team and nobody remembers how the script works.
Nx has nx release, which added Nx changelog generation in recent versions. It supports both conventional commits and a Changesets-like interactive workflow. The output is decent for developer-facing changelogs, and the integration with Nx's project graph means it can automatically detect which packages were affected by a change. The limitation, like every tool on this list, is that it produces one format for one audience.
Auto-changelog and semantic-release round out the landscape. Both infer changelogs from conventional commits. Both produce technically accurate output that means nothing to a product manager or a customer. They're fine for internal developer tooling. They're insufficient for anything customer-facing.
What Breaks When Your Monorepo Release Notes Hit Scale
Everything above works tolerably when you have four packages and a release every two weeks. Here's what happens when you have twelve packages and release weekly.
Changeset fatigue sets in. Developers start writing perfunctory changeset descriptions because the process feels like busywork. "Updated SDK" replaces "Added filterByDate parameter to listReports() method." The changelogs technically exist but they've lost their information content.
Cross-cutting PRs create changelog sprawl. A single infrastructure change that updates a shared utility might affect eight packages. The developer writes one changeset. The CI bot demands seven more. The developer writes seven variations of "bumped dependency on @acme/shared" and nobody reads any of them.
The aggregation problem goes unsolved. The VP of Engineering asks "what did we ship this quarter?" and nobody can answer without opening twelve CHANGELOG.md files, mentally deduplicating the cross-cutting changes, and summarizing the result. The per-package changelogs serve individual package consumers. They don't serve anyone who needs the big picture.
Audience mismatch becomes acute. Your API changelog says feat: add date_range filter to /reports endpoint. Your SDK changelog says feat: add filterByDate option to listReports(). Your customer success team needs to know: "Customers can now filter reports by date range in the dashboard and via the API. This was the #3 requested feature." That translation happens manually, if it happens at all.
The tooling solved versioning. Nobody solved communication.
A Real-World Scenario
Your monorepo has four packages: @acme/api, @acme/sdk-node, @acme/sdk-python, and @acme/dashboard. A single PR lands that adds date range filtering. It adds the API endpoint, updates both SDKs with new method signatures, and adds a date picker to the dashboard's reports page.
With Changesets, the developer writes four changeset files. The API gets a minor bump, both SDKs get a minor bump, and the dashboard gets a minor bump. Four changelog entries describe the same feature in four slightly different ways. This is accurate, but it's also four files that a customer would need to read to understand one feature.
With conventional commits, the PR gets one commit message: feat(api): add date range filtering to reports. The API changelog picks it up. The SDK changelogs miss it entirely because the scope says api. You could scope it feat(api,sdk-node,sdk-python,dashboard): but nobody does that, and most tools don't parse multi-scope commits.
What should actually happen is this: the same underlying change should produce different outputs for different audiences. The engineer changelog should reference the PR and list the affected packages with their version bumps. The internal release notes should describe the feature in terms of customer impact and mention that both SDKs now support it. The customer-facing changelog should say "You can now filter reports by date range" and link to the updated docs. One change, three descriptions, zero manual duplication.
That's the gap. Every tool in the monorepo ecosystem solves the versioning problem well, and Changesets solves it brilliantly. But versioning and changelogs are not the same thing. A changelog is a communication artifact, and communication requires knowing your audience. A version bump doesn't.
A Better Approach: Automated Aggregation With Audience Awareness
The tooling split that makes monorepo changelogs painful is the split between "what changed technically" and "what it means to different people." Solving one doesn't solve the other.
The technical layer (Changesets, Lerna version, Nx release) should keep doing what it does well: tracking which packages changed, computing version bumps, and coordinating publishes. That's mechanical work, and these tools handle it correctly.
The communication layer needs to sit on top. It should consume the same source material (PRs, commits, changeset descriptions, linked issues) and produce audience-appropriate outputs: per-package breakdowns with migration notes for the engineer, feature summaries organized by customer impact for the PM, plain language with links to documentation for the customer. If this sounds like multi-persona release notes, it is, applied to the monorepo context where the aggregation problem makes it even harder.
This isn't a pipe dream. If you've set up automated branch promotions, you already have a release pipeline that knows what commits are in each release. The missing piece is the translation from technical changes to audience-aware communication.
Some teams build this translation layer with custom scripts. A post-release GitHub Action that reads the Changesets output, groups entries by feature rather than by package, and posts a summary to Slack. It works until the script maintainer leaves. I've seen three of these scripts in production. All three were abandoned within a year.
ReleaseRay handles this natively. It reads your monorepo's PRs, commits, and changeset descriptions across all packages, groups changes by logical feature rather than by package boundary, and generates release notes for three personas: engineer, internal team, and customer. The per-package version bumps stay with Changesets or whatever versioning tool you already use. ReleaseRay handles the part those tools were never designed to do, turning technical change records into communication that different audiences can actually use.
Practical Recommendations
If you're running a monorepo with fewer than five packages and a technical audience, Changesets is enough. Use it. Write good changeset descriptions. Run the bot. Ship the per-package changelogs. You'll be ahead of most teams.
If you have more than five packages, or if your audience includes non-technical users, you need a two-layer approach. Keep Changesets (or Nx release, or Lerna version) for the mechanical versioning and per-package tracking. Add an aggregation and translation layer on top that produces audience-aware summaries across packages.
If you're spending more than an hour per release writing, editing, or reformatting changelogs for different audiences, that hour is a signal. It's telling you that the communication problem has outgrown your tooling. Automate the translation, keep the versioning tools you trust, and stop asking developers to be technical writers on top of everything else they already do.
Your monorepo architecture is a strength. Your changelog process shouldn't be the thing that makes it feel like a liability.
If you're ready to stop writing the same feature description in four different CHANGELOG.md files, try ReleaseRay free. It connects to your GitHub repos, aggregates changes across packages, and generates audience-specific release notes from a single source of truth. Setup takes 10 minutes. The first generated draft arrives the next time a PR merges.
Further Reading
- Release Notes Best Practices: The Complete Guide
- How to Automate Your Changelog with GitHub Actions
- Automate Branch Promotions with GitHub Actions
- Why Multi-Persona Release Notes Matter
Cristobal Mitchell is the founder of ReleaseRay and a VP of Engineering at an investment research firm. He builds tools to eliminate the operational overhead that slows engineering teams down.
Enjoyed this post?
Practical guides on release notes, changelogs, and shipping better software. No spam, unsubscribe anytime.
Cristobal Mitchell
Founder of ReleaseRay
Building ReleaseRay — automated release notes from GitHub PRs for developers, PMs, and customers.
Related Posts
How to Automate Your Changelog with GitHub Actions (Step-by-Step)
A practical comparison of four approaches to automated changelog generation: Release Drafter, Changesets, Conventional Commits tooling, and ReleaseRay. Includes working GitHub Actions YAML, honest tradeoffs, and a recommendation for every team size.
Release Notes Best Practices: The Complete Guide for Engineering Teams (2026)
How to write release notes that actually get read. Audience awareness, formatting standards, and automation without the team burnout. The definitive guide for engineering teams.
Automate Branch Promotions with GitHub Actions
Free GitHub Actions templates to automate release workflows: auto-create staging and production PRs with semantic version prediction. Stop manual branch promotions and version bumping.
Ready to automate your release notes?