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
| Item | Detail |
|---|---|
| URI | /api/graphql only (route name graphql) |
| Schema | graphql/schema.graphql |
| Config | config/lighthouse.php |
| Middleware | ResolveApiAccess + throttle:api |
| CSRF | Excepted 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:
| Style | How |
|---|---|
| Anonymous | Role public Collection access matrix |
| API key | Authorization: Bearer ek_… → key’s role |
No session cookies. Super-admin is not available via API keys.
Queries
| Field | Args | Returns |
|---|---|---|
collections | — | [Collection!]! |
collection | slug: String! | Collection |
items | collection: String!, filter: JSON, page: Int = 1, perPage: Int = 15 | ItemPage! |
item | collection: 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
| Field | Args | Notes |
|---|---|---|
createItem | collection, data | Same create semantics as REST |
updateItem | collection, id, data | Same update semantics as REST |
deleteItem | collection, id | Soft-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
| Concern | Location |
|---|---|
| Schema | graphql/schema.graphql |
| Resolvers | app/GraphQL/Queries/*, app/GraphQL/Mutations/* |
| Config | config/lighthouse.php |
| Auth middleware | ResolveApiAccess + EnforcePublicApiOrigin (same as /api/v1) |
| Bruno | github.com/qiick-io/externa-bruno |