(If you're interested in a REST API, we have one of those too.)
Enable GraphQL
To enable the GraphQL API, add the following to your .env
file:
STATAMIC_GRAPHQL_ENABLED=true
Or you can enable for all environments in config/statamic/graphql.php
:
'enabled' => true,
You will also need to enable the resources you want to be available. For security, they're all disabled by default.
When GraphQL is enabled, GraphiQL is available in the Control Panel. This allows you to explore and test available queries and fields.
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.
Enable Resources
You can enable resources (ie. Collections, Taxonomies, etc.) in your config/statamic/graphql.php
config:
'resources' => [ 'collections' => true, 'taxonomies' => true, // etc.]
Enable Specific Sub-Resources
If you want more granular control over which sub-resources are enabled within a resource type (ie. enabling specific Collection queries only), you can use array syntax:
'resources' => [ 'collections' => [ 'articles' => true, 'pages' => true, // 'events' => false, // Sub-resources are disabled by default ], '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
- Collections
- Collection
- Entries
- Entry
- Asset Containers
- Asset Container
- Assets
- Asset
- Taxonomies
- Taxonomy
- Terms
- Term
- Global Sets
- Global Set
- Navs
- Nav
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 . |
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:
{ 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 . |
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", }}
Forms
Used for querying multiple forms.
{ forms { handle title fields { handle display } }}
{ "forms": [ { "handle": "contact", "title": "Contact", "fields": [ { "handle": "name", "display": "Name" }, { "handle": "email", "display": "Email" }, { "handle": "inquiry", "display": "Inquiry" } ] } ]}
Form
Used for querying a single form.
{ form(handle: "contact") { handle title fields { handle display } }}
{ "form": { "handle": "contact", "title": "Contact", "fields": [ { "handle": "name", "display": "Name" }, { "handle": "email", "display": "Email" }, { "handle": "inquiry", "display": "Inquiry" } ] }}
Navs
Used for querying Navs.
{ navs { handle title }}
{ "navs": [ { "handle": "header_links", "title": "Header Links" }, { "handle": "footer_links", "title": "Footer Links" }, ]}
Nav
Used for querying a single Nav.
{ nav(handle: "footer") { handle title }}
{ "nav": { "handle": "footer", "title": "Footer Links" }}
Users
Used for querying multiple users.
Argument | Type | Description |
---|---|---|
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:
{ users { current_page data { name email } }}
{ "users": { "current_page": 1, "data": [ ] }}
User
Used for querying a single user.
{ name email }}
{ "user": { "name": "David Hasselhoff", }}
You can query by either id
or email
.
Argument | Type | Description |
---|---|---|
id |
String |
The ID of the user. If you use this, you don't email . |
email |
String |
The email address of the user. If you use this, you don't id . |
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
- Collection
- CollectionStructure
- CollectionTreeBranch
- NavTreeBranch
- PageInterface
- TermInterface
- AssetInterface
- GlobalSetInterface
- Code
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. |
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.
NavTreeBranch
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. |
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.
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
Enabling Filters
For security, filtering is disabled by default. To enable, you'll need to opt in by defining a list of allowed_filters
for each sub-resource in your config/statamic/graphql.php
config:
'resources' => [ 'collections' => [ 'articles' => [ 'allowed_filters' => ['title', 'status'], ], 'pages' => [ 'allowed_filters' => ['title'], ], 'events' => true, // Enable this collection without filters 'products' => true, // Enable this collection without filters ], 'taxonomies' => [ 'topics' => [ 'allowed_filters' => ['slug'], ], 'tags' => true, // Enable this taxonomy without filters ], // etc.],
For queries that don't have sub-resources (ie. users), you can define allowed_filters
at the top level of that resource config:
'resources' => [ 'users' => [ 'allowed_filters' => ['name', 'email'], ],],
Using Filters
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" }, ] }) { # ... }
Advanced Filtering Config
You can also allow filters on all enabled sub-resources using a *
wildcard config. For example, here we'll enable only the articles
, pages
, and products
collections, with title
filtering enabled on each, in addition to status
filtering on the articles
collection specifically:
'resources' => [ 'collections' => [ '*' => [ 'allowed_filters' => ['title'], // Enabled for all collections ], 'articles' => [ 'allowed_filters' => ['status'], // Also enable on articles ], 'pages' => true, 'products' => true, ],],
If you've enabled filters using the *
wildcard config, you can disable filters on a specific sub-resource by setting allowed_filters
to false
:
'resources' => [ 'collections' => [ '*' => [ 'allowed_filters' => ['title'], // Enabled for all collections ], 'articles' => [ 'allowed_filters' => false, // Disable filters on articles ], 'pages' => true, 'products' => true, ],],
Or you can enable queries and filters on all sub-resources at once by setting both enabled
and allowed_filters
within your *
wildcard config:
'resources' => [ 'collections' => [ '*' => [ 'enabled' => true, // All collection queries enabled 'allowed_filters' => ['title'], // With filters enabled for all ], ],],
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 } }}
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::class ]]
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 field named image
, 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->image; $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 are identical to any other Laravel middleware class. They will be executed on all GraphQL requests (unless another middleware, e.g. caching, prevents it).
Use the handle
method to perform some action, and pass the request on.
use Closure; class MyMiddleware{ public function handle($request, Closure $next) { // do something return $next($request); }}
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.