# lerna-changelog: building release notes from merged pull requests

> lerna-changelog is a MIT licensed command line tool that turns merged GitHub pull requests into changelog sections, grouped by PR label, with monorepo package names added automatically. It depends on how your team labels pull requests and on the GitHub API staying responsive.

**lerna/lerna-changelog** — :book: PR-based changelog generator with monorepo support

- Repository: https://github.com/lerna/lerna-changelog
- Stars: 801 · Forks: 96
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/lerna-lerna-changelog

## Pull request labels, not commit messages, decide the sections

lerna-changelog reads GitHub pull requests rather than your git history. The whole output is assembled from merged pull requests that carry a recognised label, and each label becomes a section heading. Five labels are supported by default: `breaking` for a breaking change, `enhancement`, `bug`, `documentation` and `internal`.

The command is meant to be run from a project directory and prints markdown to the terminal.

```bash
npx lerna-changelog
```

The generated document starts with an unreleased heading and groups entries under the label sections.

```md
## Unreleased (2018-05-24)

#### :bug: Bug Fix
* [#198](https://github.com/my-org/my-repo/pull/198) Avoid an infinite loop ([@helpful-hacker](https://github.com/helpful-hacker))

#### :house: Internal
* [#183](https://github.com/my-org/my-repo/pull/183) Standardize error messages ([@careful-coder](https://github.com/careful-coder)
```

Each entry links to the pull request and the author, and the closing section lists the committers involved. That author and link detail is the reason to choose a pull request based tool: it produces notes a reader can act on, not a wall of commit subjects.

## The latest tag is the default boundary for the range

Out of the box the tool shows every pull request merged since the latest tagged commit in the repository. That default is convenient for a release workflow, since the tag is exactly the moment you cut the previous release, and it means the changelog for the next release collects everything that happened after it.

When you need a window that does not match the tags, the `--from` and `--to` options set the range explicitly.

```bash
npx lerna-changelog --from=v1.0.0 --to=v2.0.0
```

So the same binary serves two jobs: the everyday regenerate before a release, and a one off regeneration of an old range when a release note was written badly or lost.

The range is a git range, not a date range, and the tool needs the tags to exist locally or remotely to resolve it. That assumption matters in shallow clones, where the tags may be missing entirely, and in repositories that were imported without their original tag history.

## Monorepo packages are detected from a packages folder

The feature that distinguishes this tool is monorepo support. The documentation states that if you have a packages folder with your projects in subfolders of it, lerna-changelog will detect it and include the package names in the changelog for the relevant changes.

That matters for anyone running lerna, where a single release moves several packages at once. A flat list of merged pull requests tells a consumer nothing about which package changed, so the tool adds that context to each entry.

Detection is based on folder layout rather than on a lerna.json file, according to the documentation. That is a simple rule with a simple failure mode: a monorepo that keeps its packages somewhere else, or uses a different directory name, gets no package attribution at all, and the tool falls back to producing an ordinary changelog. The repository topics list `monorepo` and `lerna-changelog` alongside `changelog-generator`, and the package description in `package.json` is simply "Generate a changelog for a lerna monorepo".

The console entry point is `bin/cli.js`, mapped to the command name `lerna-changelog` in the `bin` field, and the package script `changelog` runs that same file locally.

## Rate limits are why the tool wants an authentication token

The tool talks to the GitHub API for every pull request it renders, and the documentation warns that you may hit rate limiting. The fix is to supply a personal access token through an environment variable.

```bash
export GITHUB_AUTH="..."
```

The scope depends on what you are querying. For private repositories the token needs the `repo` scope, and for public repositories the `public_repo` scope is enough. Without a token the tool still works for small ranges, but a monorepo with a long history and many pull requests is exactly the case that will start returning errors.

There is a second, quieter mitigation in the configuration: a `cacheDir` option pointing at a directory such as `.changelog` where GitHub API responses are cached to avoid throttling. That turns repeated runs into local reads and is worth setting up in CI where the same range is generated more than once.

## Configuration is a changelog key in package.json

The easiest configuration path is a `changelog` key in the project's `package.json`.

```json
{
  // ...
  "changelog": {
    "labels": {
      "feature": "New Feature",
      "bug": "Bug Fix"
    }
  }
}
```

Five options are documented. `repo` holds your org and repo name on GitHub and is inferred from `package.json` when you leave it out. `nextVersion` sets the title used for unreleased commits, with `Unreleased` as the example. `labels` maps GitHub pull request labels to changelog section headers, and the example shows that a label named `feature` can be given a heading of `New Feature`. `ignoreCommitters` takes a list of committers to skip, matched exactly or partially, which the documentation suggests for ignoring bot commits. `cacheDir` is the API response cache path.

Overriding `labels` is the option teams actually change, because it replaces the default set of five with your own naming. Whether it replaces or extends the defaults is not spelled out in the documentation, so treat the label list as the full set of headings you want and check the output after the first run.

## Commit based tooling takes a different route to the same file

The repository itself carries a second approach as development tooling. Its `package.json` lists `release-it` and `release-it-lerna-changelog` as dev dependencies, and the release script is `release-it` with a `.release-it.js` configuration file at the root. A changelog driven by a release tool and its commit convention works from the git history on your machine, with no GitHub API call and no pull request labels involved.

The difference is concrete. lerna-changelog queries the GitHub API for merged pull requests, so it needs labels, a token on private repositories, and network access, and it produces entries with author handles and pull request links. A commit driven generator works offline on the tags and messages already in the clone, which is faster and works on any forge, but its output quality depends on how disciplined your commit messages are and it cannot link back to a review thread.

So the choice is not between two changelog formatters. It is between reading review metadata from a hosting platform and reading history from git. The repository's own `CHANGELOG.md` at the root shows the project eats its own output, and the `scripts` block includes a `changelog` entry that runs the CLI directly.

## Commits are recent while tagged releases stopped in 2021

The maintenance picture needs two dates, not one. The repository is not archived and the last push was on 2026-10-18, so work is landing. The published versions tell a different story: v2.2.0 was released on 2021-10-11, v2.1.0 on 2021-08-07 and v2.0.1 on 2021-08-07. The version in `package.json` is still 2.2.0, matching the newest tag.

That combination means the published package on npm is the 2021 build, while the repository has moved on since. Anyone installing `lerna-changelog` from the registry gets code from 2021. Anyone running it from a clone gets the newer TypeScript sources, which build with `yarn build` and a `tsc` step into a `lib` folder.

The declared engine range is `node 12.* || 14.* || >= 16`, which is old enough that a current Node release falls into the open ended branch but that the listed middle versions are long out of support. The dependency set is dated in the same way: `chalk` 4, `execa` 5, `make-fetch-happen` 9, `yargs` 17, with TypeScript 5.0.4 and Jest 28 for development.

Licensing is MIT, stated in `package.json` and covered by the LICENSE file at the repository root. That permits use inside proprietary tooling without extra conditions, though the notice has to be retained, and this is a reading of the licence terms rather than legal advice.

## Conclusion

Adopt lerna-changelog if your team already labels pull requests consistently and publishes from GitHub tags, because the output is only as good as that labelling discipline. Skip it if your release notes have to come from commit history or from a non GitHub forge, since the tool reads pull request metadata through the GitHub API. Verify first that the five default labels exist in your repository, since a pull request without a recognised label is left out of the changelog.

## FAQ

### How do I run lerna-changelog to generate release notes?

Run `npx lerna-changelog` from the project directory. It prints markdown for all pull requests merged since the latest tagged commit, grouped into sections by pull request label.

### Which pull request labels does lerna-changelog understand?

Five labels are supported by default: `breaking`, `enhancement`, `bug`, `documentation` and `internal`. You can map your own labels to headings through the `labels` option in the `changelog` key of package.json.

### Can lerna-changelog produce notes for a specific version range?

Yes. The `--from` and `--to` options set the range explicitly, for example `npx lerna-changelog --from=v1.0.0 --to=v2.0.0`, which is useful for regenerating notes for an old release.

### Why does lerna-changelog ask for a GitHub token?

It queries the GitHub API for pull request data and can hit rate limits without authentication. Setting the `GITHUB_AUTH` environment variable to a personal access token fixes it, with the `repo` scope for private repositories or `public_repo` for public ones, and a `cacheDir` can store API responses locally.

### How does lerna-changelog handle monorepo packages?

If your projects live in subfolders of a packages folder, the tool detects that layout and adds the relevant package names to the changelog entries for those changes.

## Sources

- [Issues](https://github.com/lerna/lerna-changelog/issues)
- [lerna/lerna-changelog on GitHub](https://github.com/lerna/lerna-changelog)
- [License: MIT](https://github.com/lerna/lerna-changelog/blob/master/LICENSE)
- [README](https://github.com/lerna/lerna-changelog/blob/master/README.md)
- [Releases](https://github.com/lerna/lerna-changelog/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lerna-lerna-changelog
