# GitHub GraphQL API Examples

GraphQL is a query language for web services APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more.

For the examples here you can play with the queries using also /ˈɡrafək(ə)l/, a graphical interactive in-browser GraphQL IDE. You can use it in any of these ways:

(opens new window)

Watch the youtube video GitHub's GraphQL API (opens new window) by Kent C. Dodds

More advanced staff is in the video Advanced patterns for GitHub's GraphQL API (opens new window)

# Query Example: Number of repos in an Organization

# Structure of a Query

GraphQL queries return only the data you specify and no more ...

To form a query, you must specify fields within fields (also known as nested subfields) (opens new window) until you return only scalars (opens new window).

Queries are structured like this:

query {
  JSON-OBJECT-TO-RETURN
}
1
2
3

Here is an example. These are the contents of the file org-num-repos.gql:

query {
  organization(login: "ULL-MII-SYTWS-2122") {
    repositories {
      totalCount
    }
  }
}
1
2
3
4
5
6
7

# Executing the Query

Go to the live GraphQL GitHub Explorer (opens new window), authenticate
and copy the request.

We can also execute in gh this way:

gh api graphql \
  --field query=@org-num-repos.gql \
  --jq .data.organization.repositories.totalCount
1
2
3

# Analysis of the query

Let us comment the former query

query {
  organization(login: "ULL-MII-SYTWS-2122") {
    repositories {
      totalCount
    }
  }
}
1
2
3
4
5
6
7

step by step:

  • query: The GraphQL query keyword

Every GraphQL schema has a root type (opens new window) for both queries and mutations.

  • The query root operation type must be provided and must be an Object type (opens new window).
  • The mutation root operation type is optional; if it is not provided, the service does not support mutations. If it is provided, it must be an Object type (opens new window).
  • Similarly, the subscription root operation type is also optional; if it is not provided, the service does not support subscriptions. If it is provided, it must be an Object type (opens new window).

The query type defines GraphQL operations that retrieve data from the server.

organization(login: "ULL-MII-SYTWS-2122") { ... }
1

To begin the query, we want to find a organization object.

arguments for queries

The schema validation for organization queries (opens new window) indicates this query requires a login argument.

An argument is a set of key-value pairs attached to a specific field.

Some fields require an argument. We will see later that Mutations require an input object as an argument.

Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service.

When queries come in, they are validated (opens new window) and executed (opens new window) against that schema.

GraphQL objects

The query organization is an object of type Organization (opens new window) that like any GraphQL object

  • Implements some interfaces (opens new window).
    • GraphQL interfaces represent a list of named fields and their arguments.
    • GraphQL objects can then implement these interfaces which requires that the object type will define all fields defined by those interfaces
  • Has some fields

Among the fields we can see that Organization (opens new window)

# gh cli: argument conversion

`-f` versus `-F`

Pass one or more -f/--raw-field values in "key=value" format to add static string parameters to the request payload.

The -F/--field flag has type conversion based on the format of the value.

  • Placeholder values "{owner}", "{repo}", and "{branch}" get populated with values from the repository of the current directory and
  • if the value starts with "@", the rest of the value is interpreted as a filename to read the value from. Pass "-" to read from standard input.
  • literal values "true", "false", "null", and integer numbers get converted to appropriate JSON types;

For GraphQL requests, all fields other than query and operationName[1] are interpreted as GraphQL variables.

➜  graphql-learning git:(main) ✗ cat my-repos.bash
gh api graphql --paginate -F number_of_repos=3 --field query=@my-repos.gql
1
2

# Example: -F versus -f

In this example $number_of_repos is a variable that is set to number 3 inside the command using the option -F number_of_repos=3

graphql-learning git:(main)cat my-repos.gql 

query($number_of_repos:Int!){
  viewer {
    name
     repositories(last: $number_of_repos) {
       nodes {
         name
       }
     }
   }
}
1
2
3
4
5
6
7
8
9
10
11
12

Here is the output of an execution:

➜  graphql-learning git:(main) ✗ gh api graphql \
   --paginate \
   -F number_of_repos=3 \
   --field query=@my-repos.gql
1
2
3
4

and the output:

{
  "data": {
    "viewer": {
      "name": "Casiano Rodriguez-Leon",
      "repositories": {
        "nodes": [
          {
            "name": "asyncmap-crguezl"
          },
          {
            "name": "gh-clone-org"
          },
          {
            "name": "learning-graphql-with-gh"
          }
        ]
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Exercise: -F versus -f

What is the output if we use -f number_of_repos=3 instead of -F number_of_repos=3 in the former request?

# Example: Getting issues

Follows an example of query using GraphQL (see The Example query in GitHub Docs (opens new window)).

We can set the GraphQL query in a separated file:

➜  bin git:(master) cat gh-api-example.graphql
1
query {
  repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
    issues(last:2, states:OPEN) {
      edges {
        node {
          title
          url
          labels(first:5) {
            edges {
              node {
                name
              }
            }
          }
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

To learn more, see the tutorial Forming calls with GraphQL (opens new window).

# Analysis of the query

Looking at the composition line by line:

query {
1

Because we want to read data from the server, not modify it, query is the root operation. (If you don't specify an operation, query is also the default.)

query {
  repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
1
2

To begin the query, we want to find a repository object (opens new window).

The schema validation for the query repository (opens new window) indicates this object requires

  • an owner
  • and a name argument.

As we said before a schema defines a GraphQL API's type system. It describes the complete set of possible data (objects, fields, relationships, everything) that a client can access

query {
  repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
    issues(last:2, states:OPEN) {
1
2
3

A field is a unit of data you can retrieve from an object. As the official GraphQL docs say: The GraphQL query language is basically about selecting fields on objects.

To account for all issues in the repository, we specify the issues field of the repository object.

Some details about the issues field:

# Connections in GraphQL

The docs tell us this object has the type IssueConnection (opens new window).

As usual for connections, the Schema indicates this object requires a field like last or first to specify the number of results per page as an argument, so we provide 2.

The docs also tell us this object accepts a states argument, which is an IssueState enum that accepts OPEN or CLOSED values.

To find only open issues, we give the states key a value of OPEN.

query {
  repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
    issues(last:2, states:OPEN) {
      edges {
        node { ... }
1
2
3
4
5

Edges represent connections between nodes. When you query a connection, you traverse its edges to get to its nodes.

We know issues is a connection because the Doc says it has the IssueConnection type.

Connections let us query related objects as part of the same call. With connections, we can use a single GraphQL call where we would have to use multiple calls to a REST API.

To retrieve data about individual issues, we have to access the node via edges.

query {
  repository(owner:"ULL-MII-SYTWS-2021", name:"p01-t1-iaas-alu0101040882") {
    issues(last:2, states:OPEN) {
      edges {
        node {
          title
          url
          labels(first:5) {
            edges {
              node {
                name
              }
            }
          }
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Here we retrieve the node at the end of the edge.

The IssueConnection docs (opens new window) indicate the node at the end of the IssueConnection type is an Issue object.

Now that we know we're retrieving an Issue object, we can look at the docs for issue (opens new window) and specify the fields we want to return.

Here we specify the title, url, and labels fields of the Issue object.

  • The labels field has the type LabelConnection (opens new window).
  • As with the issues object, because labels is a connection, we must travel its edges to a connected node: the label object.
  • At the node, we can specify the label object fields we want to return, in this case, name.

You can see the code at crguezl/learning-graphql-with-gh/tree/main/gh-graphql-connection-example (opens new window)

# Pagination

pageInfo.nextCursor pageInfo.hasNextPage

When in gh we use the --paginate option, all pages of results will sequentially be requested until there are no more pages of results. For GraphQL requests, this requires that

  1. the original query accepts an $endCursor: String variable and that
  2. it fetches the pageInfo{ hasNextPage, endCursor } set of fields from a collection.

Here is an example that produces an array of objects with the name and branch fields of all the repositories in the specified organization:

#!/bin/bash
ORG=$(gh pwd)
if [[ -n $1 ]]; then ORG=$1; fi

gh api graphql --paginate \
    --jq '
    [
       .data
       .organization
       .repositories
       .nodes[] | 
       { branch: .defaultBranchRef.name, name: .name }
    ]
    ' \
    -F org=$ORG \
    -F query=@org-getallrepos.gql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Where the org-getallrepos.gql file contains:

query($org:String!, $endCursor:String) {
  organization(login:$org) {
    repositories(first: 100, 
                 after: $endCursor, 
                 isFork:false, 
                 orderBy: {field:NAME, direction:ASC}) {
      pageInfo {
        hasNextPage
        endCursor
      }
      nodes {
        name
        defaultBranchRef {
          name
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

See crguezl/learning-graphql-with-gh/org-getallrepos-graphql-pagination (opens new window)

# Mutation

The mutation type defines GraphQL operations that change data on the server.

It is analogous to performing HTTP verbs such as POST, PATCH, and DELETE.

To form a mutation, you must specify three things:

  1. Mutation name. The type of modification you want to perform.
  2. Input object. The data you want to send to the server, composed of input fields. Pass it as an argument to the mutation name.
  3. Payload object. The data you want to return from the server, composed of return fields. Pass it as the body of the mutation name.

Mutations are structured like this:

mutation {
  MUTATION-NAME(
    input: {
      MUTATION-NAME-INPUT!
    }) 
    {
    MUTATION-NAME-PAYLOAD
    }
  }
}
1
2
3
4
5
6
7
8
9
10

The input object in this example is MutationNameInput, and the payload object is MutationNamePayload.

For instance, the following example shows a mutation to add an emoji reaction to the issue.

mutation AddReactionToIssue {
  addReaction(
    input: 
    { 
      subjectId:"I_kwDOGLyMF84838wt",
      content:ROCKET
    }
  ) 
  {
      reaction {
        content
      }
      subject {
        id
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

See the reference docs for addReaction mutation (opens new window), whose description is: Adds a reaction to a subject.

The docs for the mutation list three input fields:

  1. clientMutationId (String)
  2. subjectId (ID!)
  3. content (ReactionContent!)

A required content makes sense:

  • we want to add a reaction, so we'll need to specify which emoji to use.
  • the subjectId is the only way to identify which issue in which repository to react to.

Mutations often require information that you can only find out by performing a query first. In this example AddReactionToIssue, we have to find the issue id:

➜  graphql-learning git:(main) cat findissueid.gql
1
query FindIssueID {
  repository(owner:"crguezl", name:"learning-graphql-with-gh") {
    issue(number:2) {
      id
    }
  }
}
1
2
3
4
5
6
7

which we can get with:

gh api graphql --paginate -F num=1 --field query=@findissueid.gql
{
  "data": {
    "repository": {
      "issue": {
        "id": "I_kwDOGLyMF84837de"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10

How do we know which value to use for the content?

The addReaction (opens new window) docs tell us the content field has the type ReactionContent (opens new window), which is an enum (opens new window) because only certain emoji reactions are supported on GitHub issues.

The rest of the call is composed of the payload object.

This is where we specify the data we want the server to return after we've performed the mutation.

These lines come from the addReaction (opens new window) docs, which three possible return fields:

  1. clientMutationId (String)
  2. reaction (Reaction!)
  3. subject (Reactable!)

In this example, we return the two required fields (reaction and subject), both of which have required subfields (respectively, content and id).

Now we can add a reaction to the issue:

graphql-learning git:(main) cat addreactiontoissue.gql 
mutation AddReactionToIssue {
  addReaction(input:{subjectId:"I_kwDOGLyMF84838wt",content:ROCKET}) {
    reaction {
      content
    }
    subject {
      id
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11

using the command:

gh api graphql --paginate --field query=@addreactiontoissue.gql 
1

# Rename Repository

Here is a second example of mutation that renames a repository:

query getRepoId {
  repository(owner: "ULL-ESIT-DMSI-1920", name: "prueba") {
    id
  }
}

mutation renameRepoName($id: ID!) {
  updateRepository(
    input: {
      name: "prueba-funciona", 
      repositoryId: $id
    }
  ) 
  {
    repository {
      name
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Discussions

# Get Discussions in the repo crguezl/learning-graphql-with-gh

discussions-mutation git:(main)cat get-discussions.bash 
#!/bin/bash 
# Get discussions 
gh api graphql \
  -H 'GraphQL-Features: discussions_api' \
  -F owner=':owner' \
  -F name=':repo' \
  -f query='
query getDiscussions($owner: String!, $name: String!) {
  repository(owner: $owner, name: $name) {
    discussions(first: 10) {
      edges {
        node {
          id
          number
          body
        }
      }
    }
  }
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Execution:

➜  discussions-mutation git:(main) ./get-discussions.bash        
{
  "data": {
    "repository": {
      "discussions": {
        "edges": [
          {
            "node": {
              "id": "D_kwDOGLyMF84ARkhY",
              "number": 3,
              "body": "<!--\r\n    ✏️ Optional: Customize the content below to let your community know what you intend to use Discussions for.\r\n-->\r\n## 👋 Welcome!\r\n  We’re using Discussions as a place to connect with other members of our community. We hope that you:\r\n  * Ask questions you’re wondering about.\r\n  * Share ideas.\r\n  * Engage with other community members.\r\n  * Welcome others and are open-minded. Remember that this is a community we\r\n  build together 💪.\r\n\r\n  To get started, comment below with an introduction of yourself and tell us about what you do with this community.\r\n\r\n<!--\r\n  For the maintainers, here are some tips 💡 for getting started with Discussions. We'll leave these in Markdown comments for now, but feel free to take out the comments for all maintainers to see.\r\n\r\n  📢 **Announce to your community** that Discussions is available! Go ahead and send that tweet, post, or link it from the website to drive traffic here.\r\n\r\n  🔗 If you use issue templates, **link any relevant issue templates** such as questions and community conversations to Discussions. Declutter your issues by driving community content to where they belong in Discussions. If you need help, here's a [link to the documentation](https://docs.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser).\r\n\r\n  ➡️ You can **convert issues to discussions** either individually or bulk by labels. Looking at you, issues labeled “question” or “discussion”.\r\n-->\r\n"
            }
          }
        ]
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Get Comments in the Discussion

discussions-mutation git:(main)cat get-comments.bash 
#!/bin/bash 
# Discussion 3 and comments

```GraphQL
declare -i DISCUSSION_NUMBER=3

if [[ $# != 0 ]]; then
  DISCUSSION_NUMBER=$1
fi
gh api graphql \
-H 'GraphQL-Features: discussions_api' \
-F discussionNumber=${DISCUSSION_NUMBER} \
-F owner=':owner' \
-F name=':repo' \
-f query='query getComments($owner: String!, $name: String!, $discussionNumber: Int!) {
  repository(owner: $owner, name: $name) {
    discussion(number: $discussionNumber) {
      id
      title
      body
      comments(first: 10) {
        totalCount
        edges {
          node {
            id
            body
            replies(first: 5) {
              edges {
                node {
                  body
                  id
                }
              }
            }
          }
        }
      }
    }
  }
}
'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

Execution:

➜  discussions-mutation git:(main) ✗ ./get-comments.bash         
{
  "data": {
    "repository": {
      "discussion": {
        "id": "D_kwDOGLyMF84ARkhY",
        "title": "Welcome to learning-graphql-with-gh Discussions!",
        "body": "<!--\r\n    ✏️ Optional: Customize the content below to let your community know what you intend to use Discussions for.\r\n-->\r\n## 👋 Welcome!\r\n  We’re using Discussions as a place to connect with other members of our community. We hope that you:\r\n  * Ask questions you’re wondering about.\r\n  * Share ideas.\r\n  * Engage with other community members.\r\n  * Welcome others and are open-minded. Remember that this is a community we\r\n  build together 💪.\r\n\r\n  To get started, comment below with an introduction of yourself and tell us about what you do with this community.\r\n\r\n<!--\r\n  For the maintainers, here are some tips 💡 for getting started with Discussions. We'll leave these in Markdown comments for now, but feel free to take out the comments for all maintainers to see.\r\n\r\n  📢 **Announce to your community** that Discussions is available! Go ahead and send that tweet, post, or link it from the website to drive traffic here.\r\n\r\n  🔗 If you use issue templates, **link any relevant issue templates** such as questions and community conversations to Discussions. Declutter your issues by driving community content to where they belong in Discussions. If you need help, here's a [link to the documentation](https://docs.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser).\r\n\r\n  ➡️ You can **convert issues to discussions** either individually or bulk by labels. Looking at you, issues labeled “question” or “discussion”.\r\n-->\r\n",
        "comments": {
          "totalCount": 1,
          "edges": [
            {
              "node": {
                "id": "DC_kwDOGLyMF84AQN01",
                "body": "Esta es un comentario en la primera discusión.\r\nVamos a usar la API GraphQL para añadir comentarios",
                "replies": {
                  "edges": [
                    {
                      "node": {
                        "body": "Contador de cuerpo: 1",
                        "id": "DC_kwDOGLyMF84AQN4l"
                      }
                    }
                  ]
                }
              }
            }
          ]
        }
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# Mutation: Add Reply to Discussion Comment

discussions-mutation git:(main)cat reply-to-discussion-comment.bash 
#!/bin/bash 
BODY="Contador de cuerpo: 3"
discussionId="D_kwDOGLyMF84ARkhY"
replyToId="DC_kwDOGLyMF84AQN01"
if [[ $# != 0 ]]; then
  BODY=$1
fi
gh api graphql \
-H 'GraphQL-Features: discussions_api' \
-f body="Contador de cuerpo: $BODY" \
-F discussionId=$discussionId \
-F replyToId=$replyToId \
-f query='
mutation addComment($body: String!, $discussionId: ID!, $replyToId: ID!){
  addDiscussionComment(input:
    {
      body: $body , 
      discussionId: $discussionId, 
      replyToId: $replyToId 
    }
  )
  {
    comment{
      body
      id
    }
  }
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Execution:

➜  discussions-mutation git:(main) ✗ ./reply-to-discussion-comment.bash
{
  "data": {
    "addDiscussionComment": {
      "comment": {
        "body": "Contador de cuerpo: Contador de cuerpo: 3",
        "id": "DC_kwDOGLyMF84AQN_k"
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11

# References for Discussions GraphQL API

# Footnotes


  1. The operation name is a meaningful and explicit name for your operation. It is only required in multi-operation documents, but its use is encouraged because it is very helpful for debugging and server-side logging. ↩︎

Last Updated: a month ago