GraphQL
GraphQL is a way to ask an API for data by describing the shape of the answer you want. Instead of calling several addresses, you send one query to one address, and the answer has exactly the fields you asked for, nothing more.
You do not need GraphQL to build a website with Manablox. The SDK with its REST transport is simpler for beginners. GraphQL is worth learning when your team already uses it, or when a page needs only a few fields of large documents.
The address
Section titled “The address”Every query is a POST request to /graphql on the public API:
| Project | Address |
|---|---|
local, while you develop | http://localhost:3100/graphql |
| On a server | https://<your public API domain>/graphql |
The request body is JSON with a query and, optionally, variables. The public API only returns published content of its one space, and it needs no login or key.
Your first query
Section titled “Your first query”A query lists the fields you want, nested like the answer. This one asks for the title and permalink of the document at /about:
{ contentByPermalink(permalink: "about") { title permalink }}The answer mirrors the query:
{ "data": { "contentByPermalink": { "title": "About", "permalink": "about" } }}Send it from a terminal with curl:
curl -X POST http://localhost:3100/graphql -H 'content-type: application/json' -d '{"query":"{ contentByPermalink(permalink: \"about\") { title permalink } }"}'You should see the JSON above. If you see "contentByPermalink": null, nothing is published at about in the space the public API serves. If curl cannot connect, start the public API with pnpm dev:public in your CMS project.
How your content model becomes a schema
Section titled “How your content model becomes a schema”A schema is the list of everything you can ask for. Manablox builds it from your content model, so it changes when you add a content type or a field in the admin:
- Each content type becomes a GraphQL type, named in PascalCase:
pagebecomesPage,blog-postbecomesBlogPost. - Each block type becomes a type too:
teaserbecomesTeaser. - Field names are written in camelCase:
meta_descriptionbecomesmetaDescription. - Fields that only some roles may read are left out of the public schema.
Every content type shares a set of basic fields, through an interface called ContentNode:
| Field | Meaning |
|---|---|
id | The document’s id |
typeName | The content type’s technical name, for example page |
title, slug, permalink | Title, slug and full path |
locale | The language of this version |
status | The publishing status |
publishedAt, updatedAt | Dates |
Content types also have parent and children, to walk the page tree.
Fields that belong to one type only are asked for with ... on TypeName { }. This is called an inline fragment; it means “if the document is a Page, also give me these fields”.
The questions you can ask
Section titled “The questions you can ask”| Query | What it returns |
|---|---|
contentByPermalink(permalink: "...", locale: "...") | The document at a path. "" is the home page |
content(id: "...") | One document by its id |
contents(type, parentId, under, search, locale, limit, offset) | A list of documents. limit defaults to 25 |
menu(name: "main", locale: "...") | A menu with its entries |
asset(id: "...") | One image or file |
locale is optional everywhere and defaults to the space’s default language.
A page with its blocks
Section titled “A page with its blocks”A blocks field has two parts: grid (the layout an editor set, or null) and blocks. Every block has a blockId, a typeName and a layout, plus the fields of its block type. With a space made by the Basic setup:
query Page($permalink: String!) { contentByPermalink(permalink: $permalink) { title ... on Page { summary components { grid blocks { blockId typeName ... on Teaser { headline body image { alt width height card: variant(preset: "card") } } } } } }}With the variables { "permalink": "about" } it returns the About page, its summary and its teasers.
A few things this query shows:
$permalinkis a variable. You send its value next to the query, so the query text never changes.bodyis a rich text field. It arrives as structured JSON; turn it into HTML with the SDK’srichTextToHtml, see The SDK.- Images arrive as objects with
url(the original),alt,widthandheight.variant(preset: "card")returns the address of a resized copy;card: ...renames it in the answer. The presets arethumb,cardandhero. - Related documents and images are always included as objects. There is nothing to expand.
A list and a menu
Section titled “A list and a menu”The first ten articles, with their summaries:
{ contents(type: "article", limit: 10) { title permalink ... on Article { summary } }}The main menu, two levels deep:
{ menu(name: "main") { items { label url target content { permalink } children { label url content { permalink } } } }}A menu entry points either to a document (content) or to an external address (url). Build a link from url when it is set, otherwise from / plus content.permalink. The SDK does this for you and calls the result href.
Tools to try queries
Section titled “Tools to try queries”Writing queries is easier in a tool that suggests field names as you type. The public API keeps its schema private by default, so tools cannot read it. To switch that on while you develop:
- Open
.envin your CMS project and setPUBLIC_GRAPHQL_INTROSPECTION=true. - Restart
pnpm dev:public(stop it with Ctrl+C, then start it again). - Open
http://localhost:3100/graphqlin your browser.
You should see GraphiQL, an editor for GraphQL queries: type on the left, press the run button, and read the answer on the right. Press Ctrl+Space for suggestions.
Any other GraphQL client works too, including desktop tools such as Postman or Insomnia: point it at the address above.
From your website
Section titled “From your website”With the SDK on its GraphQL transport, you only write the part that is specific to a type; the basic fields are added for you:
import { createClient } from '@manablox/public-sdk';
const cms = createClient({ url: 'http://localhost:3100', transport: 'graphql' });
const page = await cms.byPermalink('/about', { selection: '... on Page { summary components { grid blocks { blockId typeName ... on Teaser { headline body } } } }',});
const data = await cms.query('{ contents(type: "article", limit: 5) { title permalink } }');cms.query runs any query you write. Any other GraphQL library, or plain fetch, works the same way.
Limits
Section titled “Limits”To protect the server, the public API refuses queries that nest too deep (more than 8 levels) or are too expensive (a complexity score above 1000). Normal page queries stay far below both. There are no mutations (queries that change data): the public API is read-only.