Skip to main content

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. articlePageArticlePage); fields are the field apiNames verbatim.
  • A list query {apiName}Collection (e.g. articlePageCollection). There is no per-type single query — use the generic entry(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 RichText type 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 { }.

note

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 LinkField is entry (not internal). System date fields are String, not a DateTime scalar.

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.