GraphQL
GraphQL queries, mutations, and the GitHub GraphQL API via gh CLI.
Query Fundamentals
Basic query — request specific fields (no over-fetching)
curl -s -X POST https://api.github.com/graphql \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ viewer { login name company } }"}' | jq .
Query with variables — parameterized, reusable
curl -s -X POST https://api.github.com/graphql \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"query": "query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { stargazerCount description } }",
"variables": {"owner": "anthropics", "name": "claude-code"}
}' | jq '.data.repository'
GitHub GraphQL (gh CLI)
gh api graphql — built-in GraphQL support
gh api graphql -f query='
query {
viewer {
login
repositories(first: 10, orderBy: {field: UPDATED_AT, direction: DESC}) {
nodes {
name
updatedAt
isPrivate
}
}
}
}
' | jq '.data.viewer.repositories.nodes[] | {name, updatedAt}'
Query with variables via gh
gh api graphql \
-f query='query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
issues(first: 5, states: OPEN) {
nodes { number title createdAt }
}
}
}' \
-f owner="owner" \
-f name="repo" | jq '.data.repository.issues.nodes[]'
Mutations
Create an issue via GraphQL mutation
gh api graphql -f query='
mutation($repoId: ID!, $title: String!, $body: String) {
createIssue(input: {repositoryId: $repoId, title: $title, body: $body}) {
issue {
number
url
}
}
}
' -f repoId="$REPO_NODE_ID" \
-f title="Bug: API returns 500 on empty payload" \
-f body="Steps to reproduce..."
Pagination (Relay Cursor)
GraphQL uses cursor-based pagination by convention
gh api graphql -f query='
query($cursor: String) {
viewer {
repositories(first: 50, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
name
}
}
}
}
' | jq '{repos: [.data.viewer.repositories.nodes[].name], next: .data.viewer.repositories.pageInfo}'
Loop through all pages
cursor=""
while true; do
if [[ -z "$cursor" ]]; then
result=$(gh api graphql -f query='{ viewer { repositories(first:100) { pageInfo { hasNextPage endCursor } nodes { name } } } }')
else
result=$(gh api graphql -f query='query($c:String!){ viewer { repositories(first:100, after:$c) { pageInfo { hasNextPage endCursor } nodes { name } } } }' -f c="$cursor")
fi
echo "$result" | jq -r '.data.viewer.repositories.nodes[].name'
has_next=$(echo "$result" | jq -r '.data.viewer.repositories.pageInfo.hasNextPage')
[[ "$has_next" != "true" ]] && break
cursor=$(echo "$result" | jq -r '.data.viewer.repositories.pageInfo.endCursor')
done
Introspection
Discover available types and fields
gh api graphql -f query='
{
__schema {
queryType { name }
types { name kind description }
}
}
' | jq '.data.__schema.types[] | select(.kind == "OBJECT") | .name' | head -20
Explore fields on a specific type
gh api graphql -f query='
{
__type(name: "Repository") {
fields { name type { name kind } }
}
}
' | jq '.data.__type.fields[] | {name, type: .type.name}'
Python httpx with GraphQL
GraphQL query via httpx
import httpx
query = """
query($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
stargazerCount
issues(states: OPEN) { totalCount }
}
}
"""
response = httpx.post(
"https://api.github.com/graphql",
headers={"Authorization": f"Bearer {token}"},
json={"query": query, "variables": {"owner": "owner", "name": "repo"}},
)
data = response.json()["data"]["repository"]
print(f"Stars: {data['stargazerCount']}, Open Issues: {data['issues']['totalCount']}")
GraphQL vs REST
Aspect GraphQL REST Over-fetching Client picks fields Server decides payload Under-fetching Single query, nested data Multiple endpoints, N+1 Versioning No versions (evolve schema) /v1/, /v2/ endpoints Caching Complex (POST only) Simple (HTTP cache, ETag) Tooling Schema introspection OpenAPI/Swagger When to use Complex relational data Simple CRUD, caching needed