Two astronauts work outside a space station in orbit while a ship approaches, with Earth below.
Guide

Release Please: How Google's Release Automation Works, and How to Set It Up on GitHub

Felix Macx · · 14 min read

Release Please is Google’s answer to the release chore: it reads your conventional commits, opens a pull request that proposes the next version and the changelog entry, and when you merge that PR it tags the release and publishes it on GitHub. Here’s how it works, how to set it up in 2026, the configuration that matters, the v4 gotchas, and where it stops.


If you’ve used a Google Cloud client library in any language, you’ve been downstream of Release Please for years. It’s the tool the googleapis organisation built to cut thousands of releases across hundreds of repositories without anyone hand-editing a CHANGELOG.md, and it’s been open source and widely adopted outside Google since. Its own README carries the disclaimer “This is not an official Google product”, which is Google’s way of saying it’s maintained by the team that needs it rather than sold by the company.

The search results for it are the repository, the GitHub Marketplace listing, and a handful of personal blog posts, one of which is titled “Beware the Release Please v4 GitHub Action”. This post is the explanation that sits between the README and that warning: what the tool actually does, the setup that works today, and the mistakes people make on the way in.

It assumes you already write Conventional Commits. If you don’t, start there, because Release Please has nothing to work with otherwise.

What Release Please does

Release Please automates three things, and only three things:

  1. Changelog generation. It writes the next entry in your CHANGELOG.md (or NEWS.md for R) from the conventional commits since the last release.
  2. Version bumps. It updates the version in the files your language expects — package.json, pyproject.toml, Cargo.toml, pom.xml, version.rb, a plain version.txt, and so on — according to what the commits say.
  3. GitHub Releases. It tags the commit and creates a GitHub Release whose body is the changelog entry.

It does this through a release pull request. Rather than releasing every time something lands on main, Release Please opens a PR titled something like chore(main): release 1.3.0, containing the changelog edit and the version bumps, and keeps that PR up to date as more work merges. When you’re ready, you merge it. That merge is the release: the tag is created, the GitHub Release is published, and the next release PR starts accumulating from there.

The README is equally clear about what it won’t do: “It does not handle publication to package managers or handle complex branch management.” Publishing to npm, PyPI, crates.io, or a container registry is a step you add after it, gated on its outputs.

How the release PR works

The release PR is the whole idea, so it’s worth being precise about its life cycle.

Commits decide the version. Release Please parses every commit on the target branch since the last release tag. fix: means a patch bump, feat: means a minor bump, and a ! after the type (feat!:, fix!:, refactor!:) or a BREAKING CHANGE: footer means a major bump. Only certain commit types are “releasable units” — feat, fix, and deps, plus docs for the Java and Python strategies. A branch full of chore: and build: commits produces no release PR at all, which is the first thing to check when it seems to have gone quiet.

The PR carries a status label. autorelease: pending while it’s open. autorelease: tagged once it’s merged and the release has been tagged. autorelease: published is a convention the README suggests for your own publish tooling to apply; Release Please doesn’t add it. If a previous PR is stuck on pending, usually after a GitHub API hiccup, no new PR will open until you remove the label.

Merging it cuts the release. On merge, Release Please updates the changelog file and the language-specific version files (these are already in the PR, so the merge is the update), tags the merge commit with the version, and creates the GitHub Release from the tag. Both squash merges and merge commits work for the release PR itself.

Squash-merge your feature PRs. The README is emphatic about this, and for good reason. Release Please reads commit messages on main. If you merge feature branches with all their intermediate commits, your changelog fills with fix: typo and fix: tests entries for bugs that never existed on main. Squash merging gives you one conventional commit per PR, which is exactly the granularity a changelog wants. It also makes git bisect useful again.

One PR, several changes. If a squashed PR genuinely contains two user-facing changes, you can list them in the commit body as additional conventional-commit lines and Release Please will produce one changelog entry per line. The extra messages have to be at the bottom of the commit.

Fixing a bad message after the fact. Edit the merged PR’s body and add a block between BEGIN_COMMIT_OVERRIDE and END_COMMIT_OVERRIDE containing the commit message you wish you’d written. The next run uses the override. This only works with squash merges, because with a plain merge Release Please can’t tell which commits the override applies to.

Forcing a specific version. A commit on main whose body contains Release-As: 2.0.0 makes the next release PR propose exactly 2.0.0. The documented pattern is an empty commit:

git commit --allow-empty -m "chore: release 2.0.0" -m "Release-As: 2.0.0"

Setting it up with GitHub Actions

The GitHub Action is the recommended way to run it. The simplest possible workflow, straight from the action’s README, is a single file at .github/workflows/release-please.yml:

on:
  push:
    branches:
      - main

permissions:
  contents: write
  issues: write
  pull-requests: write

name: release-please

jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          token: $
          release-type: node

Three things in that file deserve attention.

release-type picks the strategy, which decides which version files get bumped and what the changelog looks like. The supported types are node, python, go, rust, java, maven, php, ruby, dart, elixir, helm, terraform-module, expo, ocaml, sfdx, bazel, krm-blueprint, R, and simple, which just maintains a version.txt and a CHANGELOG.md for anything else. Setting release-type here is the quick path; it’s also the only thing you can configure this way. Anything beyond it needs the manifest config in the next section.

permissions must grant contents: write, issues: write, and pull-requests: write. If the repository belongs to an organisation, you may also need to switch on “Allow GitHub Actions to create and approve pull requests” under Settings → Actions → General. Missing either produces a run that looks successful and a PR that never appears.

token is where most first installs go wrong, and the README now carries a warning box about it. The action defaults to the built-in GITHUB_TOKEN, and that works — until you expect anything else to react to what Release Please did. GitHub’s rule is that events triggered by GITHUB_TOKEN don’t start new workflow runs, to prevent recursion. So the release PR won’t get your CI checks, and the GitHub Release won’t trigger a workflow listening for release.created. If you need either (you almost certainly do), create a personal access token or a GitHub App token, store it as a secret, and pass it as token.

Publishing after the release

Because Release Please won’t publish for you, the pattern is to add publish steps to the same job and gate them on release_created. The README’s npm example:

steps:
  - uses: googleapis/release-please-action@v4
    id: release
    with:
      release-type: node
  - uses: actions/checkout@v4
    if: $
  - uses: actions/setup-node@v4
    with:
      registry-url: 'https://registry.npmjs.org'
    if: $
  - run: npm ci
    if: $
  - run: npm publish
    env:
      NODE_AUTH_TOKEN: $
    if: $

The same shape works for cargo publish, a Docker push, or gh release upload to attach build artifacts to the release. Useful outputs on the step include tag_name, version, major, minor, patch, sha, html_url, and body (the changelog entry for this version, handy for posting elsewhere). If you publish a GitHub Action yourself, the README also shows how to move the floating v2 and v2.8 tags using the major and minor outputs.

The manifest configuration

For anything more than one package with default settings, Release Please wants two files at the root of the repository. The action uses them automatically whenever you don’t set release-type in the workflow.

release-please-config.json holds the configuration. The minimum is one package:

{
  "packages": {
    ".": {
      "release-type": "node"
    }
  }
}

.release-please-manifest.json holds the current version of each package, keyed by path:

{
  ".": "1.2.4"
}

You can generate both with the CLI (npm i -g release-please, then release-please bootstrap --token=… --repo-url=owner/repo --release-type=node), which opens a PR adding them. Or write them by hand, which is usually quicker.

Two bootstrapping settings matter on a repository with history. The version in the manifest is what Release Please treats as “last released”, so set it to your actual current version or the first PR will propose 0.1.0 and a changelog containing every commit ever. And if you don’t want the first changelog entry to reach back to the beginning of time, add a top-level "bootstrap-sha" with the full SHA of the commit before the first one you want included. Once a release PR has merged, that setting is ignored.

Options worth knowing, all documented in the manifest releaser doc:

  • changelog-sections controls which commit types appear in the changelog and under what heading. The default shows features, bug fixes, performance improvements, and reverts, and hides docs, chore, refactor, test, build, and ci. If you want a “Documentation” section, add it here.
  • changelog-type switches between the default notes builder (groups by commit type, links to PRs and commits) and github, which uses GitHub’s own generate-release-notes API instead.
  • versioning-strategy replaces the default rule with always-bump-patch, always-bump-minor, always-bump-major, or service-pack. The patch one is the right choice for a maintenance branch.
  • prerelease and prerelease-type produce versions like 1.0.0-beta.1 while you work on a pre-release branch, and mark the GitHub Release as a pre-release.
  • draft creates GitHub Releases as drafts for a human to publish. Note the caveat in the docs: GitHub doesn’t create the tag for a draft release until it’s published, which can confuse the next run.
  • extra-files lists other files that contain the version string and should be bumped too, with dedicated updaters for JSON, XML, YAML, and TOML paths.
  • pull-request-title-pattern, pull-request-header, and pull-request-footer change the release PR’s text. The default title is chore: release ${branch} for grouped PRs.

Every one of these can also be set per package under packages, which is how the monorepo story works.

Monorepos

Monorepos are the reason the manifest format exists. You list each releasable package by path:

{
  "release-type": "node",
  "include-component-in-tag": true,
  "separate-pull-requests": true,
  "packages": {
    "packages/api": {},
    "packages/cli": {},
    "packages/docs-site": {
      "release-type": "simple"
    }
  }
}

By default Release Please opens one combined release PR for all packages that have changes. separate-pull-requests: true gives each package its own. include-component-in-tag prefixes tags with the package name (api-v1.3.0, or api@1.3.0 with "tag-separator": "@"), which is what keeps tags from colliding.

Plugins handle the part a path list can’t: dependencies between packages. node-workspace builds the dependency graph of your local packages and, when one is bumped, updates the version references in the packages that depend on it, patch-bumping them and adding changelog entries even if they had no commits of their own. cargo-workspace does the same for a Cargo workspace and updates the lockfile, and maven-workspace covers Maven. linked-versions forces a group of packages to share one version number, for the cases where you release them as a set.

In the workflow, outputs are prefixed by path, so you gate per-package publish steps on steps.release.outputs['packages/api--release_created'].

Upgrading from v3 to v4, and the gotchas

The v4 action reorganised configuration, and this is where the “beware” blog posts come from. Two changes account for most of the surprises.

The command input is gone. In v3 you told the action what to do: release-pr, github-release, manifest, or manifest-pr. In v4 you describe what to skip instead. github-release becomes skip-github-pull-request: true; release-pr becomes skip-github-release: true; the two manifest commands become “don’t set release-type”, because manifest mode is now the default whenever release-type is absent.

Almost every other input moved into the config file. path, package-name, changelog-path, bump-minor-pre-major, extra-files, monorepo-tags (now include-component-in-tag), pull-request-title-pattern, versioning-strategy, and a few dozen more are no longer action inputs. They live in release-please-config.json, either at the root or per package. The action’s README has the full mapping table. If your v3 workflow had a long with: block, expect to move nearly all of it.

Beyond the migration, these are the mistakes worth avoiding on any version:

  • Gating deploys on the wrong output. The step exposes both release_created (the root package released) and releases_created (any release happened). A widely shared April 2024 post reported releases_created evaluating truthy after the v4 upgrade even when nothing had been released, tracked as issue #912 on the action, and the author had production deploys fire because of it. Whatever the current behaviour, the robust pattern is to gate on release_created for a single package or <path>--release_created for a monorepo package, never on the aggregate.
  • Using GITHUB_TOKEN and wondering why CI never runs on the release PR. Covered above; it’s the single most common support question.
  • Merge commits on feature PRs. Squash, or accept a noisy changelog and a non-functional commit override.
  • A stuck autorelease: pending label. Remove it and re-run.
  • Expecting chore: commits to trigger a release. They don’t. Use fix: or feat: for anything a user should hear about, and keep chore: for things they shouldn’t.
  • Expecting readable release notes. The changelog Release Please writes is accurate and traceable, and it reads like commit messages, because it is commit messages. More on that below.

Release Please vs semantic-release

These two get compared constantly, and the difference is simple once you see it. Both parse conventional commits and both compute a semantic version. They disagree about whether a human should be in the loop.

  Release Please semantic-release
Trigger Opens a release PR; release happens when you merge it Runs on push to the release branch; releases immediately
Human gate Yes, by design No, by design
CHANGELOG.md Written by default Only with the @semantic-release/changelog and @semantic-release/git plugins
Publishing to registries Not included; you add a gated step Core feature, via plugins for npm and many others
Monorepos Manifest config plus workspace plugins Community plugins such as semantic-release-monorepo
Languages Nineteen built-in strategies Node-centric core, plugins for the rest
Fixing a bad entry before release Edit the PR, or use the commit override Amend history or publish a follow-up

Pick Release Please when you want to see the version and the changelog before they exist, when releases need a human decision, or when you ship in a language other than JavaScript. Pick semantic-release when the branch is the release and you want push-to-publish with no ceremony. Teams that want the latter but are nervous about it sometimes run Release Please with a very short merge habit, which is a reasonable middle.

Both tools sit in the same family as git-cliff and conventional-changelog, which only write the file and leave tagging and releasing to you. The broader comparison, including the AI-scripted and managed options, is in how to automate release notes from GitHub commits.

What the output looks like, and who it’s for

A Release Please changelog entry for a Node project looks like this:

## [1.3.0](https://github.com/acme/widget/compare/v1.2.4...v1.3.0) (2026-10-16)

### Features

* **reports:** add CSV export to reports table ([#482](https://github.com/acme/widget/issues/482)) ([a1b2c3d](https://github.com/acme/widget/commit/a1b2c3d))

### Bug Fixes

* **reports:** handle empty filter state on export ([#488](https://github.com/acme/widget/issues/488)) ([e4f5a6b](https://github.com/acme/widget/commit/e4f5a6b))

That’s the same shape as Vue’s CHANGELOG.md, which the changelog examples post takes apart: compare link in the heading, sections by commit type, scope prefix, issue and commit links on every line. It is excellent for the people it’s for. A contributor can click from a line to the diff. A packager can see what moved between two tags. Nothing in it is ambiguous.

It is not a customer changelog, and it was never meant to be one. “reports: handle empty filter state on export” tells a developer exactly what happened and tells a customer nothing about whether their download works now. The changelog vs release notes distinction is this exact gap, and the product changelog post covers what the customer-facing version should contain instead.

So the honest picture of Release Please is one half of a two-artifact setup. It owns the repository side: version, tag, CHANGELOG.md, GitHub Release, all generated from commits with a human merge in between. The customer-facing side starts from the same merges and needs a sentence a user can act on.

Where ReleasePad fits

ReleasePad is for that second half, and only that half. It reads the same commits and pull requests Release Please does, drafts an entry in customer language, and waits for you to edit and publish it to a hosted changelog page, an in-app widget, and Markdown, RSS, and JSON feeds. It does not bump versions, tag commits, or write CHANGELOG.md, so it doesn’t replace Release Please and doesn’t compete with it. The usual arrangement is Release Please managing the release PR and the file in the repo, and the customer entry drafted from the merge that closed it. The GitHub integration page shows how the drafting step works.

If your readers are all contributors, you don’t need the second half. Release Please plus a well-kept CHANGELOG.md is complete, and the Keep a Changelog post covers how to make that file good.

A setup checklist

For a single-package repository on GitHub, in order:

  1. Make sure PRs are squash-merged and titles follow Conventional Commits. Add a PR title linter if you need to.
  2. Create release-please-config.json with your release-type and .release-please-manifest.json with your current version.
  3. Create a personal access token or GitHub App token with contents and pull-requests write access, and store it as a repository secret.
  4. Add .github/workflows/release-please.yml with the permissions block, the token, and no release-type (so the config file is used).
  5. If the repo is in an organisation, enable “Allow GitHub Actions to create and approve pull requests”.
  6. Merge a fix: or feat: commit and watch for the release PR. If it doesn’t appear, check the releasable-units rule, the autorelease: pending label, and the workflow run log, in that order.
  7. Add publish steps gated on steps.release.outputs.release_created.
  8. Decide what customers hear about it, separately.

Setup details are from the release-please and release-please-action READMEs and the manifest and customization docs, read on 16 October 2026. The action moves; if a label or input here doesn’t match what you see, the README wins.

Further reading

Frequently Asked Questions

What is Release Please?

Release Please is an open-source tool from Google's googleapis organisation that automates three chores for a GitHub repository: generating the CHANGELOG.md, bumping version numbers in files like package.json, and creating GitHub Releases. It reads your conventional commit messages, works out the next semantic version, and opens a release pull request that stays up to date as more work merges. Merging that PR is what cuts the release. It runs as a GitHub Action or as a CLI, and it does not publish to package registries.

What is the difference between Release Please and semantic-release?

Both read conventional commits to decide the next version, but they differ on when the release happens. semantic-release runs in CI on every push to the release branch, computes the version, publishes to npm or another registry, and tags, all without a human step. Release Please opens a release pull request instead and does nothing until someone merges it. Release Please writes CHANGELOG.md by default and leaves publishing to you; semantic-release publishes by default and only writes a changelog file if you add its changelog and git plugins.

Does Release Please publish to npm?

No. Release Please tags the commit and creates the GitHub Release, then exposes outputs such as release_created, tag_name, and version. You add your own publish step to the same workflow and gate it on release_created, so npm publish, cargo publish, or a Docker push only runs when a release PR was actually merged. The action's README includes a worked npm example.

Why isn't Release Please creating a release PR?

Three usual causes. First, nothing releasable has merged: only feat, fix, and deps commits (plus docs for Java and Python) count, so a branch of chore and build commits produces no PR. Second, an older pull request still carries the autorelease: pending label, which blocks a new one; remove the label if that release is done. Third, the run failed or was skipped, so re-run the workflow. Also check that the workflow has contents, issues, and pull-requests write permissions and that your organisation allows Actions to create pull requests.

How does Release Please handle a monorepo?

Through the manifest configuration. You list each package path under packages in release-please-config.json and track each version in .release-please-manifest.json. By default all packages share one release PR; set separate-pull-requests to true for one PR per package. include-component-in-tag prefixes tags with the package name, and plugins like node-workspace, cargo-workspace, and linked-versions keep interdependent packages' versions in step. In the workflow, outputs are prefixed by path, for example packages/api--release_created.

Can I override the version Release Please proposes?

Yes. Add a Release-As footer to a commit on the default branch, for example an empty commit with the message chore: release 2.0.0 and the body Release-As: 2.0.0, and Release Please opens a PR for exactly that version. For a first run, set the current version directly in .release-please-manifest.json and, if needed, a bootstrap-sha in the config so it only collects commits from a chosen point. Versioning strategies such as always-bump-patch change the rule for a whole branch.

release-please github-actions conventional-commits changelog versioning automation

Ready to put this into practice?

Your changelog shouldn't be an afterthought.

ReleasePad makes it easy to publish great release notes — from a public changelog page to an in-app widget, GitHub integration, and analytics. Free to get started.

Get started — it's free
Try me now!