PagibleAI GraphQL API

PagibleAI exposes its authenticated content-management API at https://your-domain.example/graphql. You can query pages, shared elements, files and editor data, then create drafts and publish them when they are ready. The API is powered by Lighthouse and follows the GraphQL request and response format.

Documentation scope: the operations below match the PagibleAI 0.13 schema. If your installed package differs, compare its published graphql/cms.graphql file before copying an operation.

JSON scalar values

PagibleAI uses MLL’s JSON scalar. Send JSON documents as encoded strings and parse JSON scalar responses before using them. With JavaScript, use JSON.stringify(value) once when building variables and JSON.parse(result.data.page.content) after reading a field. Native GraphQL object literals are not accepted for this scalar.

Before you start

You need a Laravel user with CMS permissions. Grant the editor role from your application directory (the command asks for a password and creates the user if it doesn't exist yet):

php artisan cms:user --role=editor editor@example.com

The examples below use browser session authentication. Send JSON requests with Content-Type: application/json and keep credentials: "include" so the session cookie is sent with later requests. Use HTTPS outside local development.

Authenticate an editor

const response = await fetch('/graphql', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    query: `mutation Login($email: String!, $password: String!) {
      cmsLogin(email: $email, password: $password) {
        id
        name
        email
      }
    }`,
    variables: {
      email: 'editor@example.com',
      password: 'secret',
    },
  }),
});

const result = await response.json();
{
  "data": {
    "cmsLogin": {
      "id": "42",
      "name": "CMS editor",
      "email": "editor@example.com"
    }
  }
}

A successful response places the requested fields under data.cmsLogin. Authentication failures and validation errors appear in the top-level errors array. See GraphQL User & Authentication for login, editor permissions, preferences and logout.

Call GraphQL with curl

The installed GraphQL route uses Laravel sessions and CSRF validation. Start a session with a same-origin GET, retain its cookies, URL-decode the XSRF-TOKEN cookie and send it as X-XSRF-TOKEN:

BASE='https://your-domain.example'

curl --silent --show-error \
  --cookie-jar cms.cookies \
  "$BASE/cmsadmin" \
  --output /dev/null

ENCODED=$(awk '$6 == "XSRF-TOKEN" {print $7}' cms.cookies | tail -1)
TOKEN=$(php -r 'echo urldecode($argv[1]);' "$ENCODED")

curl --silent --show-error \
  --cookie cms.cookies \
  --cookie-jar cms.cookies \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header "X-XSRF-TOKEN: $TOKEN" \
  --data-binary @- \
  "$BASE/graphql" <<'JSON'
{
  "query": "mutation Login($email: String!, $password: String!) { cmsLogin(email: $email, password: $password) { id name email } }",
  "variables": {
    "email": "editor@example.com",
    "password": "secret"
  }
}
JSON

Keep cms.cookies outside source control and delete it after the session. If your application exposes the CMS under a different route, use any same-origin GET that starts the configured web session and issues the XSRF-TOKEN cookie.

With the PagibleAI theme package installed, you can also use the lightweight GET /cmsapi/csrf endpoint (route cms.api.csrf). It starts the web session, sets the XSRF-TOKEN cookie and returns the plain token as {"token": "..."}, which you can send unchanged in an X-CSRF-TOKEN header.

Query paginated content

Collection queries are paginated. Select records through data and request paginatorInfo when you need page counts. first defaults to 100 and is capped at 100 items per request; use page to fetch further results. Omit publish to list all items regardless of their publication state.

query PageList($first: Int!, $page: Int!) {
  pages(
    filter: { parent_id: null, lang: "" }
    trashed: WITHOUT
    first: $first
    page: $page
  ) {
    data {
      id
      name
      title
      path
      status
      latest { id }
    }
    paginatorInfo {
      currentPage
      lastPage
      total
    }
  }
}
{
  "first": 25,
  "page": 1
}

Use page(id: ID!), element(id: ID!) or file(id: ID!) when you already know an item’s UUID. You can request several root fields in one operation; GraphQL returns each result under its field name or alias.

Create a page draft

PagibleAI uses MLL’s JSON scalar. It expects a JSON-encoded string, not a native GraphQL object. When you use variables, stringify each meta, config and content document once; the surrounding PageInput remains a normal variable object. File and shared-element references inside the decoded documents are discovered automatically.

mutation AddPage($input: PageInput!, $parent: ID) {
  addPage(input: $input, parent: $parent) {
    id
    title
    latest { id }
  }
}
{
  "parent": "018f0f9d-83b3-7d69-a1b2-6f2db5e40c35",
  "input": {
    "lang": "en",
    "path": "getting-started",
    "name": "Getting started",
    "title": "Getting started with PagibleAI",
    "status": 1,
    "cache": 5,
    "meta": "{\"meta-tags\":{\"type\":\"meta-tags\",\"data\":{\"description\":\"Set up your first PagibleAI page.\"},\"files\":[]}}",
    "config": "{}",
    "content": "[{\"id\":\"intro\",\"type\":\"text\",\"group\":\"main\",\"data\":{\"text\":\"Welcome to your new page.\"}}]"
  }
}

addPage creates an unpublished version. Keep the returned latest.id; pass it as latestId when saving later so concurrent edits can be detected.

Save and publish safely

mutation SavePage($id: ID!, $input: PageInput!, $latestId: ID!) {
  savePage(id: $id, input: $input, latestId: $latestId) {
    id
    title
    changed
    latest { id }
  }
}

mutation PublishPage($id: ID!) {
  pubPage(id: [$id]) {
    id
    title
  }
}

Saving creates another draft version; it does not update the public page. Publish only after reviewing the returned draft. Mutations in one GraphQL operation run serially, but separate publish operations are clearer when your workflow includes review or approval.

Handle GraphQL errors

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const result = await response.json();

if (result.errors?.length) {
  throw new Error(result.errors.map(error => error.message).join('\n'));
}

return result.data;

Transport failures use HTTP status codes. GraphQL validation, authentication, permission and resolver failures normally use the errors array and may still arrive with an HTTP 200 response. Do not expose debug details from production responses.

Request limits

The GraphQL package protects the endpoint against expensive or abusive requests. The query limits are only applied if your application hasn't configured its own lighthouse.security values.

GraphQL limits

Limit
Default
Configuration
Maximum query depth
15
CMS_GRAPHQL_MAXDEPTH
Maximum query complexity
10000
CMS_GRAPHQL_MAXCOMPLEXITY
Queries and mutations
120 per minute per user (or IP)
cms-graphql rate limiter
Login attempts
10 per minute per IP
cms-login rate limiter
AI mutations
10 per minute per user
cms-ai rate limiter
Items per collection request
100
first argument is capped
Schema introspection
Disabled when APP_DEBUG=false
lighthouse.security.disable_introspection

Troubleshoot requests

Common GraphQL failures

Symptom
Likely cause
What to check
HTTP 419
Missing or stale CSRF token
Refresh the session cookie and resend its decoded token header
HTTP 429 / too many requests
A rate limit was exceeded
Wait a minute and reduce the request rate or batch fields into fewer operations
Unauthenticated
Missing or expired session
Send cookies with the request and log in again
Insufficient permissions
The user lacks a CMS capability
Inspect me.permission and the operation’s required capability
Max query depth or complexity exceeded
The query nests too deeply or requests too many fields
Request fewer nested levels or fields, or raise the configured limits
Unknown argument or field
Documentation and installed schema differ
Inspect your application’s published graphql/cms.graphql file. Schema introspection is disabled when APP_DEBUG=false, so introspection queries and GraphQL IDE schema loading only work in debug mode
Invalid JSON value
A native object was sent to the JSON scalar
Send one JSON-encoded string
Conflict details in changed
Another editor saved a newer version
Re-read the item, merge the fields and retry with its latest ID
Upload rejected
Size, MIME or image dimensions violate policy
Check CMS upload configuration and the detected MIME type

Filtering enums

Collection filters

Enum
Value
Meaning
Publish
DRAFT
Items whose latest version has unpublished changes
Publish
PUBLISHED
Items whose latest version is published
Publish
SCHEDULED
Unpublished items with a scheduled publication date
Publish
(omitted)
All items regardless of publication state
Trashed
WITHOUT
Exclude soft-deleted items
Trashed
WITH
Include active and soft-deleted items
Trashed
ONLY
Return only soft-deleted items

GraphQL reference guides