GraphQL API

Pro Feature

The GraphQL API is a read-only API for delivering content from Statamic to your frontend, external apps, SPAs, and numerous other possible sources. Content is delivered as JSON data.

(If you're interested in a REST API, we have one of those too.)

Enable GraphQL

Enable the GraphQL API in your config or with an environment variable.

// config/statamic/graphql.php
'enabled' => true,
STATAMIC_GRAPHQL_ENABLED=true
Hot Tip!

If you publish the underlying package's config, the query routes will be enabled regardless of whether you've disabled it in the Statamic config.

You will also need to enable the resources you want to be available. For security, they're all disabled by default.

// config/statamic/graphql.php
 
'resources' => [
'collections' => true,
'taxonomies' => true,
// etc
]

Interfaces

Statamic will provide "interface" types, which describe more generic items. For instance, an EntryInterface exists for all
entries, which would provide fields like id, slug, status, title, and so on.

In addition to the interfaces, Statamic will provide implementations of them, which would come from the blueprints.

For example, if you had a collection named pages, and it had blueprints of page and home, you would find Entry_Pages_Page
and Entry_Pages_Home types. These implementations would provide fields specific to the blueprint, like subtitle, content, etc.

{
entries {
id
title
... on Entry_Pages_Page {
subtitle
content
}
... on Entry_Pages_Home {
hero_intro
hero_image
}
}
}

Queries

Statamic has a number of root level queries you can perform to get data.

You can read about the available queries further down the page,
but know that you can perform more than one query at a time. They just need to be at the top level of your GraphQL query body.

For example, the following would perform both entries and collections queries

{
entries {
# ...
}
collections {
# ...
}
}

The response will contain the results of both queries:

{
"entries": { /* ... */ },
"collections": { /* ... */ },
}

Note that you can even perform the same query multiple times. If you want to do this, you should use aliases:

{
home: entry(id: "home") {
title
}
contact: entry(id: "contact") {
title
}
}
{
"home": { /* ... */ },
"contact": { /* ... */ },
}

Available Queries

Ping

Used for testing that your connection works. If you send a query of {ping}, you should receive {"data": {"ping": "pong"}}.

{
ping
}
{
"data": {
"ping": "pong"
}
}

Collections

Used for querying collections.

Returns a list of Collection types.

{
collections {
handle
title
}
}
{
"collections": [
{ "handle": "blog", "title": "Blog Posts" },
{ "handle": "events", "title": "Events" },
]
}

Collection

Used for querying a single collection.

Returns a Collection type.

{
collection(handle: "blog") {
handle
title
}
}
{
"collections": {
"handle": "blog",
"title": "Blog Posts"
}
}

Entries

Used for querying multiple entries.

Returns a paginated list of EntryInterface types.

Argument Type Description
collection [String] Narrows down the results by entries in one or more collections.
limit Int The number of results to be shown per paginated page.
page Int The paginated page to be shown. Defaults to 1.
filter JsonArgument Narrows down the results based on filters.
sort [String] Sorts the results based on one or more fields and directions.

Example query and response:

{
entries {
current_page
data {
id
title
}
}
}
{
"entries": {
"current_page": 1,
"data": [
{ "id": 1, "title": "First Entry" },
{ "id": 2, "title": "Second Entry" }
]
}
}

Entry

Used for querying a single entry.

{
entry(id: 1) {
id
title
}
}
{
"entry": {
"id": 1,
"title": "First Entry"
}
}

Asset Containers

Used for querying asset containers.

{
assetContainers {
handle
title
}
}
{
"assetContainers": [
{ "handle": "images", "title": "Images" },
{ "handle": "documents", "title": "Documents" },
]
}

Asset Container

Used for querying a single asset container.

Returns an AssetContainer type.

{
assetContainer(handle: "images") {
handle
title
}
}
{
"assetContainer": {
"handle": "images",
"title": "Images"
}
}
Argument Type Description
handle String! Specifies which asset container to retrieve.

Assets

Used for querying multiple assets of an asset container.

Returns a paginated list of AssetInterface types.

Argument Type Description
container String! Specifies which asset container to query.
limit Int The number of results to be shown per paginated page.
page Int The paginated page to be shown. Defaults to 1.
sort [String] Sorts the results based on one or more fields and directions.

Example query and response:

{
assets(container: "images") {
current_page
data {
url
}
}
}
{
"entries": {
"current_page": 1,
"data": [
{ "url": "/assets/images/001.jpg" },
{ "url": "/assets/images/002.jpg" },
]
}
}

Asset

Used for querying a single asset.

{
asset(id: 1) {
id
title
}
}
{
"asset": {
"id": 1,
"title": "First Entry"
}
}

You can either query by id, or by container and path together.

Argument Type Description
id String The ID of the asset. If you use this, you don't need container or path.
container String The container to look for the asset. You must also provide the path.
path String The path to the asset, relative to the container. You must also provide the container.

Taxonomies

Used for querying taxonomies.

{
taxonomies {
handle
title
}
}
{
"taxonomies": [
{ "handle": "tags", "title": "Tags" },
{ "handle": "categories", "title": "Categories" },
]
}

Taxonomy

Used for querying a single taxonomy.

{
taxonomy(handle: "tags") {
handle
title
}
}
{
"taxonomy": {
"handle": "tags",
"title": "Tags"
}
}

Terms

Used for querying multiple taxonomy terms.

Returns a paginated list of TermInterface types.

Argument Type Description
taxonomy [String] Narrows down the results by terms in one or more taxonomies.
limit Int The number of results to be shown per paginated page.
page Int The paginated page to be shown. Defaults to 1.
filter JsonArgument Narrows down the results based on filters.
sort [String] Sorts the results based on one or more fields and directions.

Example query and response:

{
terms {
current_page
data {
id
title
}
}
}
{
"terms": {
"current_page": 1,
"data": [
{ "id": "tags::one", "title": "Tag One" },
{ "id": "tags::two", "title": "Tag Two" }
]
}
}

Term

Used for querying a single taxonomy term.

{
term(id: "tags::one") {
id
title
}
}
{
"term": {
"id": "tags::one",
"title": "Tag One""
}
}

Global Sets

Used for querying multiple global sets.

Returns a list of GlobalSetInterface types.

Argument Type Description
taxonomy [String] Narrows down the results by terms in one or more taxonomies.
limit Int The number of results to be shown per paginated page.
page Int The paginated page to be shown. Defaults to 1.
filter JsonArgument Narrows down the results based on filters.
sort [String] Sorts the results based on one or more fields and directions.

Example query and response:

{
globalSets {
title
handle
... on GlobalSet_Social {
twitter
}
... on GlobalSet_Company {
company_name
}
}
}
{
"globalSets": [
{ "handle": "social", "twitter": "@statamic" },
{ "handle": "company", "company_name": "Statamic" },
]
}

Global Set

Used for querying a single global set.

{
globalSet(handle: "social") {
title
handle
... on GlobalSet_Social {
twitter
}
}
}
{
"globalSet": {
"title": "Social",
"handle": "social",
"twitter": "@statamic",
}
}

Used for querying Navs.

{
navs {
handle
title
}
}
{
"navs": [
{ "handle": "header_links", "title": "Header Links" },
{ "handle": "footer_links", "title": "Footer Links" },
]
}

Used for querying a single Nav.

{
nav(handle: "footer") {
handle
title
}
}
{
"nav": {
"handle": "footer",
"title": "Footer Links"
}
}

Custom Queries

Here's an example of a basic query class. It has the name attribute which is the key the user needs to put in the request, any number of middleware, the type(s) that will be returned, any arguments, and how the data should be resolved.

use Statamic\Facades\GraphQL;
use Statamic\GraphQL\Queries\Query;
 
class Products extends Query
{
protected $attributes = [
'name' => 'products',
];
 
protected $middleware = [
MyMiddleware::class,
];
 
public function type(): Type
{
return GraphQL::paginate(GraphQL::type(ProductType::NAME));
}
 
public function args(): array
{
return [
'limit' => GraphQL::int(),
];
}
 
public function resolve($root, $args)
{
return Product::paginate($args['limit']);
}
}
{
products {
name
price
}
}

You may add your own queries to Statamic's default schema.

You can add them to the config file, which makes sense for app specific queries:

// config/statamic/graphql.php
'queries' => [
MyCustomQuery::class
]

Or, you may use the addQuery method on the facade, which would be useful for addons.

GraphQL::addQuery(MyCustomQuery::class);

Types

EntryInterface

Field Type Description
id ID!
title String!

Each EntryInterface will also have implementations for each collection/blueprint combination.

You will need to query the implementations using fragments in order to get blueprint-specific fields.

{
entries {
id
title
... on Entry_Blog_Post {
intro
content
}
... on Entry_Blog_ArtDirected_Post {
hero_image
content
}
}
}

The fieldtypes will define their types. For instance, a text field will be a String, a grid field will expose a list of GridItem types.

Collection

Field Type Description
handle String!
title String!
structure CollectionStructure If the collection is structured (e.g. a "pages" collection), you can use this to query its tree.

CollectionStructure

Field Type Description
handle String!
title String!
tree [CollectionTreeBranch] A list of tree branches.

CollectionTreeBranch

Represents a branch within a structured collection's tree.

Field Type Description
depth Int! The nesting level of the current branch.
entry (or page) EntryInterface Contains the entry's fields.
children [CollectionTreeBranch] A list of tree branches.
Hot Tip!

It's not possible to perform recursive queries in GraphQL. If you want to retrieve multiple levels of child branches, take a look at a workaround in recursive tree branches below.

Represents a branch within a nav's tree.

Field Type Description
depth Int! The nesting level of the current branch.
page PageInterface Contains the page's fields.
children [NavTreeBranch] A list of tree branches.
Hot Tip!

It's not possible to perform recursive queries in GraphQL. If you want to retrieve multiple levels of child branches, take a look at a workaround in recursive tree branches below.

PageInterface

A "page" within a nav's tree.

Field Type Description
id ID! The ID of the page.
entry_id ID The entry ID.
title String For entry pages, it's the entry's title unless overridden on the branch. For basic pages, it's the title.
url String For entry pages, it's the entry's url. For basic pages, it's the url. For text-only pages it'll be null.
permalink String The absolute version of url.

If you want to query any fields that you've added to the nav's blueprint, you have 4 different options available to you that you can use as inline fragments.
You can use more than one at a time:

  • EntryInterface for all entry pages.
  • NavEntryPage_{NavHandle}_{Collection}_{Blueprint} for a specific entry/blueprint combination on entry pages.
  • NavBasicPage_{NavHandle} for basic non-entry pages.
  • NavPage_{NavHandle} for either basic or entry pages.
page {
title
url
... on EntryInterface {
# ...
}
... on NavPage_HeaderLinks {
# ...
}
... on NavBasicPage_HeaderLinks {
# ...
}
... on NavEntryPage_HeaderLinks_Blog_ArtDirected {
# ...
}
}

TermInterface

Field Type Description
id ID!
title String!
slug String!

Each TermInterface will also have implementations for each taxonomy/blueprint combination.

You will need to query the implementations using fragments in order to get blueprint-specific fields.

{
terms {
id
title
... on Term_Tags_RegularTag {
content
}
... on Term_Tags_SpecialTag {
how_special
content
}
}
}

The fieldtypes will define their types. For instance, a text field will be a String, a grid field will expose a list of GridItem types.

AssetInterface

Field Type Description
path String! The path to the asset.

Each AssetInterface will also have an implementation for each asset container's blueprint.

You will need to query the implementations using fragments in order to get blueprint-specific fields.

{
entries {
path
... on Asset_Images {
alt
}
}
}

The fieldtypes will define their types. For instance, a text field will be a String, a grid field will expose a list of GridItem types.

GlobalSetInterface

Field Type Description
handle String! The handle of the set.
title String! The title of the set.

Each GlobalSetInterface will also have an implementation for each set's blueprint.

Hot Tip!

While Statamic doesn't enforce a blueprint for globals (see Blueprint is Optional), it is required within the GraphQL context. Fields that haven't been explicitly added to a blueprint will not be available.

You will need to query the implementations using fragments in order to get blueprint-specific fields.

{
globalSets {
handle
... on GlobalSet_Social {
twitter
}
}
}

The fieldtypes will define their types. For instance, a text field will be a String, a grid field will expose a list of GridItem types.

Code

Field Type Description
code String! The actual code value.
mode String! The language "mode".

The code fieldtype will return this type when mode_selectable is enabled. Otherwise, it'll just be a string.

{
snippet {
code
mode
}
}

Filtering

You can filter the results of listing queries (like entries) using the filter argument. This argument accepts a JSON object containing different
conditions.

{
entries(filter: {
title: { contains: "rad", ends_with: "!" }
}) {
data {
title
}
}
}
{
"data": [
{ "title": "That was so rad!" },
{ "title": "I wish I was as cool as Daniel Radcliffe!" },
]
}

If you only need to do a simple "equals" condition, then you can use a string and omit the condition name, like the rating here:

{
entries(filter: {
title: { contains: "rad" }
rating: 5
}) {
# ...
}

If you need to use the same condition on the same field more than once, you can use the array syntax:

{
entries(filter: {
title: [
{ contains: "rad" },
{ contains: "awesome" },
]
}) {
# ...
}

Sorting

You can sort the results of listing queries (like entries) on one or multiple fields, in any direction.

{
entries(sort: "title") {
# ...
}
{
entries(sort: "title desc") {
# ...
}
{
entries(sort: ["price desc", "title asc"]) {
# ...
}

Pagination

Some queries (like entries) will provide their results using pagination.

In a paginated response, you will find the actual items within a data key.

By default there will be 1000 per page. You can change this using a limit argument.
You can specify the current paginated page using the page argument.

{
entries(limit: 15, page: 2) {
current_page
has_more_pages
data {
# ...
}
}
}
Field Type Description
data [mixed] A list of items on the current page. In an entries query, there will be EntryInterface types, etc.
total Int! Number of total items selected by the query.
per_page Int! Number of items returned per page.
current_page Int! Current page of the cursor.
from Int Number of the first item returned.
to Int Number of the last item returned.
last_page Int! The last page (number of pages).
has_more_pages Boolean! Determines if cursor has more pages after the current page.

Fieldtypes

Replicator

Replicator fields require that you query each set using a separate fragment.

The fragments are named after your configured sets using StudlyCased field and set handles. e.g. Set_{ReplicatorFieldName}_{SetHandle}

fields:
-
handle: content_blocks
field:
type: replicator
sets:
image:
fields:
-
handle: image
type: assets
max_files: 1
pull_quote:
fields:
-
handle: quote
field:
type: textarea
-
handle: author
field:
type: text
{
content_blocks {
... on Set_ContentBlocks_Image {
type
image
}
... on Set_ContentBlocks_PullQuote {
type
quote
author
}
}
}
Hot Tip!

If you have nested fields, include each parent's handle, (and grandparent's, great grandparent's etc), like so: Set_TopLevelReplicator_NestedReplicator_DeeplyNestedReplicator_SetHandle

Bard

Bard fields work the same as Replicator, except that you also have an additional BardText for the text fragment.

{
content_blocks {
... on BardText {
type
text
}
... on Set_ContentBlocks_Image {
type
image
}
... on Set_ContentBlocks_PullQuote {
type
quote
author
}
}
}

Grid

Grid fields can be queried with no extra requirements. You can just use the nested field handles.

{
cars {
make
model
}
}

Select, Radio, Checkboxes, and Button Group

These fieldtypes provide you with labels and values. You'll need to use a sub selection.

my_select_field {
value
label
}
"my_single_select_field": {
"value": "potato",
"label": "Potato"
}

The same syntax is used when multiple values are expected. e.g. a select field with multiple values enabled, or a checkboxes field. You'll just get a nested array returned.

"my_multi_select_field": [
{
"value": "potato",
"label": "Potato"
},
{
"value": "tomato",
"label": "Tomato",
}
]

Recursive Tree Branches

Often, when dealing with navs, you need to recursively output all the child branches. For example, when using the nav tag in Antlers, you might do something like this:

<ul>
{{ nav }}
<li>
<a href="{{ url }}">{{ title }}</a>
{{ if children }}
<ul>{{ *recursive children* }}</ul>
{{ /if }}
</li>
{{ /nav }}
</ul>

In GraphQL, it's not possible to perform recursive queries like that. You'll need to explicitly query each level:

{
nav(handle: "links") {
tree {
page {
title
url
}
children {
page {
title
url
}
children {
page {
title
url
}
}
}
}
}
}

In this example, if you wanted anything more than title and url, you'd need to add them to each level.

This can quickly become tedious and is very repetitive, so here's a workaround using fragments.

If you wanted to add more fields, you only need to do it one spot - the Fields fragment. If you want to query more levels, you can just increase the nesting level of the RecursiveChildren fragment.

{
nav(handle: "links") {
tree {
...Fields
...RecursiveChildren
}
}
}
 
fragment Fields on NavTreeBranch {
depth
page {
title
url
# any other fields you want for each branch
}
}
 
fragment RecursiveChildren on NavTreeBranch {
children {
...Fields
children {
...Fields
children {
...Fields
# just keep repeating this as deep as necessary
}
}
}
}

Hat tip to Hash Interactive for their blog post on this technique.

Custom Fieldtypes

A fieldtype can define what GraphQL type will be used. By default, all fieldtypes will return strings.

use GraphQL\Type\Definition\Type;
 
public function toGqlType()
{
return GraphQL::string();
}

You're free to return an array with a more complicated structure in order to provide arguments, etc.

use GraphQL\Type\Definition\Type;
 
public function toGqlType()
{
return [
'type' => GraphQL::string(),
'args' => [
//
]
];
}

If you need to register any types, the fieldtype can do that in the addGqlTypes method:

public function addGqlTypes()
{
// A class that extends Rebing\GraphQL\Support\Type
$type = MyType::class; // or `new MyType;`
 
GraphQL::addType($type);
}

Laravel Package

Under the hood, Statamic uses the rebing/graphql-laravel package.

By default, the integration should feel seamless and you won't even know another package is being used. Statamic will
perform the following automatic configuration of this package:

  • Setting up the default schema to Statamic's.
  • Disabling the /graphiql route (since we have our own inside the Control Panel)

However, you're free to use this package on its own, as if you've installed it into a standalone Laravel application.

If Statamic detects that you've published the package's config file (located at config/graphql.php), it will assume you're trying to use it manually and will
avoid doing the automatic setup steps mentioned above.

If you'd like to use Statamic's GraphQL schema within the config file (maybe you want a different default, and want Statamic's one at /graphql/statamic)
you can use the DefaultSchema class.

[
'schemas' => [
'statamic' => \Statamic\GraphQL\DefaultSchema::config()
]
]

Authorization

By default, all queries are allowed by anyone. We plan to add native features in the future.

You can define custom authorization logic for any query by providing a closure to the static auth method.

EntriesQuery::auth(function () {
return true; // true authorizes, false denies.
});

Custom Fields

You can add fields to certain types by using the addField method on the facade.

The method expects the type name, the field name, and a closure that return a GraphQL field definition array.

For example, if you wanted to include a thumbnail from an asset, you could do that here. You can even have arguments. In this example, we'll expect the width of the thumbnail to be passed in.

use GraphQL\Type\Definition\Type;
use Statamic\Facades\GraphQL;
use Statamic\Facades\Image;
use Statamic\Facades\URL;
 
GraphQL::addField('EntryInterface', 'thumbnail', function () {
return [
'type' => GraphQL::string(),
'args' => [
'width' => [
'type' => GraphQL::int(),
]
],
'resolve' => function ($entry, $args) {
$asset = $entry->augmentedValue('image')->value();
$url = Image::manipulate($asset)->width($args['width'])->build();
return URL::makeAbsolute($url);
}
];
});
{
entry(id: 1) {
thumbnail(width: 100)
}
}
 
{
"entry": {
"thumbnail": "http://yoursite.com/img/asset/abc123?w=100"
}
}

The closure you pass to the method should return a GraphQL field definition array.

You may add custom fields to the following types and any of their implementations:

  • EntryInterface
  • PageInterface
  • TermInterface
  • AssetInterface
  • GlobalSetInterface

Caching

GraphQL uses a basic whole-response cache by default. Each query/variables combination's response will be cached for an hour. You may customize the cache expiry in config/statamic/graphql.php.

'cache' => [
'expiry' => 60,
],

Cache Invalidation

Cached responses are automatically invalidated when content is changed. Depending on your GraphQL usage and blueprint schema, you may also wish to ignore specific events when invalidating.

'cache' => [
'expiry' => 60,
'ignored_events' => [
\Statamic\Events\UserSaved::class,
\Statamic\Events\UserDeleted::class,
],
],

Disabling Caching

If you wish to disable caching altogether, set cache to false.

'cache' => false,

Custom Middleware

You may add custom middleware, which is almost identical to a standard Laravel middleware class.

Use the handle method to perform some action, and pass the request on.

use Closure;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Middleware;
 
class MyMiddleware extends Middleware
{
public function handle($root, $args, $context, ResolveInfo $info, Closure $next)
{
// do something
 
return $next($root, $args, $context, $info);
}
}

You may add your own middleware to Statamic's default schema.

You can add them to the config file, which makes sense for app specific middleware:

// config/statamic/graphql.php
'middleware' => [
MyMiddleware::class
]

Or, you may use the addMiddleware method on the facade, which would be useful for addons.

GraphQL::addMiddleware(MyMiddleware::class);

Troubleshooting

"Cannot query field" error"

If you see an error like Cannot query field "entries" on type "Query", this likely means you haven't enabled that query. See Enable GraphQL.
After enabling it, you may need to clear your cache as the request would probably have been cached.

Docs feedback

Submit improvements, related content, or suggestions through Github.

Betterify this page →