earl

GraphQL

Call GraphQL APIs from an Earl template.

The GraphQL protocol sends queries and mutations to a GraphQL endpoint over HTTP POST.

A complete example

Here is a template that fetches basic info about a GitHub repository:

version    = 1
provider   = "github"
categories = ["scm"]

command "get_repo" {
  title       = "Get repository"
  summary     = "Fetch basic info about a GitHub repository"
  description = "Returns the star count, description, and primary language for a repository."

  annotations {
    mode    = "read"
    secrets = ["github.token"]
  }

  param "owner" {
    type        = "string"
    required    = true
    description = "Repository owner (user or org)"
  }

  param "repo" {
    type        = "string"
    required    = true
    description = "Repository name"
  }

  operation {
    protocol = "graphql"
    url      = "https://api.github.com/graphql"

    auth {
      kind   = "bearer"
      secret = "github.token"
    }

    graphql {
      query = <<-GQL
        query($owner: String!, $repo: String!) {
          repository(owner: $owner, name: $repo) {
            stargazerCount
            description
            primaryLanguage { name }
          }
        }
      GQL
      variables = {
        owner = "{{ args.owner }}"
        repo  = "{{ args.repo }}"
      }
    }
  }

  result {
    decode = "json"
    output = "{{ result.data.repository.stargazerCount }} stars — {{ result.data.repository.description | default('no description') }}"
  }
}

Store your token and run it:

earl secrets set github.token
earl call github.get_repo --owner torvalds --repo linux

Walk-through

operation

operation {
  protocol = "graphql"
  url      = "https://api.github.com/graphql"
  ...
}

protocol = "graphql" and url are the only required fields. GraphQL requests default to HTTP POST. A method field exists but is rarely needed — only set it if the server requires GET instead of the standard POST.

For the full list of auth kinds and OAuth2 profile setup, see Secrets & Auth.

graphql block

graphql {
  query = <<-GQL
    query($owner: String!, $repo: String!) {
      repository(owner: $owner, name: $repo) {
        stargazerCount
        description
        primaryLanguage { name }
      }
    }
  GQL
  variables = {
    owner = "{{ args.owner }}"
    repo  = "{{ args.repo }}"
  }
}

query holds the GQL document. The heredoc syntax (<<-GQL ... GQL) keeps multi-line queries readable without escaping.

variables is a map whose keys must match the $variable declarations in the query. Values support Jinja expressions — "{{ args.owner }}" is rendered before the request is sent.

A third field, operation_name, is optional. Use it only when the document contains multiple named operations and you need to tell the server which one to execute.

result

result {
  decode = "json"
  output = "{{ result.data.repository.stargazerCount }} stars — {{ result.data.repository.description | default('no description') }}"
}

The GraphQL response is a JSON envelope. decode = "json" parses it and makes the full object available as result. Successful data is at result.data.<field>. If the server returns errors, they appear at result.errors.

Mutations

Mutations work the same way. Use the mutation keyword in the GQL document and set mode = "write" in annotations.

command "add_star" {
  title       = "Star repository"
  summary     = "Add a star to a GitHub repository"
  description = "Stars a repository on behalf of the authenticated user."

  annotations {
    mode    = "write"
    secrets = ["github.token"]
  }

  param "repo_id" {
    type        = "string"
    required    = true
    description = "GraphQL node ID of the repository"
  }

  operation {
    protocol = "graphql"
    url      = "https://api.github.com/graphql"

    auth {
      kind   = "bearer"
      secret = "github.token"
    }

    graphql {
      query = <<-GQL
        mutation($id: ID!) {
          addStar(input: { starrableId: $id }) {
            starrable { stargazerCount }
          }
        }
      GQL
      variables = {
        id = "{{ args.repo_id }}"
      }
    }
  }

  result {
    decode = "json"
    output = "Starred. New star count: {{ result.data.addStar.starrable.stargazerCount }}"
  }
}

To switch between production and staging endpoints, see Environments.

For naming conventions, secret declarations, and other patterns that apply across all protocols, see Best Practices.

For the full field reference, see Template Schema — GraphQL.

On this page