Simplifying GitHub API Queries with the actions/github-script Action

I faced the challenge of making complex queries to the GitHub API. Initially, I started with a bash script, as it’s often the simplest solution for many of my use cases. However, after reviewing the documentation, I discovered an official action designed specifically for interacting with the GitHub API. Upon further exploration, I realized that using this action would be much easier than continuing with bash or Python.

The actions/github-script provides a straightforward way to query the GitHub API. It comes with convenient features, such as the ability to get and set outputs, as well as seamless access to GitHub context details.

Its usage is intuitive. The input “script” accepts “the body of an asynchronous function call.” By default, the action leverages the token generated for the workflow run. If this default token lacks sufficient permissions, you can supply a custom token. As a best practice, it’s recommended to use the “permission” instruction to explicitly request permissions for the default token, ensuring that workflows fail early if permissions are insufficient. So lets make a simple call using the default token:

name: Simple GitHub API call
permissions: read-all
on:
  workflow_dispatch:
jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            const emojis = await github.rest.emojis.get();
            console.log(emojis.data);

To determine the specific API call you need, you can refer to the documentation for octokit. For instance, searching for “emojis” revealed the call to retrieve the complete list of emojis: octokit.rest.emojis.get();.

As mentioned earlier, there is an argument named “github” that represents a “pre-authenticated octokit/rest.js client with pagination plugins.” To make the action work, you need to replace “octokit” with “github”. This approach provides a straightforward way to identify and implement the required API call.

If a specific octokit endpoint isn’t available (or if you prefer a custom approach), you can use github.requestmethod to craft a custom API call. For example, the previous case for fetching emojis can be written as:

name: Simple GitHub API call
permissions: read-all
on:
  workflow_dispatch:
jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            const emojis = await github.request('GET /emojis');
            console.log(emojis.data);

Keep in mind that, according to the documentation, when a GitHub API call returns too many results, it provides only a subset. This process is known as pagination.

To handle this automatically and retrieve all results at once, you can use the “github.paginate” method, similar to how “github.request” is used. However, be cautious, as some responses can be massive. To avoid potential issues, consider limiting the results to what you actually need. Most API calls provide options to limit the number of results retrieved.

Retrieving All The Workflow Runs in the Current Repository

To retrieve detailed information about all workflow runs in the current repository—which can be extensive and may require the use of the ‘paginate’ method—you can use the following code:

name: Get workflow runs details from current repository
permissions: read-all
on:
  workflow_dispatch:
jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        with:
          script: |
            const owner = context.repo.owner;
            const repo = context.repo.repo;

            const workflowRuns = await github.paginate('GET /repos/{owner}/{repo}/actions/runs', {
              owner,
              repo,
              per_page: 100
            });

            console.log(workflowRuns);

You can also fetch workflow runs from other repositories by modifying the relevant variables (owner and repo) and providing a token with sufficient permissions using the github-token input in the action.

All sorts of data manipulation are possible using this action and JavaScript code, so further steps to trim or clean the data obtained might not be necessary as everything can be handled within the script block of the action. When generating outputs as part of an action, the “core” argument comes off handy to set outputs using the following syntax:

core.setOutput('outputKey', 'outputVal');

Inputs can be accessed in a similar way. However, during testing, I observed that inputs might occasionally return empty values, even when properly configured. A straightforward solution is to use expressions within the script block, such as the standard ${{ inputs.<input_name> }}. Additionally, different log levels can be configured using the “core” module.

Using a separate file for the script

Another useful feature is the ability to use an external JavaScript file instead of writing the code directly in the script block. To do this, I created a file named get-wf.js in the .github/workflows/scripts directory of the repository:

└── workflows
    ├── get-wf.yml
    ├── scripts
    │   └── get-wf.js
    └── simple-call.yml

The .js file has the following content:

module.exports = async ({github, context}) => {
    const emojis = await github.request('GET /emojis')
    return emojis.data
}

The github and contexts arguments will be passed to the script because they are not directly accessible when the code is placed in an external file instead of the script block. The workflow to print the results of that script looks like this:

name: Get workflow runs details from current repository
permissions: read-all
on:
  workflow_dispatch:
jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/github-script@v7
        with:
          script: |
            const script = require('${{ github.workspace }}/.github/workflows/scripts/get-wf.js');
            console.log(await script({github, context}));
| Theme: UPortfolio