HTTP reference

GraphQL

Externa exposes a Lighthouse GraphQL endpoint at /api/graphql for the same headless surface as REST /api/v1. Auth, Collection access, field ACL, and item_filter match the Public CMS API.

Related

REST setup and matrices: Public CMS API. Filter dialect: Collections API — Advanced list filters. Ready-to-run collection: externa-bruno (PublicApi/GraphQL/ — open folder in Bruno, env Local, or duplicate for staging). Ops cache: Operations · Environment variables.

Endpoint

ItemDetail
URI/api/graphql only (route name graphql)
Schemagraphql/schema.graphql
Configconfig/lighthouse.php
MiddlewareResolveApiAccess + throttle:api
CSRFExcepted for api/graphql (and a bare graphql path that is not registered as a route)

There is no /graphql application route — clients must post to /api/graphql.

curl -sS http://externa-core.test/api/graphql \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer ek_YOUR_SECRET" \
  -d '{"query":"{ collections { slug name } }"}'

Omit Authorization for anonymous access via the public role matrix.

Auth

Same as REST:

StyleHow
AnonymousRole public Collection access matrix
API keyAuthorization: Bearer ek_… → key’s role

No session cookies. Super-admin is not available via API keys.

Queries

FieldArgsReturns
collections[Collection!]!
collectionslug: String!Collection
itemscollection: String!, filter: JSON, page: Int = 1, perPage: Int = 15ItemPage!
itemcollection: String!, id: ID!Item

Collection fields: id, name, slug, is_singleton.

Item fields: id, collection_id, data, created_at, updated_at, user_created_id, user_updated_id.

ItemPage: data, meta { total, current_page, … }.

Note: GraphQL uses camelCase perPage (REST uses per_page). Locale flattening for translatable fields follows the same resolver rules as REST when applicable — prefer REST ?locale= when you need explicit locale control.

Query collections

{
  "query": "{ collections { id slug name is_singleton } }"
}
{
  "data": {
    "collections": [
      { "id": "1", "slug": "posts", "name": "Posts", "is_singleton": false }
    ]
  }
}

Query items (+ collection)

query ($slug: String!, $filter: JSON) {
  collection(slug: $slug) {
    id
    name
    is_singleton
  }
  items(collection: $slug, filter: $filter, perPage: 15) {
    data {
      id
      data
      user_created_id
    }
    meta {
      total
      current_page
    }
  }
}
{
  "query": "query ($slug: String!, $filter: JSON) { items(collection: $slug, filter: $filter) { data { id data } meta { total current_page } } }",
  "variables": {
    "slug": "posts",
    "filter": { "status": { "_eq": "published" } }
  }
}
{
  "data": {
    "items": {
      "data": [
        { "id": "1", "data": { "title": "Hello", "status": "published" } }
      ],
      "meta": { "total": 1, "current_page": 1 }
    }
  }
}

Query item

{
  "query": "query ($slug: String!, $id: ID!) { item(collection: $slug, id: $id) { id collection_id data created_at updated_at } }",
  "variables": { "slug": "posts", "id": "1" }
}

Mutations

FieldArgsNotes
createItemcollection, dataSame create semantics as REST
updateItemcollection, id, dataSame update semantics as REST
deleteItemcollection, idSoft-delete (boolean); no force-delete on GraphQL

createItem

{
  "query": "mutation ($slug: String!, $data: JSON!) { createItem(collection: $slug, data: $data) { id data } }",
  "variables": {
    "slug": "posts",
    "data": { "title": "GraphQL create", "status": "draft" }
  }
}
{
  "data": {
    "createItem": {
      "id": "42",
      "data": { "title": "GraphQL create", "status": "draft" }
    }
  }
}

Requires create on the collection for the actor’s role.

updateItem

{
  "query": "mutation ($slug: String!, $id: ID!, $data: JSON!) { updateItem(collection: $slug, id: $id, data: $data) { id data } }",
  "variables": {
    "slug": "posts",
    "id": "42",
    "data": { "status": "published" }
  }
}

Requires update. Field rules + item_filter apply like REST PATCH.

deleteItem

{
  "query": "mutation ($slug: String!, $id: ID!) { deleteItem(collection: $slug, id: $id) }",
  "variables": { "slug": "posts", "id": "42" }
}
{
  "data": { "deleteItem": true }
}

Requires delete. Soft-delete semantics match REST.

Ops notes

  • Query cache defaults to opcache (LIGHTHOUSE_QUERY_CACHE_MODE) so parsed ASTs are not stored in the database cache driver.
  • Bruno: externa-bruno PublicApi/GraphQL/. Pest: tests/Feature/Api/GraphqlCollectionApiTest.php.

Source map

ConcernLocation
Schemagraphql/schema.graphql
Resolversapp/GraphQL/Queries/*, app/GraphQL/Mutations/*
Configconfig/lighthouse.php
Auth middlewareResolveApiAccess + EnforcePublicApiOrigin (same as /api/v1)
Brunogithub.com/qiick-io/externa-bruno
Previous
Public CMS API types