# peaceiris/actions-gh-pages: pushing built static sites to a gh-pages branch from a workflow

> A TypeScript GitHub Action that takes a built directory such as ./public and publishes it to a branch, with three token types, sixteen option entries, and a few platform gaps worth knowing before you depend on it.

**peaceiris/actions-gh-pages** — GitHub Actions for GitHub Pages 🚀 Deploy static files and publish your site easily. Static-Site-Generators-friendly.

- Repository: https://github.com/peaceiris/actions-gh-pages
- Website: https://github.com/marketplace/actions/github-pages-action
- Stars: 5,370 · Forks: 448
- Language: TypeScript
- License: MIT
- Published: 2026-09-22 · Updated: 2026-09-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/peaceiris-actions-gh-pages

## The first deployment with GITHUB_TOKEN still needs a manual branch selection

The default path is the shortest one. A job that has a built directory adds one step, and the action pushes that directory to the remote gh-pages branch.

```yaml
- name: Deploy
  uses: peaceiris/actions-gh-pages@v4
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    publish_dir: ./public
```

The README stresses that GITHUB_TOKEN is not a personal access token: the runner creates it automatically, so nothing has to be stored in repository secrets.

The catch is stated in the same section. GITHUB_TOKEN works for deploying to GitHub Pages but has some limitations, and for the first deployment you must open the repository settings tab and select the gh-pages branch or another branch. Until someone does that, the workflow can succeed while the site stays unpublished. If you automate repository creation, this click is the step you cannot script away with the default token.

## Three tokens, three protocols, and what each costs you to set up

The action accepts github_token, deploy_key, and personal_token. The first two cells that matter are the protocol and the setup effort. github_token uses HTTPS and needs no setup. deploy_key uses SSH and needs setup. personal_token uses HTTPS and also needs setup. All three are marked as working for both private and public repositories.

The practical difference is where your effort goes. With deploy_key you generate an SSH key pair, which the README covers under its Create SSH Deploy Key tip, and you store the private half where the workflow can read it. With personal_token you create and rotate a personal access token yourself. Only github_token comes for free with the runner, which is why it is the default in the example and also why it carries the first-deployment restriction above.

## deploy_key on a Windows runner is marked WIP

The platform table covers ubuntu-22.04, ubuntu-20.04, ubuntu-latest, macos-latest, and windows-latest. Every combination of runner and token is ticked except one: windows-latest with deploy_key shows a footnote instead of a tick, and the footnote reads WIP with a pointer to Issue #87.

The consequence is concrete. If your build has to run on Windows, for example because the static site generator step only works there, you cannot assume SSH deploy keys will work in that job. Use github_token or personal_token on windows-latest, or split the work so the build runs on Windows and the deployment step runs in a separate Ubuntu job. Follow Issue #87 before choosing deploy_key for a Windows pipeline.

## GitHub Enterprise Server works above 2.22.6, but the runner token may not be allowed to push

GitHub Enterprise Server is supported above 2.22.6. That version floor is the only compatibility promise given, so an older GHES install is outside what the action claims.

The second warning matters more in daily use. The GITHUB_TOKEN created by the runner might not inherently have push or publish privileges on GHES. The suggested remedy is to create or request a technical user with write permissions to the target repository. In practice that means the zero-setup story from github.com does not carry over: on GHES you may need an administrator to provision an account, and you would then fall back to personal_token or deploy_key instead of the default.

## The workflow job needs contents: write and a per-ref concurrency group

The Hugo example in the Getting started section shows two job-level settings that are easy to miss when you adapt it. The first is `permissions` with `contents: write`, since the job has to push a commit to the publishing branch. The second is a `concurrency` group built from `${{ github.workflow }}-${{ github.ref }}`, which keeps overlapping runs on the same ref from racing each other to the same branch.

The checkout step in that example sets `submodules: true` to fetch Hugo themes and `fetch-depth: 0` to fetch all history for .GitInfo and .Lastmod. Those two lines belong to Hugo, not to the deploy action. The example also triggers on both a push to main and on pull_request, so a pull request run reaches the deploy step unless you add your own condition. Review that before you let forks or unreviewed branches run it.

## Sixteen option entries that decide what happens to the target branch

The Options section has sixteen entries: github_token, deploy_key, personal_token, publish_branch, publish_dir, destination_dir, exclude_assets, cname, enable_jekyll, allow_empty_commit, keep_files, external_repository, force_orphan, Git username and email, a custom commit message, and Git tag creation.

A few of these change what the deployment does to content that already exists. destination_dir deploys into a subdirectory, keep_files is about retaining existing files, exclude_assets filters what gets published, and force_orphan has its own entry. external_repository sends the output to a different repository than the one running the workflow. Each has its own README section, so confirm the exact semantics there before you point any of them at a branch that holds content you cannot regenerate. A wrong combination can change or remove files on a branch that serves a live site.

## A TypeScript action bundled with esbuild that now expects Node 24

The package is named actions-github-pages at version 4.1.0, with main set to lib/index.js. The build script runs `esbuild src/index.ts --bundle --platform=node --target=node24 --format=cjs --outfile=lib/index.js --minify`, and the engines field asks for node >=24.0.0 and npm >=11.0.0. Runtime dependencies are @actions/core, @actions/exec, @actions/github, @actions/glob, @actions/io, and shelljs.

For a user of the action, none of this changes the workflow syntax. For anyone who forks it or sends a patch, the Node 24 and npm 11 floor means an older local toolchain will not match the project. The test script runs jest with --experimental-vm-modules, and the repository carries a test_projects directory next to __tests__, so changes can be exercised against sample projects.

## The README sends you to GitHub's official Pages action first

A note at the top of the README tells readers to look at the official GitHub Pages action first and links two GitHub posts: one titled GitHub Pages now uses Actions by default, and one about custom GitHub Actions workflows in beta. The maintainer is directing new users toward the platform's own mechanism before this one.

That is useful context for choosing. This action pushes to a branch such as gh-pages, which is why the first deployment needs a branch selected in settings. If you are starting a new site and have no reason to manage a publishing branch, read GitHub's posts and the official action before adding this one. If you already publish from a branch, or you need external_repository, deploy_key, or a custom commit message, this action is built around those.

On activity, the latest push was 2026-10-31. Releases run v3.9.3 on 2023-03-30, v4.0.0 on 2024-04-08, and v4.1.0 on 2026-05-12, so the gap between the last two is more than two years.

## Conclusion

Use it when you want a build step in your own workflow to push static output to a branch and you are fine with token and branch settings you manage yourself. Check GitHub's official Pages action first for a new site, and verify the Windows deploy_key gap and the GHES token limits before relying on either.

## FAQ

### What do GH pages do when deployed with peaceiris/actions-gh-pages?

The action deploys your static files to GitHub Pages. In the basic example it publishes the ./public directory to the remote gh-pages branch.

### How to view GH pages after running peaceiris/actions-gh-pages?

The README shows an Actions log overview and a GitHub Pages log for a run. For the first deployment with GITHUB_TOKEN, you also need to select the gh-pages branch or another branch in the repository settings tab.

### How do I deploy Pages from a GitHub Action with peaceiris/actions-gh-pages?

Add a step that uses peaceiris/actions-gh-pages@v4 with github_token set to secrets.GITHUB_TOKEN and publish_dir set to your build output, and give the job contents: write permission.

## Sources

- [License: MIT](https://github.com/peaceiris/actions-gh-pages/blob/main/LICENSE)
- [peaceiris/actions-gh-pages on GitHub](https://github.com/peaceiris/actions-gh-pages)
- [Project website](https://github.com/marketplace/actions/github-pages-action)
- [README](https://github.com/peaceiris/actions-gh-pages/blob/main/README.md)
- [Releases](https://github.com/peaceiris/actions-gh-pages/releases)

---

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