Developer
Get Support
Sign in
Get Support
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Sign in
DOCUMENTATION
Cloud
Data Center
Resources
Sign in
Last updated Nov 13, 2025

GraphQL and Cypher

The Teamwork Graph API provides powerful query capabilities to help you access and work with connected data. This guide introduces you to the query languages used to interact with Teamwork Graph: GraphQL for querying graph data and Cypher for traversing relationships in the Graph.

GraphQL

GraphQL is a query language for APIs that enables you to request exactly the data you need. Unlike traditional REST APIs where you might need to make multiple requests to different endpoints, GraphQL allows you to:

  • Request specific fields: Fetch only the properties you need from objects
  • Get multiple resources in a single request: Reduce over-fetching and under-fetching
  • Navigate relationships: Follow connections between objects naturally
  • Strong typing: Benefit from a predictable, type-safe API

When you query Teamwork Graph using GraphQL, you can access objects and their relationships in a structured way. For example, you might query for work items and their related documents, comments, and contributors—all in a single request.

Cypher

For all the relationships on the Teamwork Graph API, we’ll be providing examples of Cypher queries you can use. You don’t need to learn a whole new language to get started with the Teamwork Graph API, though if you are keen to understand more, this page describes how Cypher works in greater detail.

Cypher is a declarative query language designed specifically for working with graph databases. Created for Neo4j, Cypher provides an intuitive syntax for expressing graph patterns and traversing relationships.

Cypher syntax basics

First, let's start with the syntax basics:

TypeBase caseWith label
(is of given type)
With multiple labels
(is of one of the given types)
With properties
Node()
Any node
(:A)
Node with label A
(:A|B|C)
Node with label A or B or C
(:A {id: '123', color: 'red'})
Node with label A and properties
Node with Variable(a)
Any node assigned to variable 'a'
(a:A)
Node with label A assigned to variable 'a'
(a:A|B|C)
Node with label A or B or C assigned to variable 'a'
(a:A {id: 'xyz', color: 'red'})
Node with label A and properties assigned to variable 'a'
Edge-->
Any edge (direction left to right)
-[:E]->
Edge with label E
-[:E|F|G]->
Edge with label E or F or G
-[:E {since: 2020, weight: 5}]->
Edge with label E and properties
Edge with Variable-[e]->
Any edge assigned to variable 'e'
-[e:E]->
Edge with label E assigned to variable 'e'
-[e:E|F|G]->
Edge with label E or F or G assigned to variable 'e'
-[e:E {since: 2020, weight: 5}]->
Edge with label E and properties assigned to variable 'e'

Additional background

  • Cypher relies on “pattern matching” aka MATCH to perform graph traversals.
  • All paths in the graph that satisfy the provided pattern are returned.
  • When no paths match the pattern it returns the empty set ∅.
  • OPTIONAL MATCH is a variant of MATCH; when a pattern is not found in the graph, it returns null instead of ∅.

Illustrative examples

Let's look at some sample Cypher queries in the context of the Teamwork Graph API.

Simple node match

Return an IdentityUser whose ari = 'ari:cloud:identity::user/xyz'

Inline filtering

1
2
MATCH (user:IdentityUser {ari: 'ari:cloud:identity::user/xyz'})
RETURN user

Explicit filtering

1
2
MATCH (user)
WHERE user:IdentityUser AND user.ari = 'ari:cloud:identity::user/xyz'
RETURN user

Most common usage

  • Node label filter and ari filter are inlined
  • Metadata filters are explicitly written in WHERE clauses.
1
2
MATCH (issue:JiraWorkItem {ari: 'ari:cloud:jira::issue/xyz'})
WHERE issue.status = 'done'

Simple one-hop

Return all issues ever reported by a given user.

  • Pattern match a user-reports-issue relation between a user and issues.
1
2
MATCH (user:IdentityUser {ari: 'ari:cloud:identity::user/xyz'})-[:atlassian_user_reported_jira_work_item]->(issue:JiraWorkItem)
RETURN issue

One-hop with filtering

Return all issues reported by a user in the last week.

  • Pattern match a user-reports-issue relation between a user and an issue.
  • Filter results such that the lastUpdated time field on the edge (reported) is within last 7 days.
1
2
MATCH (user:IdentityUser)-[reported:atlassian_user_reported_jira_work_item]->(issue:JiraWorkItem)
WHERE reported.lastUpdated > datetime() - duration('P7D')
RETURN issue

Simple two-hop

Return all Jira projects in which a user created issues.

  • Pattern match a user-reports-issue relation between a user and an issue.
  • Pattern match the issue from above via a jira_space_has_jira_work_item inverse relationship with a Jira project.
1
2
MATCH (user:IdentityUser)-[reported:atlassian_user_reported_jira_work_item]->(issue:JiraWorkItem)<-[:jira_space_has_jira_work_item]-(project:JiraSpace)
RETURN DISTINCT project

Note: this can also be written using two MATCH statements:

1
2
MATCH (user:IdentityUser)-[reported:atlassian_user_reported_jira_work_item]->(issue:JiraWorkItem)
MATCH (issue)<-[:jira_space_has_jira_work_item]-(project:JiraSpace)
RETURN DISTINCT project

Two-hop with filtering

Return all projects a user reported issues in that are not 'done'.

  • Pattern match a user-reports-issue relation between a user and an issue.
  • Pattern match the issue from above via a jira_space_has_jira_work_item inverse relationship with a Jira project.
  • Filter out issues that are already 'done', so only projects that have at least one issue reported by the user that is still not done are returned.
1
2
MATCH (user:IdentityUser)-[reported:atlassian_user_reported_jira_work_item]->(issue:JiraWorkItem)<-[:jira_space_has_jira_work_item]-(project:JiraSpace)
WHERE issue.status != 'done'
RETURN DISTINCT project

Multi-hop with optional matches

Typically we use optional matches in conjunction with matches if we want to "fetch" more information about a certain node.

For each user, find all issues assigned to that user and all Confluence pages they updated and return as a table user, list_of_issues, list_of_pages:

1
2
MATCH (user:IdentityUser)
OPTIONAL MATCH (user)<-[rel1:atlassian_user_assigned_jira_work_item]-(issue:JiraWorkItem)
WHERE rel1.lastUpdated > datetime() - duration('P7D')
OPTIONAL MATCH (user)-[rel2:atlassian_user_updated_confluence_page]->(page:ConfluencePage)
WHERE rel2.lastUpdated > datetime() - duration('P7D')
RETURN user, collect(issue) AS issues, collect(page) AS pages

Another popular use of optional matches is in conjunction with an is null predicate on a path; this is used to perform negative checks.

Find users who got assigned an issue this week but did not create a PR this week:

1
2
MATCH (user:IdentityUser)-[assigned_issues:atlassian_user_assigned_jira_work_item]->(issue:JiraWorkItem)
WHERE assigned_issues.lastUpdated >= datetime() - duration('P7D')
OPTIONAL MATCH (user)-[wrote_code:external_user_created_external_pull_request]->(pr:ExternalPullRequest)
WHERE wrote_code.lastUpdated >= datetime() - duration('P7D')
RETURN user

Multi-relationship

Return all Confluence pages and Jira issues that a user has created:

1
2
MATCH (user:IdentityUser)-[:atlassian_user_created_confluence_page|atlassian_user_created_jira_work_item]->(target)
RETURN user, target

Use the pipe | delimiter to separate relationship types. Filters and ordering are supported on edge fields given that the field is present on all relationship types with the same data type:

1
2
MATCH (user:IdentityUser)-[r:atlassian_user_created_confluence_page|atlassian_user_created_jira_work_item]->(target)
WHERE r.lastUpdated > datetime() - duration('P7D')
RETURN user, target
ORDER BY r.createdAt desc

WHERE NOT: negative pattern match

where not predicate is used to check the non-existence of a pattern among the nodes and edges that are typically pattern matches using other MATCH clauses.

Find pages linked to the issues assigned to me that I have not viewed yet:

1
2
MATCH (user:IdentityUser {ari: $me})
OPTIONAL MATCH (user)-[:atlassian_user_assigned_jira_work_item]->(issue:JiraWorkItem)
WHERE NOT (user)-[:atlassian_user_viewed_jira_work_item]->(issue)
RETURN DISTINCT issue

Simple aggregations

We can also perform simple aggregations in the final return statement.

Get the distinct issues assigned to my teammates:

1
2
MATCH (user:IdentityUser {ari: $me})
MATCH (user)-[:atlassian_user_is_in_atlassian_team]->(team:IdentityTeam)<-[atlassian_user_is_in_atlassian_team]-(mate:IdentityUser)
MATCH (mate)-[:atlassian_user_assigned_jira_work_item]->(issue:JiraWorkItem)
RETURN COLLECT(DISTINCT issue) as issues_without_pr_in_my_team

Recommendations

Use parameters over inline values

We have enhanced the performance of the Cypher query pipeline by implementing query plan caching. To truly benefit from this improvement, we encourage our customers to use parameterized Cypher queries instead of embedding parameters directly within the query string.

Example - preferred:

1
2
{
  "cypherQuery": "MATCH (:IdentityUser {ari: $id})<-[:atlassian_user_assigned_jira_work_item]-(issue:JiraWorkItem) RETURN collect(issue) AS issues",
  "params": {
    "id": "user_ari"
  }
}

Example - less optimal:

1
2
{
  "cypherQuery": "MATCH (:IdentityUser {ari: 'user_ari'})<-[:atlassian_user_assigned_jira_work_item]-(issue:JiraWorkItem) RETURN collect(issue) AS issues"
}

Putting it all into practice

Endpoints

To query the Teamwork Graph API, use the GraphQL endpoint for your Atlassian site. Replace yourinstancename with your site's name:

1
2
https://yourinstancename.atlassian.net/gateway/api/graphql/twg

For example, if your site is abc.atlassian.net, the endpoint would be:

1
2
https://abc.atlassian.net/gateway/api/graphql/twg

Query structure

1
2
query companyName_userTeams($cypherQuery: String!, $params: CypherRequestParams) {
  cypherQuery(query: $cypherQuery, params: $params) {
    edges {
      node {
        columns {
          key
          value {
            __typename
            ... on CypherQueryResultListNode {
              nodes {
                id
                data {
                  __typename
                  ... on Team { # Adjust fragment spread depending on hydrated fields
                    id
                    displayName
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Parameters:

  • $cypherQuery (String!): The Cypher query string.
  • $params (CypherRequestParams): A JSON-like object containing parameters for the Cypher query. Use this to safely inject dynamic values into your query using $paramName syntax.

Example variables

1
2
{
  "cypherQuery": "MATCH (user:IdentityUser {ari: $id})-[:atlassian_user_is_in_atlassian_team]->(team:IdentityTeam) RETURN collect(distinct team) as teams",
  "params": {
    "id": "ari:cloud:identity::user/712020:5fb4febcfacfd60076a1c699"
  }
}

Next steps

Ready to start querying Teamwork Graph?


Call the Teamwork Graph API

Rate this page: