Home GADGETS Exploring GitHub CLI: How to interact with GitHub’s GraphQL API endpoint

Exploring GitHub CLI: How to interact with GitHub’s GraphQL API endpoint

Exploring GitHub CLI: How to interact with GitHub’s GraphQL API endpoint

You might have heard of the GitHub CLI and all of the awesome things you can do with it. However, one of its hidden superpowers is the ability to execute complex queries and mutations through GitHub’s GraphQL API. This post will walk you through what GitHub’s GraphQL API endpoint is and how to query it with the GitHub CLI.

What is GraphQL?

Let’s start with the basics: GraphQL is a query language for APIs and a runtime for executing those queries against your data. Unlike traditional REST APIs that provide fixed data structures from predefined endpoints, GraphQL allows clients to request exactly the data they need in a single request. This single-request approach reduces network overhead, speeds up application performance, and simplifies client-side logic by eliminating the need to reconcile multiple API responses—a capability that has been openly available since the specification was open sourced in 2015.

GraphQL operations come in two primary types: queries and mutations. Queries are read-only operations that retrieve data without making any changes—similar to GET requests in REST. Mutationson the other hand, are used to modify server-side data (create, update, or delete)—comparable to POST, PATCH, PUT, and DELETE in REST APIs. This clear separation between reading and writing operations makes GraphQL interactions predictable while maintaining the flexibility to precisely specify what data should be returned after a change is made.

How is GraphQL used at GitHub?

GitHub implemented GraphQL in 2016 to address limitations of RESTful APIs. This adoption has significantly enhanced the developer experience when working with GitHub data. With the GraphQL endpoint, you can retrieve a repository’s issues, its labels, assignees, and comments with a single GraphQL query. Using our REST APIs, this would have otherwise taken several sets of nested calls.

Some GitHub data and operations are only accessible through the GraphQL API (such as discussions, projects, and some enterprise settings), others exclusively through REST APIs (such as querying actions workflows, runners, or logs), and some using either endpoint (such as repositories, issues, pull requests, and user information). GitHub’s GraphQL endpoint is accessible at api.github.com/graphql and you can explore the full schema in our GraphQL documentation or through the interactive GraphQL Explorer.

A key consideration when choosing between the REST API and the GraphQL API is how the rate limits are calculated. As a quick summary for how this is implemented:

  • REST API: Limited by number of requests (typically 5,000 requests per hour for authenticated users and up to 15,000 for GitHub Apps installed in an Enterprise)
  • GraphQL API: Limited by “points” (typically 5,000 points per hour for authenticated users but can go up to 10,000-12,500 points per hour for GitHub Apps)

Each GraphQL query costs at least one point, but the cost increases based on the complexity of your query (number of nodes requested, connections traversed, etc.). The GraphQL API provides a rateLimit field you can include in your queries to check your current limit status.

For scenarios where you need to fetch related data that would otherwise require multiple REST calls, GraphQL is often more rate limit friendly because:

  • One complex GraphQL query might cost 5-10 points but replace 5-10 separate REST API calls.
  • You avoid “over-fetching” data you don’t need, which indirectly helps with rate limits.
  • The GraphQL API allows for more granular field selection, potentially reducing the complexity and point cost.

However, poorly optimized GraphQL queries that request large amounts of nested data could potentially use up your rate limit faster than equivalent REST requests—and quickly run into secondary rate limit issues.

A quick rule of thumb on deciding between which to use:

  • For querying relational objects, such as GitHub Projects and their issues, GraphQL is often more effective, especially if it’s a discrete number of items.
  • For bulk data of one type or single data points, such as pulling in a list of repository names in an organization, the REST API is often preferred.

Sometimes there isn’t a right or wrong answer; so as long as the object exists, try one out!

Why use GitHub CLI for GraphQL?

While many developers start with GitHub’s GraphQL Explorer on the web, curlor other API querying tools, there’s a more streamlined approach: using built-in GraphQL support in the GitHub CLI. Before diving into the how-to, let’s understand why GitHub CLI is often my go-to tool for GraphQL queries and mutations:

  1. Authentication is handled automatically: No need to manage personal access tokens manually.
  2. Streamlined syntax: Simpler than crafting curl commands.
  3. Local development friendly: Run queries and mutations right from your terminal.
  4. JSON processing: Built-in options for filtering and formatting results.
  5. Pagination support: Ability to work with cursor-based pagination in GraphQL responses.
  6. Consistent experience: Same tool you’re likely using for other GitHub tasks.

How to get started with gh api graphql

First, ensure you have GitHub CLI installed and authenticated with gh auth login. The basic syntax for making a GraphQL query with gh api graphql is:

gh api graphql -H X-Github-Next-Global-ID:1 -f query='
  query {
    viewer {
      login
      name
      bio
    }
  }
'

This simple query returns your GitHub username, the name you have defined in your profile, and your bio. The -f flag defines form variables, with query= being the GraphQL query itself.

Here’s our example output:

{
  "data": {
    "viewer": {
      "login": "joshjohanning",
      "name": "Josh Johanning",
      "bio": "DevOps Architect | GitHub"
    }
  }
}

Running queries and mutations

Basic query example

Let’s try something more practical—fetching information about a repository. To get started, we’ll use the following query:

gh api graphql -H X-Github-Next-Global-ID:1 -f query='
  query($owner:String!, $repo:String!) {
    repository(owner:$owner, name:$repo) {
      name
      description
      id
      stargazerCount
      forkCount
      issues(states:OPEN) {
        totalCount
      }
    }
  }
' -F owner=octocat -F repo=Hello-World

The -F flag sets variable values that are referenced in the query with $variable.

Here’s our example output:

{
  "data": {
    "repository": {
      "name": "Hello-World",
      "description": "My first repository on GitHub!",
      "id": "R_kgDOABPHjQ",
      "stargazerCount": 2894,
      "forkCount": 2843,
      "issues": {
        "totalCount": 1055
      }
    }
  }
}

💡 Tip: The -H X-Github-Next-Global-ID:1 parameter sets an HTTP header that instructs GitHub’s GraphQL API to use the new global node ID format rather than the legacy format. While your query will function without this header, including it prevents deprecation warnings when referencing node IDs (such as when passing repository.ID in subsequent operations). GitHub recommends adopting this format for all new integrations to ensure long-term compatibility.

Running mutations

Mutations work similarly. Here’s how to create a new issue:

gh api graphql -H X-Github-Next-Global-ID:1 -f query='
  mutation($repositoryId:ID!, $title:String!, $body:String) {
    createIssue(input:{repositoryId:$repositoryId, title:$title, body:$body}) {
      issue {
        url
        number
        title
        body
        state
      }
    }
  }
' -F repositoryId="R_kgDOABPHjQ" -F title="Creating issue with GraphQL" -F body="Issue body created via GraphQL\!"

Make sure to update the repositoryId parameter with the actual repository’s GraphQL ID (an example of returning a repository’s ID is shown in the basic query above!).

Here’s our example output:

{
  "data": {
    "createIssue": {
      "issue": {
        "url": "https://github.com/octocat/Hello-World/issues/3706",
        "number": 3706,
        "title": "Creating issue with GraphQL",
        "body": "Issue body created via GraphQL!",
        "state": "OPEN"
      }
    }
  }
}

Filtering GraphQL results

GitHub CLI supports JQ-style filtering for extracting specific parts of the response, which is invaluable when you need to parse just the repository names or URLs from a query for use in automation scripts. Here is an example of using the --jq flag:

gh api graphql -H X-Github-Next-Global-ID:1 -f query='
  query($owner:String!, $repo:String!) {
    repository(owner:$owner, name:$repo) {
      issues(first:3, states:OPEN) {
        nodes {
          number
          title
          url
        }
      }
    }
  }
' -F owner=octocat -F repo=Hello-World --jq '.data.repository.issues.nodes[]'

The --jq flag accepts JQ expressions to process JSON output. This query returns just the array of issues, without the surrounding GraphQL response structure.

Here’s our example output:

{
  "number": 26,
  "title": "test issue",
  "url": "https://github.com/octocat/Hello-World/issues/26"
}
{
  "number": 27,
  "title": "just for test",
  "url": "https://github.com/octocat/Hello-World/issues/27"
}
{
  "number": 28,
  "title": "Test",
  "url": "https://github.com/octocat/Hello-World/issues/28"
}

We could have modified the --jq flag to just return the issue URLs, like so:

gh api graphql -H X-Github-Next-Global-ID:1 -f query='
  query($owner:String!, $repo:String!) {
    repository(owner:$owner, name:$repo) {
      issues(first:3, states:OPEN) {
        nodes {
          number
          title
          url
        }
      }
    }
  }
' -F owner=octocat -F repo=Hello-World --jq '.data.repository.issues.nodes[].url'

Here’s our example output:

https://github.com/octocat/Hello-World/issues/26
https://github.com/octocat/Hello-World/issues/27
https://github.com/octocat/Hello-World/issues/28

Handling pagination

GitHub’s GraphQL API limits results to a maximum of 100 items per page, which means you’ll need pagination to retrieve larger datasets.

Pagination in GraphQL works by returning a “cursor” with each page of results, which acts as a pointer to where the next set of results should begin. When you request the next page, you provide this cursor to indicate where to start.

The easiest way to handle this pagination in the GitHub CLI is with the --paginate flag, which automatically collects all pages of results for you by managing these cursors behind the scenes. Here’s what that looks like in a query:

gh api graphql --paginate -H X-Github-Next-Global-ID:1 -f query='
  query($owner:String!, $repo:String!, $endCursor:String) {
    repository(owner:$owner, name:$repo) {
      issues(first:100, after:$endCursor, states:OPEN, orderBy:{field:CREATED_AT, direction:DESC}) {
        pageInfo {
          hasNextPage
          endCursor
        }
        nodes {
          number
          title
          createdAt
        }
      }
    }
  }
' -F owner=octocat -F repo=Hello-World

The pageInfo object with its hasNextPage and endCursor fields is essential for pagination. When you use the --paginate flag, GitHub CLI automatically uses these fields to fetch all available pages for your query, combining the results into a single response.

Here’s our example output:

{
  "data": {
    "repository": {
      "issues": {
        "pageInfo": {
          "hasNextPage": true,
          "endCursor": "Y3Vyc29yOnYyOpK5MjAyNC0xMi0zMFQxNDo0ODo0NC0wNjowMM6kunD3"
        },
        "nodes": [
          {
            "number": 3708,
            "title": "Creating issue with GraphQL once more",
            "createdAt": "2025-04-02T18:15:11Z",
            "author": {
              "login": "joshjohanning"
            }
          },
          {
            "number": 3707,
            "title": "Creating issue with GraphQL again",
            "createdAt": "2025-04-02T18:15:02Z",
            "author": {
              "login": "joshjohanning"
            }
          },
          {
            "number": 3706,
            "title": "Creating issue with GraphQL",
            "createdAt": "2025-04-02T18:14:37Z",
            "author": {
              "login": "joshjohanning"
            }
          },
          … and so on
        ]
      }
    }
  }
}

This approach works great for moderate amounts of data, but keep in mind that GitHub’s GraphQL API has rate limits, so extremely large queries might need to implement delays between requests.

💡 Important limitation: The --paginate flag can only handle pagination for a single connection at a time. For example, when listing repository issues as shown above, it can paginate through all issues, but cannot simultaneously paginate through each issue’s comments. For nested pagination, you’ll need to implement custom logic.

Building complex scripts: Chaining GraphQL queries together

When working with GitHub’s GraphQL API, you often need to connect multiple queries to accomplish a complex task. Let’s look at how to chain GraphQL calls together using the GitHub CLI:

ISSUE_ID=$(gh api graphql -H X-Github-Next-Global-ID:1 -f query='
  query($owner: String!, $repo: String!, $issue_number: Int!) {
    repository(owner: $owner, name: $repo) {
      issue(number: $issue_number) {
        id
      }
    }
  }
' -F owner=joshjohanning -F repo=graphql-fun -F issue_number=1 --jq '.data.repository.issue.id') 
gh api graphql -H GraphQL-Features:sub_issues -H X-Github-Next-Global-ID:1 -f query='
query($issueId: ID!) {
  node(id: $issueId) {
    ... on Issue {
      subIssuesSummary {
        total
        completed
        percentCompleted
      }
    }
  }
}' -F issueId="$ISSUE_ID"

Here’s what this shell script is doing:

  1. The first query captures an issue’s ID using the repository name and issue number
  2. The --jq flag extracts just the ID value and stores it in a variable
  3. The second query passes this ID to retrieve a summary of sub-issues

Here’s our example output:

{
  "data": {
    "node": {
      "subIssuesSummary": {
        "total": 3,
        "completed": 1,
        "percentCompleted": 33
      }
    }
  }
}

Take this with you

The gh api graphql command provides a convenient way to interact with GitHub’s GraphQL API directly from your terminal. It eliminates the need for token management, simplifies query syntax and formatting, and handles basic pagination that would otherwise be complex to implement. Whether you’re running complex queries or simple mutations, this approach offers a streamlined developer experience.

Next time you need to interact with GitHub’s GraphQL API, skip the GraphQL Explorer on the web and try the GitHub CLI approach. It might just become your preferred method for working with GitHub’s powerful GraphQL API capabilities.

Written by

Exploring GitHub CLI: How to interact with GitHub’s GraphQL API endpoint

Josh is a senior DevOps architect on the GitHub FastTrack team. He has extensive experience promoting DevOps principles to companies large and small from his time as a consultant and now at GitHub. He is very passionate about accelerating teams’ cloud journeys with GitHub and GitHub Actions. In his off time, Josh enjoys home improvement projects, traveling to new places, mixing craft cocktails, and spending time with his wife, cats, and daughter.

Source link