Reading content
Four methods read published content: get for one document, list for one folder, tree for a folder's whole subtree as navigation, and walk for every document under a folder with its body.
Why a site asks for a page or a folder
A site asks two kinds of question. "Show me this page" is about one document, found by its key. "What pages are there" is about a folder, and a site asks it for a menu, a sitemap or a build that makes one page per document. The four methods answer those questions in the shapes a site uses. Nobody has to write a loop over listing pages, or rebuild a folder tree by hand from slash-separated keys.
If you are a content manager, think of the key as the document's address, such as docs/intro. Its folder is the part before the last slash. A folder's title and order are set in its _index, and once you publish them they change what these methods answer.
What get, list, tree and walk answer
get(key, options) answers with one published document. The answer holds its key, the locale asked for, its type (json, md or text), its content, and _meta, which says how the answer was made:
_meta.resolvedLocaleis the locale that answered, andfallbackLocaleis the one that filled a gap, ornull.fallbackslists what came from that other locale: field paths for a structured document,*for a whole body._meta.versionis the version served,publishedAtis when it went live, andcontentHashis a hash of the body as served._meta.refslists every reference delivery met, each with itspath,keyandversion, or withmissing: truewhen it names nothing published._meta.assetsdoes the same for files.
The method takes these options: locale; refs, the number of levels of references to inline, 0 to 5; at, an instant to read as of, which is never stored and throws a NotFoundError before the document's first publish; preview, a preview token; and vars and missing, for variables.
A reference looks like { "$ref": "components/spring" }. Delivery replaces it with the published body of the document it names, in the same locale, as deep as refs allows. refs: 0 leaves every reference as written, and isRef from @quilletcms/sdk tells a reference apart from content. Files are inlined at every depth, whatever refs says; Assets covers them.
list(prefix, options) answers with one folder. Its own settings come back as folder, its subfolders as folders, and its documents as items. Each item has key, title, type, order, tags, schema, version, publishedAt and contentHash, but no body. A listing reaches one level below the prefix unless depth (1 to 10) says otherwise. The tags option keeps only the items that carry every tag named.
tree(prefix, options) reads the whole subtree and nests it. The result is a FolderNode with path, title, description, order, its items, and children, sorted by order and then by path. A node's path has no trailing slash, so tree('docs/') answers docs, and tree('') answers ''.
walk(prefix, options) reads every document under the prefix, at any depth unless depth stops it, and yields { item, document } in listing order. It keeps four documents in flight at a time, and concurrency changes that number.
A key must be lower-case segments joined by slashes. A prefix must be empty or end in a slash. The SDK checks both before it spends a request, and on a bad one it throws a QuilletError with code invalid_key or invalid_prefix. _singletons/<name> is a key like any other.
Errors
Every error the SDK throws is a QuilletError. It carries status, code (the API's own error code, or one of the SDK's) and details. Four subclasses name the common cases:
| Class | When | status |
|---|---|---|
NotFoundError |
The key is not published in this locale or a fallback, or the route does not exist, such as search on a project with search off. | 404 |
UnauthorizedError |
The key is wrong or revoked (401, "Invalid API key."), or cannot read this environment (403). | 401 or 403 |
RateLimitedError |
The CMS still answered 429 after the retries; retryAfter holds its seconds. |
429 |
NetworkError |
No answer at all: the CMS is down or the address is wrong. | 0 |
Anything else, such as a 422 for a bad option, is a plain QuilletError, and its details.fields names the field at fault.
How to
Read one document and tell the SDK what shape its body has. The SDK takes your word for it and does not check the body against that shape:
// page.ts: one structured document and how delivery answered it.
import { quillet } from '@quilletcms/sdk/node'
interface Spring {
title: string
hero: { headline: string }
}
const q = quillet()
const page = await q.get<Spring>('pages/spring')
console.log(page.content.title, '/', page.content.hero.headline)
console.log('version', page._meta.version, 'published', page._meta.publishedAt)
for (const ref of page._meta.refs) {
console.log('reference at', ref.path, '->', ref.key, ref.missing ? '(missing)' : `v${ref.version}`)
}
Ask for a locale, and tell the reader when the text came from another one:
// locale.ts: a German read that may be answered in English.
import { quillet } from '@quilletcms/sdk/node'
const q = quillet()
const intro = await q.get<string>('docs/intro', { locale: 'de-DE' })
if (intro._meta.fallbackLocale !== null) {
console.log(`Not translated yet; shown in ${intro._meta.resolvedLocale}.`)
}
Build a menu from a folder's tree. The titles and the order of the folders come from each folder's _index:
// nav.ts: the docs/ folder as nested lists of links.
import type { FolderNode } from '@quilletcms/sdk'
import { quillet } from '@quilletcms/sdk/node'
const q = quillet()
function menu(node: FolderNode, indent = ''): string[] {
const lines = [`${indent}${node.title ?? node.path}`]
for (const item of node.items) lines.push(`${indent} /${item.key}/ ${item.title ?? item.key}`)
for (const child of node.children) lines.push(...menu(child, `${indent} `))
return lines
}
console.log(menu(await q.tree('docs/')).join('\n'))
A static build makes one page per document. Each step of this loop gets both the listing item and the full document:
// build.ts: every Markdown document under docs/, rendered.
import { quillet } from '@quilletcms/sdk/node'
const q = quillet()
for await (const { item, document } of q.walk<string>('docs/')) {
if (document.type !== 'md') continue
const html = await q.renderMarkdown(document.content)
console.log(item.key, item.title, `${html.length} bytes of HTML`)
}
Leave references as written when you want to resolve them yourself, or to show what a page points at:
// refs.ts: the page with its references untouched.
import { isRef } from '@quilletcms/sdk'
import { quillet } from '@quilletcms/sdk/node'
const q = quillet()
const page = await q.get<Record<string, unknown>>('pages/spring', { refs: 0 })
for (const [field, value] of Object.entries(page.content)) {
if (isRef(value)) console.log(field, 'points at', value.$ref)
}
Turn a missing document into your site's 404 page. Let every other error through:
// route.ts: a 404 for an unknown key, and a 503 when the CMS is unreachable.
import { NetworkError, NotFoundError } from '@quilletcms/sdk'
import { quillet } from '@quilletcms/sdk/node'
const q = quillet()
export async function render(key: string): Promise<{ status: number; body: string }> {
try {
const doc = await q.get<string>(key)
return { status: 200, body: await q.renderMarkdown(doc.content) }
} catch (error) {
if (error instanceof NotFoundError) return { status: 404, body: 'No such page.' }
if (error instanceof NetworkError) return { status: 503, body: 'Try again in a moment.' }
throw error
}
}
Limits
- Every read is of published content. A draft needs a preview token, and
walkandlistshow drafts only when given one. - The type you give
get<T>is a claim, not a check. Types generates it from your components, but nothing validates a body at run time. - References are inlined five levels deep at most, and at most 200 of them in one answer, counting all levels together. Past that budget the rest are left as
{ "$ref" }, and_meta.refsTruncatedistrue. - A reference to a document that is not published comes back as
nullin the body, and its entry in_meta.refshasmissing: true. Your page has to cope with thenull. listreaches 10 levels at most in one request.walkandtreefollow deeper folders with further listings, andlistdoes not.- You cannot combine
atandpreview. The CMS answers 422, "Choose a preview or an instant, not both." The SDK passes both through rather than checking for you. _indexdocuments hold a folder's settings, not content. They appear asfolderandfolders, never as items. A prefix under_schemas/is refused.
Related
- Documents, keys and folders: why keys have slashes and folders are implied.
- Locales and fallback: what
_metasays about a translation. - Inline and referenced components: when a body holds a reference.
- Delivery API: the routes behind these methods.
- Delivery: why reads are cached, and what a publish clears.
- Assets: the files inlined into a body.
- Static sites:
treeandwalkin a real build.