How it differs from GraphQL
- The query begins with
query. The first five characters are dropped before parsing, so{ Page { id } }on its own fails. queryis the only key in the body.variablesoroperationName, which GraphQL client libraries add, make it a 500. Write the values into the query.- It reads only. There are no mutations, subscriptions or introspection. Writes go through REST.
- Fields are separated by commas or line breaks. A space alone glues two fields into one, which comes back as an
"Error: …"string. - Nothing is limited for you. A part without
firstreturns every record, and nesting has no depth limit.
Each top-level field names a kind of record, and its fields are the model's, as REST's properties uses them: aliases (title: activeRevision.name), relations in braces, and getter arguments by name (published(format: "Y-m-d")). Give every part an alias; the response is keyed by it.
Kinds of record
| Area | Names |
|---|---|
| Content | Page, Blog, BlogPost, Book, BookUnit, BookPage, FaqPage, FaqQuestion, Menu, MenuElement (needs menu), Rewrite |
| Structure | Layout, LayoutCategory, InputType, CustomField, GlobalComponent, Category, Tag |
| Files | Folder, Filename |
| Forms | Form, FormSubmission (needs form), StepSubmission (needs step) |
| Commerce | Product, ProductCategory, ProductVariant, Cart |
| People | User, Person, Group |
| Custom entities | Record for every record, or CustomEntities_CustomEntity_event for one definition's (its identifier at the end) |
Anything else, orders and invoices included, is a 500 Entity not supported: read those through REST.
Arguments
| Argument | Does | Example |
|---|---|---|
filter | Criteria; dotted paths reach relations | filter: {parent.id: 1} |
first or limit | How many records | first: 10 |
offset | How many to skip. There is no page | offset: 20 |
orderBy | Field and direction | orderBy: {published: "DESC"} |
| Anything else | Added to filter | menu: 1 |
Inside filter, unlike REST's query string, suffixes work: _contains, _not, _gt, _lt, _gte, _lte, _in and _not_in, and OR and AND take a list of conditions. null matches an empty relation; NULL does not. Name fields exactly as in the model, as in REST.
Errors
| Request | Status | Message |
|---|---|---|
A GET | 405 | |
Another key beside query | 500 | Currently, only Query GraphQL requests are supported by this API |
A query that does not begin with query | 500 | Could not decode … as GraphQL query |
| A kind of record not in the table | 500 | Entity not supported: Order |
MenuElement without menu | 500 | Required argument missing: menu |
| A misspelt field | 200 | The field holds "Error: Getter for property … not found" |
Who can call it
The credentials are REST's: an OAuth access token or an API key, or a signed-in backend user's session with the CSRF token. Site members cannot use it. Each part is read through the same handler REST uses for that kind of record, so a query reaches nothing its credential could not reach through REST. For a headless front end, call it from your server, as a backend user that may read only what the site publishes.