Interacting with the GraphQL API
Interacting with the GraphQL APIWorking with Custom Posts

Working with Custom Posts

We use fields customPost and customPosts to fetch CPT data, both for CPTs that are mapped to the schema (such as Post and Page) or not (such as a CPT from some plugin). Because the results can include entities from different types, these fields return the CustomPostUnion type.

CustomPostUnion type

Custom Post fields in the schema

Gato GraphQL makes a clear distinction of when a custom post is a "custom post", and not directly a "post".

For instance, a comment can be added to a post, but also to a page and to a CPT, hence type Comment has field customPost: CustomPostUnion! to retrieve the entity where the comment was added, instead of field post: Post!.

Comment type

That's also why field customPosts receives argument customPostTypes instead of postTypes.

CPTs mapped to the schema

There are CPTs which have been mapped to the schema (such as Post and Page to represent CPTs "post" and "page"). In this case, the query will be resolved using the corresponding GraphQL type for that CPT.

When fetching results from a union type, we need to specify the fields to retrieve through fragments. These can be evaluated on interface CustomPost, which is implemented by all CPT types, or on each individual type, such as Post or Page.

In the query below, we fetch custom posts with CPTs "post" and "page". We display their fields through 3 fragments, which evaluate if the entity implements CustomPost, or is of type Post or Page:

query {
  customPosts(filter: { customPostTypes: ["post", "page"] }) {
    ...CustomPostProps
    ...PostProps
    ...PageProps
  }
}
 
fragment CustomPostProps on CustomPost {
  __typename
  title
  excerpt
  url
  dateStr(format: "d/m/Y")
}
 
fragment PostProps on Post {
  tags {
    id
    name
  }
}
 
fragment PageProps on Page {
  author {
    id
    name
  }
}

CPTs not mapped to the schema

When a CPT has not been mapped to the schema yet (such as "attachment", "revision" or "nav_menu_item", or any CPT installed by any plugin), we still use fields customPost and customPosts, and we must pass the corresponding CPT name under the filter.customPostTypes field arg.

Because their types do not exist in the schema, their data will be retrieved via type GenericCustomPost, which contains all the common properties to CPTs (title, content, excerpt, date, etc).

Generic Custom Post

In the query below, we fetch custom posts for a variety of CPTs:

query {
  customPosts(
    filter:{
      customPostTypes: [
        "page",
        "nav_menu_item",
        "wp_block",
        "wp_global_styles"
      ]
    }
  ) {
    ... on CustomPost {
      id
      title
      customPostType
      status
    }
    __typename
  }
}

Allowing access to Custom Post Types

CPTs must be explicitly allowed to be queryable, as explained in guide Allowing access to Custom Post Types.

Querying custom posts

The GraphQL types for CPTs which have been mapped to the schema (such as "post" => Post and "page" => Page) are incorporated directly into CustomPostUnion.

For any CPT that has not been modeled in the schema (such as "attachment", "revision" or "nav_menu_item", or any CPT installed by any plugin), their data will be accessed via the GenericCustomPost type.

We indicate the CPTs to retrieve via field argument filter.customPostTypes, which receives a list of strings, with the CPT names as defined in WordPress (such as "post", "page", etc). For instance:

query {
  customPosts(
    filter: { customPostTypes: ["some-custom-cpt"] }
  ) {
    ... on CustomPost {
      id
      title
    }
  }
}

This query retrieves entries from multiple CPTS:

query {
  customPosts(
    filter: {
      customPostTypes: [
        "post",
        "page",
        "attachment",
        "nav_menu_item",
        "custom_css",
        "revision"
      ],
      status: [
        publish,
        inherit,
        auto_draft
      ]
    }
  ) {
    id
    title
    content
    status
    customPostType
    __typename
  }
}

Because all Custom Posts implement interface CustomPost, we can retrieve data from CustomPostUnion using a fragment reference or an inline fragment:

query {
  comments {
    id
    date
    content
    customPost {
      __typename
      ...on CustomPost {
        id
        title
        url
      }
    }
  }
}

If we know that the comment was added to a post, we can also query fields specific to the Post:

query {
  comments {
    id
    date
    content
    customPost {
      __typename
      ...on CustomPost {
        id
        title
        url
      }
      ...on Post {
        categoryNames
      }
    }
  }
}

Filtering CPTs by a custom taxonomy

A custom post type can have custom taxonomies (tags and categories) associated to them. For instance, a CPT "product" may have associated the category taxonomy "product-cat" and the tag taxonomy "product-tag".

We can filter results by these associated taxonomies, via inputs tagTaxonomy and categoryTaxonomy in the filter input.

In the query below, we fetch custom posts filtering by category:

query {
  customPosts(
    filter: {
      categoryIDs: [26, 28],
      categoryTaxonomy: "product-cat"
    }
  ) {
    ... on CustomPost {
      id
      title
    }
    ... on GenericCustomPost {
      categories(taxonomy: "product-cat") {
        id
      }
    }
  }
}

Fetching custom CPT data

Using GenericCustomPost, we can only request those fields which are common to all CPTs; fetching custom data from some CPT is not supported (such as fetching the price data for a custom CPT "product").

To fetch custom CPT data, instead, we need to create the corresponding resolvers, in PHP code, to map the CPT to the schema:

  • Create a type Product
  • Attach a price field to it

Now, the CustomPostUnion type (returned by Root.customPosts) will resolve all entries from this CPT to a Product type.

query {
  customPosts(
    filter: {
      customPostTypes: "product"
    }
  ) {
    __typename
    ...on CustomPost { # interface implemented by all CPT types
      id
      title
      customPostType
      status
    }
    ...on Product { # custom CPT type
      price # custom field
    }
  }
}

We can additionally create field Root.products: [Product!], and use it directly:

query {
  products {
    __typename # Product
    id
    title
    status
    price # custom field
  }
}