release-please-action: release PRs from Conventional Commits
release-please-action reads your Conventional Commit history and keeps one release pull request open that bumps the version and writes the changelog; merging that pull request tags the release and creates the GitHub release. At googleapis/release-please-action@v5, its release_created output is how a later job publishes only when a release actually happened.
Most teams that adopt release-please want the same three things: no hand-edited version numbers, a changelog nobody has to write, and a publish step that runs exactly once per release. Each of them depends on something outside the action: commit messages that follow the convention, and a workflow that reads release_created before it publishes.
Two things trip people up after that. The action moved to googleapis/release-please-action, and the old google-github-actions/release-please-action repository is archived, so older snippets point at a repository that no longer changes. And with the default token, CI on the release PR waits for approval or does not run at all.

How the release PR loop works
Every push to the release branch runs the action, and each run does two things in order. First it checks whether a release PR was just merged and, if so, tags it and creates the GitHub release. Then it reads the commits since the last release and opens or updates the release PR. The version bump comes from the commit types: fix: is a patch, feat: is a minor, and a ! after the type, as in feat!:, is a major.
The workflow needs contents: write, issues: write and pull-requests: write. In an organization you may also need to switch on Allow GitHub Actions to create and approve pull requests under Settings, Actions, General; without it the PR step is refused.
name: release-please
on:
push:
branches: [main]
permissions:
contents: write
issues: write
pull-requests: write
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
release-type: nodeThe few settings that live in the workflow
Most configuration lives in the config file, not here. These are all the inputs the action accepts.
| Input | Default | What it does |
|---|---|---|
token | github.token | Token for creating release PRs, tags and releases |
release-type | none | Strategy such as node, python, go, rust, simple; unset means use the manifest config |
path | repository root | Release a package in a subdirectory |
target-branch | detected | Branch to open the release PR against |
config-file | release-please-config.json | Path to the config file |
manifest-file | .release-please-manifest.json | Path to the versions manifest |
include-component-in-tag | false | Prefix tags and branches with the component name |
skip-github-release | false | Only open PRs, do not tag |
skip-github-pull-request | false | Only tag, do not open PRs |
skip-labeling | false | Do not label the PR |
versioning-strategy | default | How the next version is computed |
release-as | none | Force the next version |
fork | false | Open the PR from a fork; needs a user token, not GITHUB_TOKEN |
repo-url, github-api-url, github-graphql-url, changelog-host, proxy-server | this repository and server | For GitHub Enterprise and proxies |
Config and manifest files
Since v4, nearly every option lives in two files at the root of the default branch. release-please-config.json holds the settings per package, and .release-please-manifest.json records the last released version of each package path. When release-type is not set, the action uses these files; when it is set, the action builds a one-package config on the fly and the files are ignored.
For an existing project, seed the manifest with the current version so release-please does not propose its default starting version, and set bootstrap-sha so the first changelog does not sweep in the whole history. The manifest docs say bootstrap-sha is ignored once the first release PR has merged, so you can delete it afterwards.
{
"bootstrap-sha": "<full sha of the commit before the first one to include>",
"packages": {
".": {
"release-type": "node",
"changelog-path": "CHANGELOG.md"
}
}
}Monorepos: one PR, one tag per package
List each package path under packages and its current version in the manifest. By default all packages share one combined release PR; separate-pull-requests: true gives each its own. Set include-component-in-tag so tags read api-v1.4.0 rather than colliding on v1.4.0.
Outputs for a package outside the root are prefixed with its path, so the root release_created becomes packages/api--release_created. A path containing / is not a valid identifier in an expression, so read it with bracket syntax: steps.release.outputs['packages/api--release_created']. The paths_released output is a JSON array of every path released in the run, which the action's source says exists to feed a matrix.
// release-please-config.json
{
"include-component-in-tag": true,
"packages": {
"packages/api": { "release-type": "node" },
"packages/cli": { "release-type": "node" }
}
}
// .release-please-manifest.json
{ "packages/api": "1.4.0", "packages/cli": "0.9.2" }Chaining a publish job: compare with 'true'
The outputs are how the publish job knows a release happened: release_created, tag_name, version, major, minor, patch, sha, html_url, upload_url and body for the root package, and releases_created, paths_released, prs_created, pr and prs for the run as a whole.
One of them behaves differently from the rest. In src/index.ts at v5.0.0, release_created is only set when a release was created, so it is empty otherwise, but releases_created is always set, to the string true or false. GitHub's expression rules treat any non-empty string as truthy, so if: ${{ steps.release.outputs.releases_created }} runs every time. Issue #912 on the action reports exactly that. Compare with == 'true' for every output and the question never comes up.
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
release-type: node
publish:
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release-please.outputs.tag_name }}
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false
- run: npm ci
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}Why CI does not run on the release PR, and the token fix
With the default GITHUB_TOKEN, the release PR, its tag and its GitHub release are all created by that token, and GitHub does not start new workflow runs from events that token causes. The release-please README still describes it that way. GitHub's own documentation, read 2026-11-12, now carves out one exception on github.com: a pull request opened, synchronized or reopened by GITHUB_TOKEN creates its pull_request runs in an approval-required state, and someone with write access has to click Approve workflows to run. The tag push and the release event still start nothing.
Either way, the fix is the one both documents give: pass a GitHub App installation token or a PAT as token. Then the release PR runs CI like any other pull request, and a separate workflow listening for the tag or the release fires. An App token is the better choice, because it is short-lived and not tied to a person.
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
id: app-token
with:
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
token: ${{ steps.app-token.outputs.token }}
release-type: nodev4 moved configuration out of the workflow
v4 is where configuration moved out of the workflow; v5 changed only the runtime.
| Release | Change |
|---|---|
| v4.0.0 | TypeScript rewrite, Node 20; command removed in favor of release-type, skip-github-release and skip-github-pull-request; most options moved to the manifest config |
| v4.2.0 | skip-labeling input |
| v4.4.0 | versioning-strategy and release-as inputs |
| v5.0.0 | Node 24 runtime, release-please library 17.6.0 |
Key takeaways
fix:is a patch,feat:a minor,!a major; merging the release PR is what tags the release.- Without
release-type, everything comes fromrelease-please-config.jsonand the manifest. releases_createdis always the stringtrueorfalse; compare with== 'true'.- Use a GitHub App token so CI and release workflows run on what release-please creates.
Frequently asked questions
Why does outputs.releases_created behave differently compared to v3?
releases_created, to the string true or false, and a non-empty string is truthy in a GitHub expression. A bare if: ${{ steps.release.outputs.releases_created }} therefore always passes. Write == 'true' instead; it also works for release_created, which is only set when a release exists.Is there an example of release-please with a monorepo?
release-type, list each package path under packages in release-please-config.json, and record each current version in .release-please-manifest.json. Turn on include-component-in-tag so tags do not collide, and read per-package outputs as outputs['<path>--release_created'].How do I use release-please-action to perform a hotfix?
1.x as well as main, and pass target-branch: ${{ github.ref_name }}, so a fix: merged into 1.x gets its own release PR against that branch and its own patch tag.How do I attach build artifacts to the release release-please creates?
release_created == 'true' and push files to the tag it outputs. The README uses gh release upload with the tag_name output; softprops/action-gh-release with tag_name set works too, because it updates the existing release instead of creating one.Related guides
References
- googleapis/release-please-action action.yml at v5.0.0 (read 2026-11-12)
- release-please-action README and src/index.ts at v5.0.0
- release-please: manifest driven releases (config and manifest files)
- GitHub Docs: triggering a workflow from a workflow (GITHUB_TOKEN events)
- Issue #912: outputs.releases_created has different behavior compared to v3
- GitHub Actions documentation