The Content API is dynamically generated based on your data model. Because of this, queries, filters, and mutations will be different per project.

Queries

Queries are automatically generated based on your projects data model. When performing a query, you can define a set of fields to return with the response, including any relational data.

Depending on the query type, there will be a range of available arguments, these typically include the ability to filter, sort, limit or perform aggregate counts on your data.

Query a single node

GraphCMS will automatically generate a query to fetch one specific node of each of your project models. To query the node, you will need to provide id or any other unique field, as defined in the data model.

For example, if we wanted to find a specific Post by id, we would provide this to the where argument.

query {
  post(where: { id: "cixnen2vv33lo0143bdwvr52n" }) {
    title
    slug
  }
}

In the example above where slug is set to unique, we could query for the Post by the unique slug field.

query {
  post(where: { slug: "my-post-slug" }) {
    title
    slug
  }
}

Query multiple nodes

Similar to querying a single node, GraphCMS will also automatically generate queries to fetch all nodes of models defined in your data model.

For example, if we wanted to get all nodes for our Post type, we can use the generated posts query:

query {
  posts {
    title
    slug
  }
}
The query name approximates the plural rules of the English language. You can check the generated query name from within the API Explorer.

Query arguments

All fields and queries returned from GraphCMS accept a range of query arguments, these typically allow you to;

  • Order by field
  • Filter by multiple fields
  • Paginate nodes

Order by field

When querying multiple nodes, you can provide the orderBy argument for every scalar field, either acending or descending, making sure it follows the format of <field>_ASC or <field>_DESC.

If we wanted to order our Post model nodes by title ascending, you would do the following:

query {
  posts(orderBy: title_ASC) {
    title
  }
}

If we wanted the reverse order, the query looks like:

query {
  posts(orderBy: title_DESC) {
    title
  }
}

Query filtering

When querying all nodes of a type you can supply different parameters to the where argument to constrain the data in the response according to your requirements. The available options depend on the scalar and relational fields defined on the type in question.

Single filters

If you supply exactly one parameter to the where argument, the query response will only contain nodes that adhere to this constraint. Multiple filters can be combined using AND and/or OR.

Filter by value

The easiest way to filter a query response is by supplying a field value to filter by.

Query all Post nodes that are not yet published:

query {
  posts(where: { status: DRAFT }) {
    id
    title
    status
  }
}

Advanced filtering

Depending on the type of the field you want to filter by, you have access to different advanced criteria you can use to filter your query response.

Below is a list of the available filters for use with your data model:

MatchesBehavior
*_inOne of
*_ltLess than
*_gtGreater than
*_notNot this
*_lteLess than or equal to
*_gteGreater than or equal to
*_not_inNot one of
*_starts_withStarts with string
*_not_starts_withDoesn't start with string
*_ends_withEnds with string
*_not_ends_withDoesn't end with string
*_containsIncludes string
*_not_containsDoes not include string

For example, we can query all Post nodes whose title is in a given list of strings:

query {
  posts(where: { title_in: ["My biggest Adventure", "My latest Hobbies"] }) {
    id
    title
    status
  }
}

You have to supply a list as the <field>_in argument: title_in: ["My biggest Adventure", "My latest Hobbies"]

Relation filters

For to-one relations, you can define conditions on the related node by nesting the according argument in where.

Query all Post nodes where the author has the USER access role:

query {
  posts(where: { author: { accessRole: USER } }) {
    title
  }
}

For to-many relations, three additional arguments are available: every, some and none, to define that a condition should match every, some or none related nodes.

Query all User nodes that have at least one Post node that's published:

query {
  users(where: { posts_some: { status: PUBLISHED } }) {
    id
    posts {
      status
    }
  }
}

Relation filters are also available in the nested arguments for to-one or to-many relations.

Query all User nodes that did not like a Post of an author in the ADMIN access role:

query {
  users(where: { likedPosts_none: { author: { accessRole: ADMIN } } }) {
    name
  }
}

likedPosts is not part of the above mentioned data model but can easily be added by adding the corresponding field to the User type: likedPosts: [Post!]! @relation(name: "LikedPosts").

Note that we also provide a name for the relation to resolve the ambiguity we would otherwise create because there are two relation fields targeting Post on the User type.

Combining multiple filters

You can use the filter combinators OR and AND to create an arbitrary logical combination of filter conditions.

Using OR or AND

Let's start with an easy example:

Query all Post nodes that are published and whose title is in a given list of strings:

query {
  posts(
    where: {
      AND: [
        { title_in: ["My biggest Adventure", "My latest Hobbies"] }
        { status: PUBLISHED }
      ]
    }
  ) {
    id
    title
    status
  }
}

OR and AND accept a list as input where each list item is an object and therefore needs to be wrapped with {}, for example:

AND: [{title_in: ["My biggest Adventure", "My latest Hobbies"]}, {status: PUBLISHED}]

Arbitrary combination of filters with AND and OR

You can combine and even nest the filter combinators AND and OR to create arbitrary logical combinations of filter conditions.

Query all Post nodes that are either published and whose title is in a list of given strings, or have the specific id we supply:

query($published: Status) {
  posts(
    where: {
      OR: [
        {
          AND: [
            { title_in: ["My biggest Adventure", "My latest Hobbies"] }
            { status: $published }
          ]
        }
        { id: "cixnen24p33lo0143bexvr52n" }
      ]
    }
  ) {
    id
    title
    status
  }
}

Notice how we nested the AND combinator inside the OR combinator, on the same level with the id value filter.

Apart from the filter combinators AND and OR, the available filter arguments for a query for all nodes of a type depend on the fields of the type and their types. Use the API Explorer to explore available filter conditions.

Query pagination

When querying all nodes of a specific type, you can supply arguments that allow you to paginate the query response.

Pagination allows you to request a certain amount of nodes at the same time. You can seek forwards or backwards through the nodes and supply an optional starting node:

  • to seek forwards, use first; specify a starting node with after.
  • to seek backwards, use last; specify a starting node with before.

You can also skip an arbitrary amount of nodes in whichever direction you are seeking by supplying the skip argument.

Consider a blog where only 3 Post nodes are shown at the front page. To query the first page:

query {
  posts(first: 3) {
    id
    title
  }
}

To query the first two Post nodes after the first Post node:

query {
  posts(first: 2, after: "cixnen24p33lo0143bexvr52n") {
    id
    title
  }
}

We could reach the same result by combining first and skip:

query {
  posts(first: 2, skip: 1) {
    id
    title
  }
}

Query the last 2 posts:

query {
  posts(last: 2) {
    id
    title
  }
}

You cannot combine first with before or last with after. You can also query for more nodes than exist without an error message.

Querying localized content

Localized content requires special handling from the API. There are two methods for requesting localized content. When nothing is passed in, the default language is returned. Feel free to query along with the following API. https://api-euwest.graphcms.com/v1/cjr6hhs897w5p01eqpai0kkqa/master

Passing in a query argument

You can pass a locale argument to a field on a model. See the example below.

{
  robots {
    greeting(locale: RB)
  }
}

Requesting Multiple Languages

Sometimes you will want to access all the languages, and in those cases you can simply ask for the data explicitly.

{
  # Using Aliases
  rb: robots {
    greeting(locale: RB)
  }
  en: robots {
    greeting(locale: EN)
  }
}

Passing a header flag

You can query for the content by passing in a gcms-locale header. This header takes a comma separated list of locales. If the content of that first specified locale in the list would return null, the next locale will be checked and so on until one of these contains content. If all of the specified locales return null, the default locale will be checked.

To disbale that default value check, you can use the gcms-locale-no-default header. This boolean header is set to false as default. To enable it, pass gcms-locale-no-default: true.

Here's an example of setting a header with the web standard, fetch.

fetch('https://api-euwest.graphcms.com/v1/cjr6hhs897w5p01eqpai0kkqa/master', {
  method: 'post',
  headers: {
    'gcms-locale': 'RB, DE, EN',
    'gcms-locale-no-default': true,
    'Content-Type': 'application/json',
  },
  body: '{"query": "{ robots { greeting } }"}',
})
  .then(res => res.json())
  .then(data => {
    console.log(data);
  })
  .catch(function(error) {
    console.log(error);
  });

Mutations

A GraphQL mutation is used to modify data. GraphCMS will automatically generate mutations based on your data model, but will always generate simple, nested and batch mutations

  • Type mutations: Create, Update, Upsert and Delete nodes of a certain object type
  • Nested mutations: Connect, Disconnect, Create, Update and Upsert nodes across relations
  • Batch mutations: Update and Delete many nodes of a certain model

Depending on the data model you defined inside of GraphCMS, the mutations and filters will vary, but follow the pattern below.

We will use the Hotel primitive for the mutation examples below.

Type mutations

Creating nodes

When creating new nodes, the data will have an associated input type. In the case of our Hotel model, the HotelCreateInput input type will automatically be created.

mutation {
    createHotel(data: HotelCreateInput!): Hotel!
}

All required fields without a default value need to be provided in the data payload.

Updating nodes

When updating existing nodes, the generated mutation requires two payloads, a data payload for the fields that need to be updated and the where payload which consists of all unique fields.

The data and where arguments require payloads matching the HotelUpdateInput and HotelWhereUniqueInput input types respectively.

mutation {
    updateHotel(data: HotelUpdateInput!, where: HotelWhereUniqueInput!): Hotel
}

Upserting nodes

For creating a node or updating a node if the ID exists. This takes three payloads, where, create, update.

These behave similar to switch statements. Provide the unique input in the where clause, such as an email address. Provide the data to create in case the email is not found, alternatively provide the data to update if the email is found.

mutation {
    upsertHotel(
        where: HotelWhereUniqueInput!
        create: HotelCreateInput!
        update: HotelUpdateInput!
    ): Hotel!
}

The create and update have the same input type as those in createHotel and updateHotel mutations.

Deleting nodes

Similar to updating and upserting nodes, we can select a specific node to delete using the where payload using the automatically generated deleteHotel mutation.

mutation {
    deleteHotel(where: HotelWhereUniqueInput!): Hotel
}

Nested mutations

The automatically generated nested mutation arguments below can be used to modify nodes across relations at the same time.

  • create
  • update
  • upsert
  • delete
  • connect
  • disconnect

Not all of the above arguments exist on every mutation type. Below are the conditions for using the above arguments;

Type of parent mutation

  • create mutation
  • update mutation
  • upsert mutation

Type of relation

  • Optional to-one relation

  • Required to-one relation

  • to-many relation

  • The create mutation only exposes nested create and connect mutations.

  • The update mutation exposes update and upsert mutations for a required to-one relation.

Nested mutations are executed transactionally.

We can use the connect action within a nested input object field to connect to one or more related nodes.

In the example below, we will create a new Post node and connect it to an existing author via the unique email field.

mutation {
  createPost(
    data: {
      title: "This is a draft post"
      status: DRAFT
      author: { connect: { email: "zeus@example.com" } }
    }
  ) {
    id
    auhor {
      name
    }
  }
}

If we provide a create argument instead of connect within our author action, we could create a related author and at the same time, connect to it, instead of connecting to an existing author.

When creating a User instead of a Post, we can create and connect to multiple Post nodes at the same time. This is because User has a to-many relation to Post.

In the example below, we are creating a new User and connecting it to several new and existing Post nodes:

mutation {
  createUser(
    data: {
      name: "Zeus"
      email: "zeus3@example.com"
      age: 42
      posts: {
        create: [
          { status: PUBLISHED, title: "My first blog post" }
          { status: PUBLISHED, title: "My second blog post" }
        ]
        connect: [
          { id: "cjcdi63j80adw0146z7r59bn5" }
          { id: "cjcdi63l80ady014658ud1u02" }
        ]
      }
    }
  ) {
    id
    posts {
      id
    }
  }
}

It's recommended you explore the API explorer inside the Dashboard to explore the full nested mutations and mutation arguments with your data model.

When updating nodes, you can update one or more related nodes at the same time.

mutation {
  updateUser(
    where: { id: "cjcf1cj0c017y01461c6enbfe" }
    data: {
      posts: {
        update: [
          {
            where: { id: "cjcf1cj0r017z014605713ym0" }
            data: { title: "My awesome blog post" }
          }
        ]
      }
    }
  ) {
    id
    posts {
      id
      title
    }
  }
}

It's useful to remember that update can receive multiple objects with where and data fields for the updatePost mutation to execute.

Below is an example of nested upserting:

mutation {
  updatePost(
    where: { id: "cjcf1cj0r017z014605713ym0" }
    data: {
      author: {
        upsert: {
          where: { id: "cjcf1cj0c017y01461c6enbfe" }
          update: { email: "zeus2@example.com", name: "Zeus2" }
          create: { email: "zeus@example.com", name: "Zeus" }
        }
      }
    }
  ) {
    id
    posts {
      id
      title
    }
  }
}

You can also delete one or more related nodes at the same time when updating nodes.

mutation {
  updateUser(
    where: { id: "cjcf1cj0c017y01461c6enbfe" }
    data: {
      posts: {
        delete: [
          { id: "cjcf1cj0u01800146jii8h8ch" }
          { id: "cjcf1cj0u01810146m84cnt34" }
        ]
      }
    }
  ) {
    id
    posts {
      id
      title
    }
  }
}

Scalar list mutations

When an object type has a field that is has a scalar list as its type, you can use the set operation to create or update values for the given list.

In the following data model, the User type has three such fields:

type User {
  id: ID! @unique
  scores: [Int!]!         # scalar list for integers
  friends: [String!]!     # scalar list for strings
  coinFlips: [Boolean!]!  # scalar list for booleans
}

Creating nodes

When creating a new node of type User, a list of values can be provided for each scalar list field using set:

mutation {
  createUser(data: {
    scores: { set: [1, 2, 3] }
    friends: { set: ["Sarah", "Jane"] }
    throws: { set: [false, false] }
  }) {
    id
  }
}

Updating nodes

When updating an existing Nodes scalar list fields, you can use set to override the existing list with new values.

Each scalar list field takes an object with a set field in an update-mutation. The value of that field is a single value or a list of the corresponding scalar type.

Examples

Set the scores of an existing User node to [1]:

mutation {
  updateUser(
    where: {
      id: "cjd4lfdyww0h00144zst9alur"
    }
    data: {
      scores: {
        set: 1
      }
    }
  ) {
    id
  }
}

Set the scores of an existing User node to [10,20,30]:

mutation {
  updateUser(
    where: {
      id: "cjd4lfdyww0h00144zst9alur"
    }
    data: {
      scores: {
        set: [10,20,30]
      }
    }
  ) {
    id
  }
}

Batch mutations

Batch mutations are used to update or delete many nodes at once and have their own unique input types generated. With our Hotel primitive, the input type HotelUpdateManyMutationInput will be generated, for use when sending the data payload.

Batch mutations will return a BatchPayload response type, containing the count of the affected nodes.

mutation {
    updateManyHotels(data: HotelUpdateManyMutationInput!, where: HotelWhereInput): BatchPayload!
}

We can also delete many nodes at once using the automatically generated deleteManyHotels mutation.

mutation {
    deleteManyHotels(where: HotelWhereInput): BatchPayload!
}

The mutations are generated automatically and follow this pattern, where our primitive type is Hotel:

createHotel(data: HotelCreateInput!): Hotel!

For creating new nodes. Data payload consists of all fields marked as required and any additional fields you want to provide when created.

updateHotel(data: HotelUpdateInput!where: HotelWhereUniqueInput!): Hotel

For updating existing nodes. This mutation requires two payloads, a data payload for the the fields that need to be replaced/updated and the where payload which consists of all unique fields. By convention, ID is always unique but one could have additional unique fields such as an e-maill address or telephone number. See the Examples below.

deleteHotel(where: HotelWhereUniqueInput!): Hotel

For deleting an existing node. Requires a where payload that contains all unique fields, see updateHotel above.

upsertHotel(
    where: HotelWhereUniqueInput!
    create: HotelCreateInput!
    update: HotelUpdateInput!
): Hotel!

For creating a node or updating a node if the ID exists. This takes three payloads, where, create, update. These behave a little like switch statements. Provide the unique input in the where clause, such as an e-mail address. Provide the data to create in case the e-mail is not found, alternatively provide the data to update if the e-amil is found.

updateManyHotels(data: HotelUpdateManyMutationInput!where: HotelWhereInput): BatchPayload!

Updating multiple nodes at once. Requires the data payload of what is to be updated and the where payload that consists of a filter logic for which fields are to be updated. See filters above.

deleteManyHotels(where: HotelWhereInput): BatchPayload!

Deleting many nodes at once. Requires the where payload that consists of a filter logic for which fields are to be deleted. See filters above.

Mutating Localized Content

The individual fields contain a localized prefix and mutation occurs by posting content into the relevant fields. Here's an example of creating a new Robot.

mutation {
  createRobot(
    data: { greetingEN: "Hello Humanoid!", greetingRB: "Bipp Bipp Bopp!" }
  ) {
    id
  }
}