Files
2025-11-30 08:38:11 +08:00

17 KiB

GitLab GraphQL API Reference

Overview

GitLab's GraphQL API provides a more efficient alternative to REST API, allowing you to request exactly the data you need in a single query. The GraphQL API is the primary development focus for new features at GitLab.

GraphQL Endpoint

https://gitlab.com/api/graphql

For self-hosted instances:

https://your-gitlab-instance.com/api/graphql

Authentication

Personal Access Token

curl --header "Authorization: Bearer <your_token>" \
  --header "Content-Type: application/json" \
  --request POST \
  --data '{"query": "{ currentUser { username } }"}' \
  https://gitlab.com/api/graphql

In HTTP Headers

Authorization: Bearer <your_access_token>

Interactive GraphQL Explorer (GraphiQL)

Access the interactive explorer:

https://gitlab.com/-/graphql-explorer

Features:

  • Auto-completion
  • Schema documentation
  • Query validation
  • Query history

Basic Query Structure

{
  currentUser {
    id
    username
    name
    email
  }
}

Common Queries

Get Current User

{
  currentUser {
    id
    username
    name
    email
    avatarUrl
    webUrl
    status {
      message
      availability
    }
  }
}

Get Project Details

{
  project(fullPath: "group/project") {
    id
    name
    description
    visibility
    createdAt
    lastActivityAt
    webUrl
    repository {
      rootRef
      empty
    }
    statistics {
      commitCount
      storageSize
      repositorySize
      lfsObjectsSize
    }
  }
}

List Projects

{
  projects(first: 10, membership: true) {
    nodes {
      id
      name
      fullPath
      description
      visibility
      createdAt
      webUrl
      starCount
      forksCount
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Get Merge Requests

{
  project(fullPath: "group/project") {
    mergeRequests(first: 10, state: opened) {
      nodes {
        id
        iid
        title
        description
        state
        createdAt
        updatedAt
        author {
          username
          name
        }
        sourceBranch
        targetBranch
        webUrl
        draft
        workInProgress
        mergeStatus
        approved
        conflicts
        diffStats {
          additions
          deletions
        }
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

Get Issues

{
  project(fullPath: "group/project") {
    issues(first: 10, state: opened) {
      nodes {
        id
        iid
        title
        description
        state
        createdAt
        closedAt
        author {
          username
          name
        }
        assignees {
          nodes {
            username
            name
          }
        }
        labels {
          nodes {
            title
            color
          }
        }
        milestone {
          title
          dueDate
        }
        webUrl
        upvotes
        downvotes
      }
      pageInfo {
        hasNextPage
        endCursor
      }
    }
  }
}

Get Pipeline Details

{
  project(fullPath: "group/project") {
    pipeline(iid: "123") {
      id
      iid
      status
      ref
      createdAt
      updatedAt
      startedAt
      finishedAt
      duration
      coverage
      user {
        username
        name
      }
      jobs {
        nodes {
          id
          name
          stage
          status
          createdAt
          startedAt
          finishedAt
          duration
          webPath
        }
      }
    }
  }
}

Get Commits

{
  project(fullPath: "group/project") {
    repository {
      tree(ref: "main") {
        lastCommit {
          id
          sha
          title
          message
          authoredDate
          author {
            name
            email
          }
        }
      }
    }
  }
}

Get Group Details

{
  group(fullPath: "group-name") {
    id
    name
    fullPath
    description
    visibility
    webUrl
    projects(first: 10) {
      nodes {
        name
        fullPath
      }
    }
    members {
      nodes {
        user {
          username
          name
        }
        accessLevel {
          integerValue
          stringValue
        }
      }
    }
  }
}

Mutations

Create Issue

mutation {
  createIssue(input: {
    projectPath: "group/project"
    title: "New issue title"
    description: "Issue description"
    assigneeIds: ["gid://gitlab/User/1"]
    labelIds: ["gid://gitlab/Label/1"]
  }) {
    issue {
      id
      iid
      title
      webUrl
    }
    errors
  }
}

Update Issue

mutation {
  updateIssue(input: {
    projectPath: "group/project"
    iid: "123"
    title: "Updated title"
    description: "Updated description"
    stateEvent: CLOSE
  }) {
    issue {
      id
      title
      state
    }
    errors
  }
}

Create Merge Request

mutation {
  mergeRequestCreate(input: {
    projectPath: "group/project"
    title: "New feature"
    description: "This MR adds..."
    sourceBranch: "feature-branch"
    targetBranch: "main"
  }) {
    mergeRequest {
      id
      iid
      title
      webUrl
    }
    errors
  }
}

Update Merge Request

mutation {
  mergeRequestUpdate(input: {
    projectPath: "group/project"
    iid: "123"
    title: "Updated title"
    description: "Updated description"
    assigneeIds: ["gid://gitlab/User/1"]
  }) {
    mergeRequest {
      id
      title
      state
    }
    errors
  }
}

Merge Merge Request

mutation {
  mergeRequestMerge(input: {
    projectPath: "group/project"
    iid: "123"
    squash: true
  }) {
    mergeRequest {
      id
      state
      mergedAt
    }
    errors
  }
}

Create Note (Comment)

mutation {
  createNote(input: {
    noteableId: "gid://gitlab/Issue/123"
    body: "This is a comment"
  }) {
    note {
      id
      body
      author {
        username
      }
    }
    errors
  }
}

Update Project

mutation {
  updateProject(input: {
    projectPath: "group/project"
    description: "New description"
    visibility: "public"
  }) {
    project {
      id
      description
      visibility
    }
    errors
  }
}

Create Branch

mutation {
  createBranch(input: {
    projectPath: "group/project"
    name: "new-branch"
    ref: "main"
  }) {
    branch {
      name
      commit {
        id
      }
    }
    errors
  }
}

Create Snippet

mutation {
  createSnippet(input: {
    projectPath: "group/project"
    title: "Code snippet"
    fileName: "example.rb"
    content: "puts 'Hello World'"
    visibility: "private"
  }) {
    snippet {
      id
      title
      webUrl
    }
    errors
  }
}

Run Pipeline

mutation {
  pipelineCreate(input: {
    projectPath: "group/project"
    ref: "main"
  }) {
    pipeline {
      id
      status
    }
    errors
  }
}

Cancel Pipeline

mutation {
  pipelineCancel(input: {
    id: "gid://gitlab/Ci::Pipeline/123"
  }) {
    pipeline {
      id
      status
    }
    errors
  }
}

Variables and Fragments

Using Variables

query GetProject($fullPath: ID!) {
  project(fullPath: $fullPath) {
    id
    name
    description
  }
}

Variables:

{
  "fullPath": "group/project"
}

Using Fragments

fragment UserInfo on User {
  id
  username
  name
  email
  avatarUrl
}

query {
  currentUser {
    ...UserInfo
  }
  project(fullPath: "group/project") {
    author {
      ...UserInfo
    }
  }
}

Pagination

Cursor-Based Pagination

query {
  projects(first: 10, after: "cursor_value") {
    nodes {
      id
      name
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
  }
}

Complete Pagination Example

import requests
import json

def fetch_all_projects(token):
    url = "https://gitlab.com/api/graphql"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }

    has_next_page = True
    after_cursor = None
    all_projects = []

    while has_next_page:
        query = """
        query($after: String) {
          projects(first: 10, after: $after, membership: true) {
            nodes {
              id
              name
              fullPath
            }
            pageInfo {
              hasNextPage
              endCursor
            }
          }
        }
        """

        variables = {"after": after_cursor}

        response = requests.post(
            url,
            headers=headers,
            json={"query": query, "variables": variables}
        )

        data = response.json()["data"]
        projects = data["projects"]

        all_projects.extend(projects["nodes"])
        has_next_page = projects["pageInfo"]["hasNextPage"]
        after_cursor = projects["pageInfo"]["endCursor"]

    return all_projects

Introspection

Query Schema

{
  __schema {
    types {
      name
      description
    }
  }
}

Query Type Details

{
  __type(name: "Project") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}

Advanced Queries

Nested Relationships

{
  group(fullPath: "group-name") {
    projects(first: 5) {
      nodes {
        name
        mergeRequests(first: 3, state: opened) {
          nodes {
            title
            author {
              username
            }
            assignees {
              nodes {
                username
              }
            }
            approvedBy {
              nodes {
                username
              }
            }
          }
        }
      }
    }
  }
}

Conditional Fields

query GetProject($includeMergeRequests: Boolean!) {
  project(fullPath: "group/project") {
    id
    name
    mergeRequests(first: 10) @include(if: $includeMergeRequests) {
      nodes {
        title
      }
    }
  }
}

Aliases

{
  openIssues: project(fullPath: "group/project") {
    issues(state: opened) {
      count
    }
  }
  closedIssues: project(fullPath: "group/project") {
    issues(state: closed) {
      count
    }
  }
}

Client Libraries

Python (gql)

from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport

transport = RequestsHTTPTransport(
    url="https://gitlab.com/api/graphql",
    headers={"Authorization": f"Bearer {token}"},
)

client = Client(transport=transport, fetch_schema_from_transport=True)

query = gql("""
    query {
        currentUser {
            username
            name
        }
    }
""")

result = client.execute(query)
print(result)

JavaScript (Apollo Client)

import { ApolloClient, InMemoryCache, gql, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://gitlab.com/api/graphql',
});

const authLink = setContext((_, { headers }) => {
  return {
    headers: {
      ...headers,
      authorization: `Bearer ${token}`,
    }
  }
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
});

const GET_CURRENT_USER = gql`
  query {
    currentUser {
      username
      name
    }
  }
`;

client.query({ query: GET_CURRENT_USER })
  .then(result => console.log(result));

Ruby (graphql-client)

require "graphql/client"
require "graphql/client/http"

module GitLabAPI
  HTTP = GraphQL::Client::HTTP.new("https://gitlab.com/api/graphql") do
    def headers(context)
      { "Authorization" => "Bearer #{ENV['GITLAB_TOKEN']}" }
    end
  end

  Schema = GraphQL::Client.load_schema(HTTP)
  Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end

CurrentUserQuery = GitLabAPI::Client.parse <<-'GRAPHQL'
  query {
    currentUser {
      username
      name
    }
  }
GRAPHQL

result = GitLabAPI::Client.query(CurrentUserQuery)
puts result.data.current_user.username

Rate Limiting

GraphQL queries count towards API rate limits based on query complexity.

Check rate limit:

{
  currentUser {
    username
  }
}

Response headers:

  • RateLimit-Limit: Total allowed
  • RateLimit-Remaining: Remaining requests
  • RateLimit-Reset: Reset timestamp

Error Handling

Error Response Format

{
  "data": null,
  "errors": [
    {
      "message": "Error message",
      "path": ["field", "path"],
      "extensions": {
        "code": "ERROR_CODE"
      }
    }
  ]
}

Common Error Types

  • NOT_FOUND: Resource not found
  • UNAUTHORIZED: Authentication required
  • FORBIDDEN: Insufficient permissions
  • VALIDATION_ERROR: Invalid input
  • INTERNAL_SERVER_ERROR: Server error

Error Handling Example

def execute_query(query, variables=None):
    response = requests.post(
        "https://gitlab.com/api/graphql",
        headers={"Authorization": f"Bearer {token}"},
        json={"query": query, "variables": variables}
    )

    result = response.json()

    if "errors" in result:
        for error in result["errors"]:
            print(f"Error: {error['message']}")
            if "path" in error:
                print(f"Path: {error['path']}")
        return None

    return result["data"]

Best Practices

1. Request Only Needed Fields

Bad:

{
  project(fullPath: "group/project") {
    id
    name
    description
    # ... all fields
  }
}

Good:

{
  project(fullPath: "group/project") {
    id
    name
  }
}

2. Use Fragments for Reusability

fragment IssueFields on Issue {
  id
  iid
  title
  state
  author {
    username
  }
}

query {
  project(fullPath: "group/project") {
    openIssues: issues(state: opened) {
      nodes {
        ...IssueFields
      }
    }
    closedIssues: issues(state: closed) {
      nodes {
        ...IssueFields
      }
    }
  }
}

3. Implement Pagination

Always use pagination for large datasets:

{
  projects(first: 100, after: $cursor) {
    nodes { ... }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

4. Handle Errors Gracefully

async function fetchData() {
  try {
    const result = await client.query({ query: MY_QUERY });
    return result.data;
  } catch (error) {
    if (error.graphQLErrors) {
      error.graphQLErrors.forEach(({ message, path }) => {
        console.error(`GraphQL error: ${message} at ${path}`);
      });
    }
    if (error.networkError) {
      console.error(`Network error: ${error.networkError}`);
    }
  }
}

5. Use Variables

query GetProject($path: ID!, $mrCount: Int!) {
  project(fullPath: $path) {
    mergeRequests(first: $mrCount) {
      nodes { title }
    }
  }
}

6. Optimize Query Depth

Limit nested query depth to avoid performance issues:

# Avoid deep nesting
{
  group {
    projects {
      mergeRequests {
        commits {
          notes {
            # Too deep!
          }
        }
      }
    }
  }
}

Useful Queries for Common Tasks

Search Across Projects

{
  projects(search: "keyword", first: 10) {
    nodes {
      name
      fullPath
      description
    }
  }
}

Get User Activity

{
  user(username: "john.doe") {
    assignedMergeRequests(first: 10) {
      nodes {
        title
        project {
          name
        }
      }
    }
    authoredMergeRequests(first: 10) {
      nodes {
        title
        state
      }
    }
  }
}

Get Deployment Status

{
  project(fullPath: "group/project") {
    environments {
      nodes {
        name
        state
        lastDeployment {
          status
          createdAt
          deployable {
            ... on Job {
              name
            }
          }
        }
      }
    }
  }
}

Get Security Vulnerabilities

{
  project(fullPath: "group/project") {
    vulnerabilities {
      nodes {
        id
        title
        severity
        state
        reportType
        detectedAt
      }
    }
  }
}

Migration from REST to GraphQL

REST to GraphQL Comparison

REST: Get project and its merge requests

# 2 requests
curl "https://gitlab.com/api/v4/projects/123"
curl "https://gitlab.com/api/v4/projects/123/merge_requests"

GraphQL: Single request

{
  project(fullPath: "group/project") {
    id
    name
    mergeRequests(first: 10) {
      nodes {
        title
      }
    }
  }
}

Subscriptions (Real-time Updates)

GitLab supports GraphQL subscriptions for real-time updates:

subscription {
  issuableUpdated {
    issue {
      id
      title
      state
    }
  }
}

Additional Resources