Open-source project
mikepenz/release-changelog-builder-action avatar
mikepenz/release-changelog-builder-action

release-changelog-builder-action turns merged pull requests into templated release notes

A GitHub action that builds your release notes / changelog fast, easy and exactly the way you want.

868 stars112 forksTypeScriptApache-2.0

At a glance

What is it?
Mike Penz's GitHub Action builds a changelog between two tags by collecting the pull requests in that range, grouping them by label, and rendering the result through a template with placeholders, and it does the same for commits on repositories that do not use pull requests. The output is handed straight to a release action, so the changelog is generated rather than written.
Who is it for?
release-changelog-builder-action fits a maintainer who tags releases and would rather the notes be generated from labelled pull requests than written by hand, and who wants the changelog to look the same on every release. Skip it if your project does not label its pull requests consistently, since the categories are the whole mechanism, or if you need curated prose for a major release.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The changelog is an output of the release step

The action's job is narrow and it starts from a tag range rather than from a commit list. The hero image in the README describes it as a tag range and its merged pull requests being collected, categorised by label, and written into a templated CHANGELOG.md.

The integration is three steps. The first builds the changelog for the tag that triggered the workflow.

yml
- name: Build Changelog
  id: build_changelog
  uses: mikepenz/release-changelog-builder-action@v6
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The second takes that output and uses it as the body of the release.

yml
- name: Create Release
  uses: mikepenz/action-gh-release@v3
  with:
    body: ${{ steps.build_changelog.outputs.changelog }}

The third is where the shape of the notes is decided, by declaring categories. The action is therefore not a standalone script you run and paste from, it is a step in a pipeline whose result is consumed by another step.

The action is published in the GitHub Marketplace, and the README carries an OpenSSF Scorecard badge alongside the CI badge and the Apache-2.0 licence badge. The default branch is `develop`, the language is TypeScript, and the licence is Apache-2.0 with the text in the `LICENSE` file.

A full sample workflow in the README shows the shape of a tag-triggered release, with the job conditioned on the ref being a tag and the steps running on ubuntu-latest.

yml
name: 'CI'
on:
  push:
    tags:
      - '*'

jobs:
  release:
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    steps:
      - name: Build Changelog
        id: github_release
        uses: mikepenz/release-changelog-builder-action@v6
        env:

The `if` condition is what keeps the job from running on ordinary pushes, and the tag filter in the trigger is the first half of the same guard.

Labels are the section structure

The mechanism that makes the output look like a changelog rather than a commit dump is label based categorisation, and the README is explicit that a pull request can land in several categories or in none.

The `categories` configuration maps labels onto section titles, so a `feature` label produces a Features section and a `fix` or `bug` label produces a Fixes section. The category is a title plus the list of labels that belong to it.

The decision about a pull request that carries no matching label is where the design has to make a call, and the answer is the `UNCATEGORIZED` placeholder. The reference lists `#{{UNCATEGORIZED}}` among the available placeholders, which means the section for unclassified entries is opt-in through the template rather than implied.

That is a design decision with two sides. For a project with disciplined labelling, nothing lands in the uncategorised bucket and the template stays clean. For a project where labelling is inconsistent, a section of unclassified entries is better than silently dropping work, and the template decides how loudly it appears.

The extension point for projects that do not label pull requests at all is the `label_extractor`, which the README describes as pulling Conventional Commit types straight out of titles. This is the same idea that the action uses for commit mode, applied to pull request titles, and it means a repository can be categorised by its commit message conventions without a labelling policy. The reference groups `label_extractor`, `transformers` and `tag_resolver` together as the regex configuration, so the extraction is a set of regular expressions rather than a fixed mapping.

Templates and placeholders decide the output text

The second half of the mechanism is the template, and the README describes the action as templated end to end with `template`, `pr_template` and `commit_template`, all using `#{{...}}` placeholders.

The three templates do different jobs. The main `template` is the document, the skeleton of the whole changelog with placeholders for the assembled sections. The `pr_template` is the per pull request line, and the `commit_template` is the per commit line, so each entry can be formatted independently of the document around it.

The reference lists some of the placeholders explicitly. `#{{CHANGELOG}}` is the assembled body, `#{{UNCATEGORIZED}}` is the section of entries that matched no category, and `#{{DAYS_SINCE}}` gives a time window, which is how a changelog can say what period it covers. The action outputs are listed as `changelog`, `categorized`, `contributors`, `commits` and more, so a workflow can also consume the contributor list or the categorised entries separately from the rendered text.

Custom placeholders are supported as well, and the reference has a section for deriving your own with regex, which is the escape hatch for a project that needs something in the notes that the standard set does not cover.

The practical caution is that a placeholder typo does not fail loudly. If you write a name the action does not recognise, the template still renders, and the difference shows up as missing or unchanged text in the release notes. Reading the placeholder reference once before you write the first template is cheaper than diffing a release body afterwards.

Commit mode for repositories without pull requests

The action is not only about pull requests, and the mode switch is what makes it usable on a repository that tags releases without ever opening a pull request.

With `mode: "COMMIT"` the action builds the same kind of changelog from the commits in the range, and the per entry formatting comes from `commit_template` instead of `pr_template`. The categories still apply, and the `label_extractor` with Conventional Commit types is the natural companion, since commit messages are where the type usually lives.

The repository's test layout shows how seriously both paths are treated. The package scripts include separate suites for GitHub, for Gitea, for offline runs and for the demo, plus a `test-main` target, and the aggregate `all` script runs the build, the formatter, the linter, the packaging step and the GitHub suite in sequence.

text
    "build": "tsc",
    "package": "ncc build --source-map --license licenses.txt",
    "test": "vitest --run --reporter=default --reporter=junit --outputFile=./junit.xml",

The packaging step is the one that matters for a GitHub Action specifically. A JavaScript action has to ship a bundled entry point rather than the sources, and `ncc build` produces that bundle with a source map and a licence file, which the repository commits in the `dist/` directory. That is why the top-level entries include `dist/` alongside `src/`.

The offline test suite is a good sign about the design. It suggests the action can be exercised without reaching the GitHub API, which is what makes the behaviour testable at all in a unit test rather than only in a live workflow.

Only the pull requests between the two tags are fetched

The performance claim in the README is specific and is the reason to use this action rather than assembling notes by hand on a busy repository. It says that only the pull requests between the two tags are fetched, and that hundreds of tags stay cheap.

That is a direct consequence of how the action is scoped. A changelog is a diff between two points in history, so the input set is bounded by the release cadence rather than by the size of the repository. A project with a thousand tags still asks the API for the pull requests closed since the previous release, which is a small set, and the tag resolution is pre-release aware, so a pre-release tag does not accidentally widen the range to the last stable release.

The feature list adds parallel releases support, which matters for a project that cuts several releases from the same branch in one window, and a support for any branch, which covers the common case of a maintained release line that is not the default branch.

The other item in the feature table is worth reading for its scope. The action supports any git project, and the configuration reference separates `configurationJson` from `configuration`, with a full default documented. The JSON form is the one shown in the quickstart, and it is the form that survives YAML quoting best, which is why a large configuration block is usually written as a JSON string.

Gitea, tokens and the permission you have to grant

The action is not tied to GitHub hosting, and the mechanism for that is the `platform` setting. With `platform: "gitea"` the action talks to a Gitea instance instead, and the test suite has a dedicated gitea target, which suggests it is a supported path rather than an untested edge.

Access is a separate question. The reference has a section on token permission covering the GitHub token, fine grained tokens and classic personal access tokens, and the workflow snippet passes `GITHUB_TOKEN` through the environment rather than in the inputs, which is the pattern GitHub documents for the built-in token.

This is the part of the setup that catches people out. The action needs to read pull requests, which means a token with more than the default read-only scope in a repository whose pull requests are private, and the reference is explicit about the difference between the fine grained and classic forms. A changelog job that fails on permissions is a confusing failure, since the symptom is an empty or partial set of entries rather than a clear error.

The README also carries an upgrade warning that is worth heeding. It appears twice, in the quickstart and in the setup section, and says that when upgrading from v4 to v5 you should read the warning in the release notes, especially if regexes are used. The regex configuration is what powers the label extractor, so a project relying on it is the one most likely to be affected by a change in how the action parses titles.

Version 6, a develop branch and an Apache licence

The maintenance picture is clear and the project is active.

The repository is not archived, and the last push was on 2026-10-18. The release history shows v6 on 2025-10-30, v6.4 on 2026-09-30 and v6.4.0 the same day, so the v6 line has been iterating for about a year with a recent patch release.

Two things are worth understanding about how versions are expressed. The manifest declares `"version": "v6.0.0"`, which is unusual, since the version field normally holds a plain semver string, and the tags use a `v` prefix as well. The action is referenced by tag, and the README's setup snippet uses a moving reference.

yml
- name: "Build Changelog"
  id: build_changelog
  uses: mikepenz/release-changelog-builder-action@{latest-release}

The `@{latest-release}` form resolves to the newest release at run time, which is convenient and also means a release can change under you without a commit on your side. Pinning to `@v6` is the more conservative choice for a repository that cares about reproducible releases, and the quickstart in the README does exactly that.

The default branch is `develop` rather than `main`, which is a convention that tells you the released code comes from a release branch and the development line is separate. The repository also carries a `SECURITY.md`, a `renovate.json` for dependency updates, a `.devcontainer/` for local development, and a `caches/` directory, and the README references a local testing section for building and debugging the action itself.

Editorial conclusion

release-changelog-builder-action fits a maintainer who tags releases and would rather the notes be generated from labelled pull requests than written by hand, and who wants the changelog to look the same on every release. Skip it if your project does not label its pull requests consistently, since the categories are the whole mechanism, or if you need curated prose for a major release. Verify first that your label set maps cleanly onto the categories you want, that the token has the permissions the action asks for, and that the placeholder names you use exist in the reference, since a typo in a template silently changes the output.

Frequently asked questions

How do I use release-changelog-builder-action in a GitHub Actions workflow?

Add a step that runs `mikepenz/release-changelog-builder-action@v6` with `GITHUB_TOKEN` in the environment, then pass the result on as the body of your release, for example `body: ${{ steps.build_changelog.outputs.changelog }}` with the action-gh-release step. The README's full sample shows a job triggered by tag pushes.

How do I categorise pull requests in the changelog?

Through the `categories` configuration, which maps labels onto section titles, so a `feature` label produces a Features section and `fix` or `bug` produces a Fixes section. A pull request can land in several categories or in none, and entries that match nothing are available under the `#{{UNCATEGORIZED}}` placeholder.

Can release-changelog-builder-action work without pull requests?

Yes. With `mode: "COMMIT"` the same changelog is built from commits instead, using `commit_template` for the per entry formatting. The `label_extractor` can pull Conventional Commit types out of titles so categories still apply.

Does release-changelog-builder-action support Gitea?

The README lists `platform: "gitea"` as a configuration option, and the repository's package scripts include a separate `test-gitea` suite, so the Gitea path is a supported one rather than an afterthought.

Which licence is release-changelog-builder-action released under?

Apache-2.0, with the text in the `LICENSE` file at the top level of the repository, and the README also carries an Apache 2.0 licence badge.

Official sources

  1. License: Apache-2.0
  2. mikepenz/release-changelog-builder-action on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mikepenz-release-changelog-builder-action.svg)](https://hysenlabs.com/projects/mikepenz-release-changelog-builder-action)

Was this useful?