Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

GitHub Actions is a powerful automation platform for continuous integration and continuous delivery (CI/CD) workflows. One common requirement when working with GitHub repositories is to programmatically create a Pull Request (PR) as part of an automated update, whether it’s refreshing a timestamp, syncing generated files, or applying a bulk change across branches.

In this tutorial, we’ll explore multiple ways to create a Pull Request using GitHub Actions, including the GitHub CLI, REST API, and the peter-evans/create-pull-request action.

2. Setup

Before we can create pull requests from a GitHub Actions workflow, we must ensure Git is configured correctly and authenticated using a token.

Let’s start by creating a composite action that handles this setup:

$ cat .github/actions/setup-repo/action.yml
name: 'Setup Git Repository'
description: 'Configure Git user and authentication for commits'
inputs:
  token:
    description: 'GitHub token'
    required: true
runs:
  using: 'composite'
  steps:
    - name: Configure Git user
      run: |
        git config --global user.name "github-actions[bot]"
        git config --global user.email "github-actions[bot]@users.noreply.github.com"
      shell: bash

    - name: Authenticate using token
      run: |
        git remote set-url origin https://x-access-token:${{ inputs.token }}@github.com/${{ github.repository }}
      shell: bash

We’ll reuse this action to ensure that Git operations, such as committing and pushing, work correctly.

Next, we’ll define another composite action that updates a file and optionally pushes the changes to a new branch:

$ cat .github/actions/update-and-push/action.yml
name: 'Update File and Push Branch'
description: 'Creates a new branch, modifies a file, commits, and pushes'
inputs:
  update_only:
    required: false
    default: false

outputs:
  branch:
    description: 'Branch name'
    value: ${{ steps.push.outputs.branch }}

runs:
  using: 'composite'
  steps:
    - name: Update file
      run: echo "Updated at $(date -u --rfc-3339=ns)" > gh-update.txt
      shell: bash

    - name: Commit and push
      id: push
      run: |
        branch="gh/update-$(date +%s)"
        if [ "${{ inputs.update_only }}" = "false" ]; then
          git checkout -b "$branch"
          git add gh-update.txt
          git commit -m "chore: update file"
          echo "Pushing branch: $branch"
          git push origin "$branch"
        else
          echo "Skipping push as update_only is true"
        fi

        echo "branch=$branch" >> "$GITHUB_OUTPUT"
      shell: bash

We’ll use this action to handle file updates and create a uniquely named branch to hold the changes. Further, we’ve added the update_only flag to the composite action to skip committing to the branch.

Finally, we must create a personal access token with the “repo” scope and add it as a repository secret named GH_TOKEN.

3. Using the GitHub CLI (gh)

The GitHub CLI provides a convenient way to interact with repositories from the command line. We can use it in a workflow to open a pull request after pushing changes to a branch.

First, let’s write the workflow in the create-pr-gh-cli.yml file under the .github/workflows directory:

$ cat .github/workflows/create-pr-gh-cli.yml
name: "Create Pull Request via GitHub CLI"

on:
  workflow_dispatch:

jobs:
  create-pr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - uses: ./.github/actions/setup-repo
        with:
          token: ${{ secrets.GH_TOKEN }}

      - uses: ./.github/actions/update-and-push

      - name: Open pull request using gh CLI
        run: |
          gh pr create \
            --title "chore: update via gh cli" \
            --body "This PR was created using the GitHub CLI." \
            --base main \
            --head $(git rev-parse --abbrev-ref HEAD)
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

We used the gh pr create command to open a pull request from the newly pushed branch. Further, we explicitly passed the GITHUB_TOKEN to allow the CLI to authenticate.

Next, let’s verify our workflow by running it manually:

Create PR using GitHub CLI

The workflow executed successfully.

Lastly, we should see a new pull request in the list of open PRs:

PR created using GitHub CLI

Excellent! It looks like we nailed it.

4. Using the GitHub REST API

If we prefer not to rely on external tools like the GitHub CLI, we can use the GitHub REST API directly to create a pull request from within a workflow.

Let’s start by writing the workflow in the .github/workflows/create-pr-rest.yml file:

$ cat .github/workflows/create-pr-rest.yml
name: "Create Pull Request via REST API"

on:
  workflow_dispatch:

jobs:
  create-pr:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - uses: ./.github/actions/setup-repo
        with:
          token: ${{ secrets.GH_TOKEN }}

      - uses: ./.github/actions/update-and-push

      - name: Open pull request via REST API
        run: |
          curl -X POST \
            -H "Authorization: token ${{ secrets.GH_TOKEN }}" \
            -H "Accept: application/vnd.github+json" \
            https://api.github.com/repos/${{ github.repository }}/pulls \
            -d "$(jq -n \
              --arg title 'chore: daily timestamp update' \
              --arg head "$(git rev-parse --abbrev-ref HEAD)" \
              --arg base 'main' \
              --arg body 'This PR updates the timestamp file automatically via GitHub Actions.' \
              '{title: $title, head: $head, base: $base, body: $body}')"

We used the jq command to construct a POST request to the /pulls endpoint with the necessary metadata to open a pull request from the new branch.

Now, we can trigger a workflow run and verify that a pull request is generated with the related changes:

Create PR using REST API

Perfect! It worked as expected.

5. Using peter-evans/create-pull-request Action

If we want a ready-to-use and well-maintained solution for creating pull requests, we can use the peter-evans/create-pull-request action. It handles the entire process, from committing changes to opening the pull request, without requiring custom logic.

Let’s define the workflow in the .github/workflows/create-pr-peter-evans.yml file:

$ cat .github/workflows/create-pr-peter-evans.yml
name: "Create Pull Request via peter-evans Action"

on:
  workflow_dispatch:

jobs:
  create-pr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          persist-credentials: false

      - uses: ./.github/actions/setup-repo
        with:
          token: ${{ secrets.GH_TOKEN }}

      - uses: ./.github/actions/update-and-push
        id: update
        with:
          update_only: true

      - name: Open pull request using peter-evans action
        uses: peter-evans/create-pull-request@v5
        with:
          branch: ${{ steps.update.outputs.branch }}
          base: main
          commit-message: "chore: update via create-pull-request action"
          title: "chore: update via create-pull-request action"
          body: "This PR was created using the peter-evans/create-pull-request action."
          token: ${{ secrets.GH_TOKEN }}

We used the peter-evans/create-pull-request action to automate the entire pull request creation process in a single step. Furthermore, we set the update_only flag to true for the update-and-push action because the create-pull-request action expects uncommitted changes in the branch.

Now, let’s run the workflow:

Create PR using composite action

Fantastic! We got it working.

6. Conclusion

In this tutorial, we explored different ways to create a pull request using GitHub Actions, including the GitHub CLI, the REST API, and the peter-evans/create-pull-request action.

Each method serves a different use case, and we can choose the one that best fits our workflow.