Skip to content

GraphQL API

CM Box exposes a GraphQL API for reading content. Unlike a fixed API, the schema is generated per repository from that repository’s content types, so the types and queries you see in introspection match your content model exactly. This page explains where the endpoints live, how the schema is built, how requests are authorized, and how to query the version of content you actually want.

Each content repository has its own endpoint:

POST /api/graphql/<repository>

<repository> is the repository’s name (its namespace). Every repository runs its own Apollo Server instance with its own schema, so a type that exists in one repository does not appear in another repository’s schema. When an administrator changes a repository’s configuration or content types, that repository’s server is reloaded and its schema regenerated — other repositories are unaffected.

Because each schema is self-describing, standard GraphQL introspection (and any GraphQL IDE pointed at the endpoint) is the quickest way to see the exact types and fields available in your repository.

For every content type registered on the repository, the generator emits a GraphQL object type containing:

  • System fields — present on every type: id, slug, name, type, status, description, language, createdDate, updatedDate, createdByUser, updatedByUser, taxonomies, renditions, publishedVersion, currentVersion, latestVersion, isLatestVersion, isLatestPublished, isPublished, isArchived, isPreview, autoPublishDate, autoArchiveDate, parent, and more. See the content model for what each one means.
  • One field per content-type field, with the field’s datatype mapped to a GraphQL type (for example, text fields become String and number fields become Int). Dashes are stripped from field names and spaces become underscores.
  • Reference fields as nested objects. A reference field that targets a single content type resolves to that type; a reference field that targets several types resolves through a generated union named <Type>_<fieldName>. Reference fields are emitted as lists — even a reference configured to hold a single item returns a list unless your repository is configured to override it to a single object — so you can traverse from an item to its referenced items in one query.

The generator also emits an AllContent union of every type in the repository (used by the items search query) and a set of standard types: Taxonomy, Category, GenericContent, and the QuerySettings input.

Generated queries follow consistent names:

Query Returns Purpose
get<Type>(id: ID, slug: String, settings: QuerySettings) <Type> One item of a specific type, by ID or slug
items(id: ID, slug: String, settings: QuerySettings) { count, items: [AllContent] } Search across the repository’s types
getItem(id: ID, slug: String, settings: QuerySettings) AllContent One item when you don’t know its type
types [String] The content type names in this repository
taxonomies(shortName: String) [Taxonomy] The repository’s taxonomies
getTaxonomy(shortName: String!) Taxonomy One taxonomy with its categories
getCategory(apiName: String!) Category One category by API name
tagCloudSearch(field: String!, label: String!, settings: QuerySettings) JSON Value counts for a field (for example, counts by type or status) over a search

CM Box’s naming convention reserves create<Type>, update<Type>, and delete<Type> for mutations. The generated schemas expose read queries only — create, update, and delete content through the REST API (see the REST API Reference section in the sidebar).

QuerySettings is the input object shared by all search-style queries:

input QuerySettings {
query: String
start: Int
limit: Int
filters: JSON
types: [String]
status: [String]
sort: String
advancedQuery: String
folder: String
version: String
showArchive: Boolean
showArchiveOnly: Boolean
packageName: String
packageOnly: Boolean
includeFolders: Boolean
}

Each field’s behavior is described in Query settings.

GraphQL requests are authorized the same way as REST requests: with a JWT sent as a bearer token (see Authentication for how to obtain one):

Authorization: Bearer <token>

Browser sessions can also present the JWT in a token cookie. What happens next depends on the repository:

  • Valid token — the request runs as your identity. Search results are filtered by the read permissions of your roles, and you can see draft content.
  • No valid token, public repository — repositories marked public accept anonymous requests. List queries (items) and reference-field traversal are forced to the latest published, non-archived versions of content. Do not rely on single-item queries to apply the same filtering — always pass version: "latestPublished" explicitly in anonymous integrations.
  • No valid token, private repository — resolvers return a GraphQL error with the code UNAUTHENTICATED and HTTP status 401.

Every save of an item creates a new version, and draft versions coexist with published ones. QuerySettings.version selects which version a query returns:

version value Meaning
omitted or "latest" The most recent version, including unpublished drafts — what an editor sees
"latestPublished" The most recent published version — what site visitors see
a version number, e.g. "1.0" That specific version

Choose deliberately: an integration that renders public content should pass "latestPublished", while an editorial tool usually wants the default latest. The lifecycle behind these states is explained in Content model.

Assuming a repository with an Article content type, getArticle returns a single item. This example requests only system fields, which exist on every generated type:

query ArticleById($id: ID, $settings: QuerySettings) {
getArticle(id: $id, settings: $settings) {
id
name
slug
status
currentVersion
publishedVersion
isLatestPublished
updatedDate
}
}
{
"id": "ITEM_5D41402ABC4B2A76B9719D911017C592",
"settings": { "version": "latestPublished" }
}

items searches across the repository and returns a count plus a page of results. Because it returns the AllContent union, select fields with inline fragments per type:

query PublishedArticles($settings: QuerySettings) {
items(settings: $settings) {
count
items {
... on Article {
id
name
slug
type
updatedDate
}
}
}
}
{
"settings": {
"query": "annual report",
"types": ["Article"],
"status": ["published"],
"start": 0,
"limit": 20,
"sort": "updatedDate:DESC"
}
}

If types is omitted, the search spans all of the repository’s content types (folders are excluded unless you ask for them — see Folders). When no full-text query is given and no sort is specified, results are sorted by updatedDate descending.