# release-please-action: release PRs from Conventional Commits

> release-please-action v5 explained: release PRs from Conventional Commits, config and manifest files, monorepos, outputs and the token CI needs.

Source: https://latchkey.dev/learn/actions/googleapis-release-please-action  
Updated: 2026-11-12

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.

```.github/workflows/release-please.yml
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: node
```

> v5.0.0 is a lightweight tag; the SHA is the commit it names, from `git ls-remote https://github.com/googleapis/release-please-action refs/tags/v5.0.0`, read 2026-11-12.

## The 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 |

> Defaults for `config-file` and `manifest-file` are from the README; the rest from action.yml at tag v5.0.0, read 2026-11-12.

## 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.

```release-please-config.json
{
  "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
// 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.

```.github/workflows/release-please.yml
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.

```.github/workflows/release-please.yml
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: node
```

## v4 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 |

> From CHANGELOG.md at tag v5.0.0, read 2026-11-12. The README's examples still show `@v4`.

## FAQ

### Why does outputs.releases_created behave differently compared to v3?

Since v4 the action always sets `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?

Yes: omit `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?

Release the older line from its own branch. The README's pattern is to trigger the workflow on pushes to a maintenance branch such as `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?

Gate an upload step on `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](/learn/actions/softprops-action-gh-release) with `tag_name` set works too, because it updates the existing release instead of creating one.

## References

- [googleapis/release-please-action action.yml at v5.0.0 (read 2026-11-12)](https://github.com/googleapis/release-please-action/blob/v5.0.0/action.yml)
- [release-please-action README and src/index.ts at v5.0.0](https://github.com/googleapis/release-please-action/blob/v5.0.0/README.md)
- [release-please: manifest driven releases (config and manifest files)](https://github.com/googleapis/release-please/blob/main/docs/manifest-releaser.md)
- [GitHub Docs: triggering a workflow from a workflow (GITHUB_TOKEN events)](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow)
- [Issue #912: outputs.releases_created has different behavior compared to v3](https://github.com/googleapis/release-please-action/issues/912)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
