GraphQL Reference
POST /api/graphql/{projectSlug}
Authorization: Bearer <delivery-or-preview-or-management-key>
Content-Type: application/json
Schema generation
Krios generates a GraphQL schema per project from the project's content types. For each content type:
- A type named in PascalCase of the content type's
apiName(e.g.articlePage→ArticlePage); fields are the fieldapiNames verbatim. - A list query
{apiName}Collection(e.g.articlePageCollection). There is no per-type single query — use the genericentry(id)/entryBySlug(slug, site, locale). - Reference / blocks fields resolve to the linked types (a synthesized union when multiple types are allowed).
- Rich text fields resolve to a
RichTexttype with{ raw, html, text }.
Root queries
entry(id: ID!): ContentEntry
entryBySlug(slug: String!, site: String!, locale: String!, preview: Boolean): ContentEntry
{apiName}Collection(
site: String, locale: String, preview: Boolean,
limit: Int, skip: Int, where: …, orderBy: {Type}OrderBy
): {Type}Collection!
resolveRoute(site: String!, locale: String!, path: String!): RouteResolution
asset(id: ID!, locale: String): MediaAsset
tree(site: String): [TreeNode!]!
taxonomy(name: String!): [TaxonomyTerm!]!
entry and entryBySlug return the ContentEntry interface — read typed fields through ... on TypeName { }.
where and orderBy exist in the SDL but the resolver does not apply them yet — every collection sorts by _updatedAt descending. orderBy is an enum ({Type}OrderBy, e.g. _updatedAt_DESC, title_ASC), not an input object. Sort/filter client-side until full enforcement lands.
Built-in types
scalar JSON # the only custom scalar; system date fields are String
type RichText {
raw: JSON!
html: String!
text: String!
}
type LinkField {
mode: String! # internal | external | media | email | phone | anchor
text: String
title: String
variant: String
target: String! # default "_self"
url: String! # server-resolved href, ready for <a href>
entry: ContentEntry # resolved target — non-null only when mode = internal
asset: MediaAsset # non-null only when mode = media
}
type MediaAsset {
id: ID!
filename: String!
mimeType: String!
fileSize: Int!
kind: String!
width: Int
height: Int
duration: Int
focalPointX: Float
focalPointY: Float
altText: String # locale-resolved from the MediaAssetLocale overlay
title: String # request locale; null when no overlay row exists
description: String
tags: [String!]!
isPublic: Boolean!
url(width: Int, height: Int, format: String, quality: Int): String! # format ∈ jpeg|png|webp|avif
}
type RouteResolution {
entryId: ID
contentTypeApiName: String
isRedirect: Boolean!
redirectTarget: String
redirectStatus: Int
status: String!
}
interface ContentEntry {
_id: ID!
_type: String!
_site: Site
_slug: String
_locale: String!
_status: String!
_createdAt: String!
_updatedAt: String!
_publishedAt: String
}
The resolved-target field on
LinkFieldisentry(notinternal). System date fields areString, not aDateTimescalar.
Queries
# Single entry (interface → inline fragment)
query GetArticle($id: ID!) {
entry(id: $id) {
_id
_publishedAt
... on ArticlePage {
title
body { raw html text }
}
}
}
# List with pagination (skip/limit). orderBy is not yet honored — sort client-side.
query ListArticles {
articlePageCollection(limit: 25, skip: 0) {
total
skip
limit
items {
_id
title
excerpt
}
}
}
# Polymorphic blocks (fetch by slug, narrow with a fragment)
query Landing($slug: String!, $site: String!, $locale: String!) {
entryBySlug(slug: $slug, site: $site, locale: $locale) {
... on LandingPage {
title
blocks {
__typename
... on HeroBlock { headline ctaText }
... on CtaBlock { headline buttonText }
}
}
}
}
# Resolve a route (single object — not a union)
query ResolveRoute($site: String!, $locale: String!, $path: String!) {
resolveRoute(site: $site, locale: $locale, path: $path) {
entryId
contentTypeApiName
isRedirect
redirectTarget
redirectStatus
status
}
}
Limits
- Depth: 10
- Complexity: 1000 (a hard limit today; per-API-key overrides are planned, V1.5)
- Reference recursion: 3 hops; in GraphQL, deeper traversals resolve to
null(single) or[](list). The{ _ref, _type }placeholder is REST-delivery only.
Over-deep / over-complex queries return 400 with the violation in the body.
Introspection
Disabled for delivery keys, enabled for management and preview keys. To explore the schema in GraphiQL or codegen, use a preview / management key.
Caching
Same Cache-Control + Surrogate-Key headers as REST. POST requests with read-only bodies are cacheable. Preview-key responses set Cache-Control: private, no-store.
Type generation
krios types generate --output ./src/krios.types.ts
Generates TypeScript interfaces matching the GraphQL schema. Usable with the SDK as type parameters or as ambient types in your queries.