Skip to main content

Queries

Queries in GraphQL are used to fetch data without modifying it.

Retrieve the list of contacts​

List queries are paginated. Always pass first explicitly: the default page size is not the same on every field, so leaving it out gives you a size you did not choose (contacts returns 50, which is also its maximum).

query Contacts($first: Int!) {
contacts(first: $first) {
edges {
node {
id
name
lastName
displayName
}
}
pageInfo {
hasNextPage
endCursor
total
}
}
}
Query variables
{
"first": 25
}

The higher first is, the more the query costs. See query cost and pagination.

Retrieve the basic data of a single contact​

query Contact($id: ID!) {
contact(id: $id) {
id
name
lastName
displayName
}
}
Query variables
{
"id": "123"
}

Narrowing down a list​

Fetching a page and filtering it in your own code wastes requests and query budget. List queries take a common set of arguments for doing the work on the server instead. Most lists accept all of them; check the API Reference for the exact set on a given field.

ArgumentUse it to
searchMatch a free-text term across the record's searchable fields.
whereFilter on a column with an operator.
whereHasFilter on a related record.
orderBySort the results.
trashedInclude or isolate deleted records.

The simplest filter: a free-text term, matched the way the application's own search does.

query SearchContacts($term: String!) {
contacts(first: 25, search: $term) {
edges {
node {
id
displayName
}
}
}
}
Query variables
{
"term": "acme"
}

where​

where filters on a specific column. Each condition is a column, an operator and a value:

query Clients {
contacts(
first: 25
where: { column: IS_CLIENT, operator: EQ, value: true }
) {
edges {
node {
id
displayName
}
}
}
}

Columns come from a per-field enum (ContactsWhereColumns here), so only columns that are actually filterable are accepted. Operators include EQ, NEQ, GT, GTE, LT, LTE, LIKE, NOT_LIKE, IN and others - see SQLOperator in the reference.

Combine conditions with AND and OR:

query RecentClients($since: DateTimeTz!) {
contacts(
first: 25
where: {
AND: [
{ column: IS_CLIENT, operator: EQ, value: true }
{ column: CREATED_AT, operator: GTE, value: $since }
]
}
) {
edges {
node {
id
displayName
createdAt
}
}
}
}

whereHas​

Where where filters on the record's own columns, whereHas filters on its relations. Give it a relation, the IDs you care about, and how they should match:

query TaggedContacts($tagIds: [ID!]) {
contacts(
first: 25
whereHas: [{ relation: TAGS, mode: ANY, ids: $tagIds }]
) {
edges {
node {
id
displayName
}
}
}
}

mode decides how the IDs are applied:

  • ANY - the relation includes at least one of the given IDs.
  • ALL - the relation includes every one of them.
  • NONE - the relation is absent entirely, and ids is ignored.

Passing several entries in the array narrows the result further; they are combined with AND.

orderBy​

orderBy takes a list of clauses, applied in order:

query SortedContacts {
contacts(
first: 25
orderBy: [{ column: NAME, order: ASC }]
) {
edges {
node {
id
displayName
}
}
}
}

Sorting by more than one column is a matter of adding clauses; the first is the primary sort.

trashed​

Deleting a record is reversible, so deleted records still exist. By default a list leaves them out. trashed changes that:

  • WITHOUT - only records that are not deleted. This is the default.
  • WITH - deleted and undeleted records together.
  • ONLY - only deleted records.
query DeletedContacts {
contacts(first: 25, trashed: ONLY) {
edges {
node {
id
displayName
deletedAt
}
}
}
}

ONLY is the query to run when you need to find something in order to restore it.

Asking for the same field twice​

If you need one field under two different sets of arguments, give each an alias. Without one they would collide, because the response is keyed by field name:

query ContactSplit {
clients: contacts(first: 10, where: { column: IS_CLIENT, operator: EQ, value: true }) {
edges {
node {
id
displayName
}
}
}
others: contacts(first: 10, where: { column: IS_CLIENT, operator: EQ, value: false }) {
edges {
node {
id
displayName
}
}
}
}

The response then carries data.clients and data.others separately.

Reusing a selection​

When the same selection appears more than once, a fragment saves repeating it and keeps the two copies from drifting apart:

fragment ContactFields on Contact {
id
name
lastName
displayName
}

query ContactSplit {
clients: contacts(first: 10, where: { column: IS_CLIENT, operator: EQ, value: true }) {
edges {
node {
...ContactFields
}
}
}
others: contacts(first: 10, where: { column: IS_CLIENT, operator: EQ, value: false }) {
edges {
node {
...ContactFields
}
}
}
}

Fragments do not reduce query cost - the fields still count once per place the fragment is used - but they do keep a long document readable and under the size limit.