Skip to content

Reference

GraphQL

Read pages, lists, menus, globals and campaigns in one request, with the schema, examples, errors and limits.

Reference11 min read

GraphQL reads the same content as the Delivery API, with the same keys. Use it when one page needs several things at once, such as the page, its menu and its banners, in a single request.

Production
Address
https://api.bifrostorbit.com/cms/graphql
Staging
Address
https://api-stage.bifrostorbit.com/cms/graphql

Try queries in Configuration › API playground. It opens on GraphQL, lists every query and type beside the editor, and shows the curl command for what you run.

Requests

Send a delivery key as Authorization: Bearer <key> or x-api-key: <key>, and POST a JSON body:

query
Type
string
Means
The GraphQL document. Required.
variables
Type
object
Means
Values for the query's variables
operationName
Type
string
Means
Which operation to run when the document has several
Shell
curl 'https://api.bifrostorbit.com/cms/graphql' \  -H 'Authorization: Bearer cms_pk_…' \  -H 'Content-Type: application/json' \  -d '{"query":"query ($url: String!) { page(url: $url, region: \"de\") { name url } }","variables":{"url":"/products/hiking-boots"}}'

GET works too, with query, variables (as JSON text) and operationName in the query string. URL-encode them:

Shell
curl -G 'https://api.bifrostorbit.com/cms/graphql' \  -H 'Authorization: Bearer cms_pk_…' \  --data-urlencode 'query={ regions { key name } }'

Answers are JSON with data, and errors when something went wrong:

JSON
{ "data": { "page": { "name": "Hiking boots", "url": "/products/hiking-boots" } } }

Browser code on any website may call it with a public key; a secret key only works from a server. See From a browser in the Delivery API.

Queries

Every query that reads content takes region and locale, which work as in the Delivery API: a region key or code such as germany or de, and a language code such as de-DE. Leave them out for the defaults. draft: true reads drafts and needs a secret key.

page
Arguments
url: String!, locale, region, draft, strict: Boolean, references: Boolean
Returns
Page. One page by address, with language and region fallback unless strict is true.
pages
Arguments
type: String!, locale, region, draft, page: Int, perPage: Int, references: Boolean
Returns
PageList!. Entries of one content type, in the order editors set. page defaults to 1, perPage to 20, at most 100.
global
Arguments
key: String!, locale, region, draft, references: Boolean
Returns
Global. A global entry such as the footer, with language fallback.
navigation
Arguments
key: String!, locale, region, draft
Returns
Navigation. A menu.
regions
Returns
[Region!]!. The regions and languages the key may read.
resolve
Arguments
url: String!, locale, region, draft, references: Boolean
Returns
Resolution!. Whether an address is a page, a redirect, gone or not found.
sitemap
Arguments
region, locale, page: Int
Returns
JSON!. Without region, the index of every region's sitemap; with it, that sitemap's entries.
banners
Arguments
region, locale, path: String, everyPage: Boolean, draft
Returns
JSON!. Banners live now for a page, pinned first, with serverTime. everyPage: true returns those for every page instead.
popups
Arguments
region, locale, path: String, loggedIn: Boolean, draft
Returns
JSON!. Popups live now for a page and sign-in state, highest priority first, with serverTime.

locale and region are String, and draft is Boolean, everywhere. references: true replaces Reference and References values in fields and components with the entries they point at, as include=references does in REST. See References in the Delivery API. sitemap, banners and popups answer with the same JSON as their REST endpoints, under serverTime and items for campaigns.

Some options exist only in REST: sorting, filtering, tag, category and q for lists, strict for globals and addresses, and loggedIn for banners. Use the Delivery API for those.

Types

JSON holds content shaped by your space, such as fields and components, and cannot be selected into. DateTime is an ISO 8601 timestamp such as 2026-10-01T09:30:00.000Z.

Page

url
Type
String!
Means
The page address
name
Type
String!
Means
Its name in the CMS
contentType
Type
String!
Means
Content type key
locale, region
Type
String!
Means
The language and region that answered
requestedLocale, requestedRegion
Type
String!
Means
What was asked for
fallbackApplied
Type
Boolean!
Means
Another language answered
regionFallbackApplied
Type
Boolean!
Means
Another region answered
draft
Type
Boolean!
Means
This is a draft
publishedAt
Type
DateTime
Means
Last published, or null
version
Type
Int!
Means
Published version, or draft version for a draft
fields
Type
JSON!
Means
The content type's fields, with images and links filled in
components
Type
JSON!
Means
The page body: the component tree to render. Blocks in a slot are on the slot's field as well as in children
seo
Type
JSON!
Means
Title, description, canonical, robots, Open Graph, X card, alternates, breadcrumbs and JSON-LD

PageList and Summary

PageList
Field
items
Type
[Summary!]!
Field
total
Type
Int!, matching entries across all pages
Field
page
Type
Int!
Field
hasMore
Type
Boolean!
Summary
Field
id
Type
String!, the entry's id, the value a Reference field holds
Field
url
Type
String, null for entries without an address
Field
name, contentType
Type
String!
Field
publishedAt
Type
DateTime
Field
seo
Type
JSON, null for entries without an address
Field
fields
Type
JSON, the entry's fields, read only when you select it

Summaries leave the page body out on purpose. Ask page for one. Select fields to read entries without an address, such as FAQ entries, in one request.

Global

key, name, contentType
Type
String!
locale
Type
String!, the language that answered
fields, components
Type
JSON!
Navigation
Field
key, name
Type
String!
Field
items
Type
[NavItem!]!
NavItem
Field
label
Type
String!
Field
href, target, icon, badge
Type
String
Field
children
Type
[NavItem!]!

Select children again for each level of the menu you want.

Region and Locale

Region
Field
key, name
Type
String!
Field
locales
Type
[Locale!]!
Locale
Field
code, name
Type
String!
Field
isDefault
Type
Boolean!

Resolution and Redirect

Resolution
Field
kind
Type
String!: page, redirect, gone or notFound
Field
page
Type
Page, set when kind is page
Field
redirect
Type
Redirect, set when kind is redirect
Redirect
Field
destination
Type
String!
Field
statusCode, hops
Type
Int!
Field
permanent
Type
Boolean!, true for 301 and 308

Unlike REST, resolve answers 200 for every kind. Read kind to decide what to do.

Examples

A page with its menus and banners

One request for everything a page template needs. Aliases fetch two menus at once.

graphql
query PageView($url: String!, $region: String) {  page(url: $url, region: $region) {    name    url    locale    fallbackApplied    components    seo  }  header: navigation(key: "main", region: $region) {    items {      label      href      children {        label        href      }    }  }  footer: navigation(key: "footer", region: $region) {    items {      label      href    }  }  banners(path: $url, region: $region)}
JSON
{ "url": "/products/hiking-boots", "region": "de" }

A paged list

graphql
query Products($page: Int) {  pages(type: "product", region: "de", page: $page, perPage: 12) {    items {      url      name      publishedAt    }    total    page    hasMore  }}

Ask for the next page while hasMore is true.

Entries without an address

FAQ entries, authors and other entries without a page of their own are read through pages with their fields:

graphql
{  pages(type: "faqEntry", region: "de", perPage: 50) {    items {      id      name      fields    }  }}

A page with the entries it references

graphql
{  page(url: "/pricing", region: "de", references: true) {    components  }}

A References field in a component then holds the entries, each with id, url, name, contentType, publishedAt and fields, instead of their ids.

A global entry

graphql
{  global(key: "footer", region: "de") {    name    locale    fields    components  }}

Before rendering an address

graphql
query Route($url: String!) {  resolve(url: $url, region: "de") {    kind    redirect {      destination      statusCode    }    page {      name      components      seo    }  }}

Drafts and preview

Add draft: true to each query that should read drafts, and send a secret key (cms_sk_…). Secret keys exist only for staging environments. A public key asking for drafts gets 401.

graphql
query Preview($url: String!) {  page(url: $url, region: "de", draft: true) {    name    version    components  }}

See Set up the live preview.

From the SDK

TypeScript
const data = await cms.graphql<{ page: { name: string; components: unknown[] } }>(  'query ($url: String!) { page(url: $url, region: "de") { name components } }',  { variables: { url: '/products/hiking-boots' } },)

cms.graphql(query, options) answers with data. The options are variables, operationName, preview and signal. Pass the expected shape as the type.

  • preview: true sends the client's preview key instead of its public key. It does not add draft: true; put that in the query.
  • GraphQL requests are never cached by the SDK and are not retried.
  • Any error in the answer is thrown, not returned beside partial data: CmsAuthError for 401, CmsRateLimitError for 429, CmsUnavailableError for 500 or when the API cannot be reached, and CmsError with the status and message for anything else, such as a missing page.

See the SDK.

Errors

Errors come in errors, each with a message, a code under extensions, and the path and locations of the field that failed when there is one:

JSON
{  "data": { "page": null },  "errors": [    {      "message": "Content not found",      "locations": [{ "line": 1, "column": 3 }],      "path": ["page"],      "extensions": { "code": "not_found" }    }  ]}
400
When
No query, variables that are not a JSON object, a query that does not parse or does not match the schema (bad_request), or one over the limits (query_too_deep, query_too_complex). data is left out.
401
When
A missing, unknown, deleted or expired key, a public key asking for drafts, a secret key sent from a browser on another website, or a region or language the key may not read (unauthorized)
404
When
A page, global entry, menu, region or language that does not exist
402
When
The organisation's CMS Orbit plan has ended (plan_ended)
429
When
Too many requests. The error has "extensions": { "code": "rate_limited" } and the answer has the same Retry-After and X-RateLimit-… headers as the REST endpoints. Every other answer has the X-RateLimit-… headers too. See the Delivery API. data is left out.
413
When
A query longer than 20,000 characters (query_too_large)
500
When
Something failed on our side (internal_error). The message is general and the details stay on our side. Try again.
200
When
Success
  • When page, global or navigation fails, that field is null and the other fields still answer, so a missing menu does not lose the page.
  • pages, regions, resolve, sitemap, banners and popups never answer null, so when one of them fails the whole data is null.
  • When several fields fail, the HTTP status comes from the first error.
  • page, global and navigation report a missing item as an error rather than a quiet null. Check errors before using data.

Limits

  • Each request counts once against the rate limit of 15,000 requests a minute per key and per IP address, shared with the REST endpoints. See the Delivery API.
  • perPage in pages is at most 100.
  • A query may be at most 20,000 characters long.
  • A query may nest at most 10 levels, counting from the top field, and ask for at most 300 fields. Each alias and each use of a fragment counts. Split a bigger read into several requests.
  • Introspection is on, so GraphQL tools can read the schema from the address, and introspection fields do not count towards the limits. The API playground shows the schema too.
  • Persisted queries are not supported. Send the full query each time.

Caching

Every GraphQL answer has Cache-Control: no-store and Vary: Authorization, so nothing between your website and the API keeps it. Cache in your website instead, and refresh on publish webhooks. See Refresh the website when content changes. For pages a CDN should cache, the REST endpoints carry cache headers.

Was this page helpful?

Read next