Areca Docs

JavaScript API reference

Every export of @areca-cms/site-sdk and its subpaths, with signatures, parameters, return types, errors and examples.

The SDK has one main entry point and four subpath entry points. Import from the entry point that matches what you need. The SDK is ESM only, has no framework dependency, and needs Node.js 22.6 or later on the server. Most sites use the generated content object from areca.gen.ts, so they import these functions only when they need them. See typed content.

Keep the site token on the server

createArecaClient, pullContent, readContent, readContentManifest and createContent read server-only settings. The last four also use the file system. Do not import them into browser code, and never put a site token or schema token in a client bundle.

Entry points

Import pathExports
@areca-cms/site-sdkcreateArecaClient, defineSiteContent, manifestFromConfig, getPreviewEntry, previewHeaders, seoHead, seoHeadHtml, seoJsonLd, richTextDocument, richTextMarkdown, richTextHtml, and the types listed in Types
@areca-cms/site-sdk/pullpullContent, readContent, readContentManifest, and the types PullContentOptions, PullContentResult and ResolvedAssets
@areca-cms/site-sdk/contentcreateContent (Node only). Its types are exported from the main entry point.
@areca-cms/site-sdk/seoseoHead, seoHeadHtml, seoJsonLd, and the types HeadMeta, HeadLink, HeadScript, SeoHeadOptions, SeoHeadResult
@areca-cms/site-sdk/rich-textrichTextDocument, richTextMarkdown, richTextHtml

The main entry point does not export pullContent, readContent or createContent. Import them from @areca-cms/site-sdk/pull and @areca-cms/site-sdk/content. The CLI helpers and the internal modules are not part of the public API.

createArecaClient

Creates a client for the CMS's site API. Use it when you need to call the API from your own code. For most sites, areca pull and the helpers in /pull are enough.

function createArecaClient(options?: ArecaClientOptions): ArecaClient
ParameterTypeDefaultDescription
options.cmsUrlstringprocess.env.ARECA_CMS_URLCMS base URL. Trailing slashes are removed. The SDK does not check the protocol.
options.siteTokenstringprocess.env.ARECA_SITE_TOKENSite token, sent as a bearer token on reads (fetchManifest, fetchSnapshot). It is read when a read runs, not when the client is created.
options.schemaTokenstringprocess.env.ARECA_SCHEMA_TOKENSchema token, used only by syncManifest. It is read when that method runs, not when the client is created.

Returns: ArecaClient.

Throws: Error when cmsUrl is missing or empty after trimming. The message names the environment variable and the option:

Missing ARECA_CMS_URL. Set the ARECA_CMS_URL environment variable or pass cmsUrl to createArecaClient().

No token is needed to create the client. Each method checks the credential it sends, and fails before any request if that credential is missing. A schema-only client never needs ARECA_SITE_TOKEN, and a preview client needs no token but the preview token itself.

import { createArecaClient } from '@areca-cms/site-sdk'

// Reads ARECA_CMS_URL and ARECA_SITE_TOKEN from the environment.
const client = createArecaClient()
const snapshot = await client.fetchSnapshot()
console.log(snapshot.publicationId, Object.keys(snapshot.collections))

ArecaClient methods

Each method sends one request. There are no timeouts and no retries. Network failures and HTTP errors are thrown as Error. See Errors and limits for the full list of messages.

MethodRequestTokenReturns
syncManifest(config, options?)POST /api/sites/manifestSchema tokenPromise<{ ok: true }>
fetchManifest()GET /api/sites/manifestSite tokenPromise<SiteManifest>
fetchSnapshot()GET /api/sites/snapshot/currentSite tokenPromise<Snapshot>
fetchPreview(token)GET /api/sites/preview?token=...Preview token only (no Authorization header)Promise<PreviewResult>
fetchAsset(asset)GET /api/assets/file/{storageKey}NonePromise<Uint8Array>

syncManifest(config: SiteContentConfig) converts the config with manifestFromConfig and sends it. The siteKey is not sent; the CMS identifies the site by the token.

  • Throws if schemaToken is missing: Missing ARECA_SCHEMA_TOKEN. Set the ARECA_SCHEMA_TOKEN environment variable or pass schemaToken to createArecaClient().
  • The site token is never used as a fallback.
  • options.confirmDropFields lists the field removals the push confirms, as <key>.<field>. It is sent only when it is not empty.
  • Throws FieldDropsRequiredError when the CMS refuses the push because it would remove a field that still holds content. The error's drops lists each field with its collection or section (kind), key, field and entries. Nothing was changed. Send the same push again with those drops in confirmDropFields to accept them.
  • Throws Failed to sync manifest (<status>) with the CMS's error message, if any, appended.
  • Throws an identifier or duplicate-field error from manifestFromConfig before any request is made.

fetchManifest() returns the content definitions the CMS currently has for the site, as { collections }.

  • Throws Failed to fetch manifest (<status>) on a non-2xx response.

fetchSnapshot() returns the current publication. The CMS returns the publication of any active build first. If there is none, it returns the live publication, or the demo publication for a site in Demo mode.

  • Throws No publication exists for this site. Publish content in the CMS before running areca pull. on a 404. The CMS's message is appended when present.
  • Throws Failed to fetch snapshot (<status>) on other non-2xx responses.

fetchPreview(token) returns one draft entry for a signed preview link.

  • Throws Failed to fetch preview (<status>) on a non-2xx response. A 401 means the token is invalid or expired. A 404 means the entry no longer exists.
const client = createArecaClient()
const manifest = await client.fetchManifest()
for (const [key, collection] of Object.entries(manifest.collections)) {
  console.log(key, collection.route, collection.fields.length)
}

defineSiteContent

Returns the config object you pass in, with its literal types, so your editor and the TypeScript compiler check it. It does not read or send anything. Use it in areca.config.ts.

function defineSiteContent<
  const TCollections extends {
    [K in keyof TCollections]: CollectionInput<keyof TCollections & string, TCollections[K]>
  },
>(config: { siteKey: string; collections: TCollections; assets?: AssetsConfig }): {
  siteKey: string
  collections: TCollections
  assets?: AssetsConfig
}
ParameterTypeDescription
config.siteKeystringThe site key, as shown in the CMS.
config.collectionsobjectThe collection definitions. Field types, options, reference targets, after keys and routes are checked at compile time.
config.assetsAssetsConfig, optionalWhere areca pull copies media. Local only, never sent to the CMS.

Returns: the same object, typed with its literal values. It is assignable to SiteContentConfig, so manifestFromConfig and the CLI accept it.

Throws: nothing at run time. The compile-time rules are listed in authoring rules. Identifier and reserved-name rules are checked later, by manifestFromConfig, areca manifest push and areca pull.

areca.config.ts
import { defineSiteContent } from '@areca-cms/site-sdk'

export default defineSiteContent({
  siteKey: 'acme-site',
  collections: {
    authors: {
      route: '/authors/$slug',
      fields: [{ key: 'name', label: 'Name', type: 'text', required: true }],
    },
    articles: {
      route: '/blog/$slug',
      label: 'Articles',
      fields: [
        { key: 'title', label: 'Title', type: 'text', required: true },
        { key: 'author', label: 'Author', type: 'reference', collection: 'authors' },
      ],
    },
  },
})

manifestFromConfig

Converts a config into the request body that POST /api/sites/manifest expects. It drops siteKey and checks identifiers.

function manifestFromConfig(config: SiteContentConfig): SiteManifest
ParameterTypeDescription
configSiteContentConfigThe config to convert.

Returns: { collections }. Each collection keeps route, fields, and label only when it is set.

Throws:

  • Unsafe collection or field identifier: <value> when a collection key, field key, or a field's collection or after value is not a safe identifier.
  • Duplicate field identifier: <key> when two fields in one collection share a key.
  • Collection key "<key>" is reserved: resolve, href and preview cannot be collection keys when a collection is named resolve, href, preview, globals or previewGlobals, in any case.
import { manifestFromConfig } from '@areca-cms/site-sdk'
import config from './areca.config'

const body = manifestFromConfig(config)

pullContent

Downloads the current publication and writes it to a folder, together with areca.gen.ts. It is the function behind areca pull. Import it from @areca-cms/site-sdk/pull.

function pullContent(options: PullContentOptions): Promise<PullContentResult>
ParameterTypeDefaultDescription
options.outDirstringNone (required)Folder to write into. Created if it does not exist.
options.clientArecaClientcreateArecaClient()Client to use. Pass one in tests.
options.publicationIdstringARECA_SNAPSHOT_ID, else liveExact publication to pull.
options.assets{ dir: string; publicPath: string }NoneCopy media into the absolute folder dir and point the content at publicPath. Without it, media URLs point at the CMS. areca pull fills this in from areca.config.ts.
options.typesstring<outDir>/areca.gen.tsWhere to write the generated module. The CLI passes --types here.
options.contentDirstringoutDirThe folder written into the module as its default dir, relative to the app's working directory. The CLI passes --out as given.

Returns: a PullContentResult. It is the snapshot as the CMS sent it, with schema set to the schema that was used. It also has localAssets ({ dir, total, downloaded }) when media was copied, warnings (for example, when the CMS does not list media, or the publication has no embedded schema), and typesPath, the module that was written.

The function validates the publication against its schema before anything else. Then it downloads media, writes one <camelKey>.json file per collection into a new folder under .publications, and switches manifest.json to it in one rename. Last, it writes areca.gen.ts. It keeps the current folder and the two before it, and deletes older folders and the media files only they used.

Throws:

  • Every error from fetchSnapshot, including the "No publication exists" message.
  • Unsafe collection or field identifier: <key> when a key is not a safe identifier.
  • Collection keys <a> and <b> both map to "<name>", Field keys <a> and <b> in <collection> both map to "<name>", and Collection "<key>" uses the reserved name "<name>" when keys collide in camelCase or use a reserved name. Nothing is written.
  • Publication does not match its schema:, followed by one line per problem, up to 20. Nothing is written.
  • Output path escapes content directory when a target path leaves outDir.
  • Content output must not be a symbolic link when a target file is a symlink. Types output must not be a symbolic link does the same for the types file.
  • Failed to download N media file(s); the previous content is unchanged. followed by one line per file.
  • File-system errors, such as permission errors.

If the pull fails before the switch, manifest.json and areca.gen.ts still describe the previous complete publication, and the media files it had downloaded are removed.

import { pullContent } from '@areca-cms/site-sdk/pull'

const snapshot = await pullContent({ outDir: 'src/generated/content' })
console.log(snapshot.publicationId, snapshot.hash, snapshot.typesPath)

readContent

Reads one collection file that pullContent wrote. It is synchronous, so you can call it in a server loader or at build time. The content object uses it for every read.

function readContent<C extends CollectionKey>(
  outDir: string,
  collectionKey: C,
  manifest?: PulledContentManifest,
): Entry<C>[]
ParameterTypeDefaultDescription
outDirstringNone (required)The folder that pullContent wrote into.
collectionKeyCollectionKeyNone (required)The camelCase collection key, for example blogPosts. With a registered schema, other keys are a type error.
manifestPulledContentManifestRead from outDirA manifest captured with readContentManifest, so that several reads come from one publication.

Returns: the entries of the collection as Entry<C>[], in the pulled order. The type follows from the collection key, so there is no type parameter. An empty array is valid.

Throws:

  • Unsafe collection or field identifier: <key> when the key is not a safe identifier.
  • Collection "<key>" is not in the current publication when the manifest does not list the collection.
  • No content file for collection "<key>" in <outDir>. Run areca pull first. when the file is missing.
  • Output path escapes content directory or Content output must not be a symbolic link, as for pullContent.
  • A SyntaxError if the file is not valid JSON.
import { readContent } from '@areca-cms/site-sdk/pull'

const articles = readContent('src/generated/content', 'blogPosts') // Entry<'blogPosts'>[]

readContentManifest

Reads manifest.json from a content folder. Capture it once and pass it to several readContent calls, so that all of them come from the same publication.

function readContentManifest(outDir: string): PulledContentManifest

Returns: the parsed PulledContentManifest. Throws: the file-system error when manifest.json is missing, such as ENOENT.

createContent

Builds the typed content object for one schema and one folder. The generated areca.gen.ts calls it with the generated schema, so most code imports content instead. Use createContent only to bind another folder or another schema, for example in tests. Import it from @areca-cms/site-sdk/content, which is Node only.

import { createContent } from '@areca-cms/site-sdk/content'

function createContent<const S extends ContentSchema>(options: { schema: S; dir: string }): Content<S>
ParameterTypeDescription
options.schemaContentSchemaThe schema, as areca.gen.ts declares it.
options.dirstringThe content folder. It is resolved against process.cwd() on each call.

Returns: the Content<S> object described below.

Throws: Collection "<name>" uses a reserved name (resolve, href, preview) when the schema has a collection with one of those names.

The content object

content has one property for each collection, and three methods of its own:

MemberDescription
content.<collection>The collection's methods: all, first, get, byId and href.
content.resolve(ref)The entry that a reference points to, or null.
content.href(ref)The route of a reference's target, or of an entry.
content.preview(token, options?)A draft entry for a preview link.

Collection methods

MethodReturnsDescription
all(options?)QueryResult<C, O>[]The entries that match where, sorted by orderBy, and sliced by offset and limit.
first(options?)QueryResult<C, O> | undefinedThe first entry of all(options).
get(slug)Entry<C> | undefinedThe entry with this slug.
byId(id)Entry<C> | undefinedThe entry with this ID.
href(entryOrSlug)Href<C>The route for an entry, or for a slug.

all and first take the query options below. get and byId return undefined for an unknown value. They do not throw.

Query options

OptionTypeDescription
whereWhere<C>Keys are the collection's field keys. Several keys are combined with AND.
orderByOrderBy<C>An object { field: 'asc' | 'desc' }, or an array of them for tie-breakers, applied in order. Only sortable fields are accepted.
limitnumberThe maximum number of entries.
offsetnumberThe number of entries to skip first.
with{ [field]?: true }Expands reference fields one level deep. Only reference fields are accepted.

A direct value in where means equality. An operator object combines operators. Each field type accepts only the operators in this table:

Field typeOperators
text, textarea, url, slugeq, ne, in, notIn, contains (case-insensitive), isNull when optional
selecteq, ne, in, notIn, isNull when optional. in and notIn take the options, when the field has them.
date, datetimeeq, ne, gt, gte, lt, lte, isNull when optional
numbereq, ne, gt, gte, lt, lte, isNull when optional
booleaneq, ne, isNull when optional
image, richtext, referenceisNull, when optional
  • isNull: true matches null, '' and a missing key. isNull: false matches the rest.
  • gt, gte, lt and lte never match an empty value. ne and notIn do match one.
  • datetime values compare by the instant they name, so offsets do not matter. date values compare as text.
  • contains only matches text fields. It ignores case.

Sorting uses the same comparisons. Empty values sort last in both directions, and entries that tie on every key keep their pulled order. Without orderBy, the order is the pulled order, which is newest publishedAt first from the CMS.

with returns a copy of each entry. A field that is required and expanded is Entry | null. An optional one is Entry | null | undefined, because a missing value stays undefined.

resolve, href and preview

content.resolve(ref) // Entry<C> | null
content.href(ref) // Href<C>
await content.preview(token, { client }) // PreviewEntry<S>
  • resolve(ref) accepts a ReferenceValue<C>, or '', null or undefined. It finds the entry by id in the target collection. It returns null for an empty value or for an entry that is not in the pulled publication.

  • href(ref) accepts a ReferenceValue<C> or an entry. It replaces $slug in the target's route with the slug, URL-encoded. The result is typed as a template literal, such as `/blog/${string}`.

  • preview(token, options?) calls getPreviewEntry, using the same options. It validates the raw draft against the schema with the same rules as a pull, then maps the raw collection key to its camelCase name and converts the entry the same way. The result is a PreviewEntry<S>, a union narrowed by collection and complete:

    • complete: true: entry is an Entry<C, S> and issues is [].
    • complete: false: entry is a DraftEntry<C, S>, where every field has its optional type, and issues is a list of <camelField>: <problem> strings, such as editor: is required.

    An incomplete draft is returned, not thrown. A collection that is not in the generated schema throws Preview collection "<key>" is not in the generated schema. Run areca pull to refresh the types.

Run-time behaviour

  • content reads manifest.json in dir on every call. This is cheap. The parsed collections are cached by publication generation, so a new pull is picked up without restarting the process.
  • A collection that the publication does not contain returns [].
  • If there is no manifest.json, the call throws No content in <dir>. Run areca pull first.
  • dir is resolved against process.cwd() at call time.
  • Queries never change the cached data. with and sorting work on copies.
  • An unknown operator in where throws Unknown where operator "<name>".

getPreviewEntry

Fetches a draft entry for a preview page. Call it on the server, from the route that handles the preview link.

function getPreviewEntry(options: { token: string; client?: ArecaClient }): Promise<PreviewResult>
ParameterTypeDefaultDescription
options.tokenstringNone (required)The signed preview token from the preview link.
options.clientArecaClientcreateArecaClient()Client to use. The default reads the environment on each call.

Returns: { collection, slug, entry }.

Throws: the same errors as fetchPreview, such as Failed to fetch preview (401): Unauthorized for an invalid or expired token.

import { getPreviewEntry, previewHeaders } from '@areca-cms/site-sdk'

const preview = await getPreviewEntry({ token })
// Set these on the response that renders the preview.
const headers = previewHeaders()

previewHeaders

Returns the headers that keep a preview page out of search engines and out of caches.

function previewHeaders(): { 'X-Robots-Tag': 'noindex, nofollow'; 'Cache-Control': 'no-store' }

Parameters: none. Each call returns a new object.

Returns: the two headers above. The SDK does not set them for you. Your framework has to send them on the preview response.

seoHead

Turns an entry's seo object into the parts of a page head: a title, meta tags, links and JSON-LD scripts. Import it from the main entry or from @areca-cms/site-sdk/seo.

function seoHead(seo: SeoMeta | null | undefined, options?: SeoHeadOptions): SeoHeadResult
ParameterTypeDefaultDescription
seoSeoMeta or null or undefinedNone (required)The seo value from an entry. Older snapshots do not have it.
options.titleTemplatestring or (title: string) => stringNo templateA string template where the first %s is replaced with the title, or a function that returns the final title.
options.siteNamestringseo.openGraph.siteNameOverrides og:site_name.

Returns: a SeoHeadResult. When seo is null or undefined, the result is { title: undefined, meta: [], links: [], scripts: [] }.

The result contains:

  • title: the page title with the template applied. It is also the { title } entry in meta.
  • meta: description, robots, Open Graph, article and Twitter tags. Blank and null values are skipped.
  • links: a canonical link when canonicalUrl is set.
  • scripts: one application/ld+json script for each object in seo.jsonLd.

Throws: nothing.

import { createFileRoute } from '@tanstack/react-router'
import { seoHead } from '@areca-cms/site-sdk/seo'

export const Route = createFileRoute('/blog/$slug')({
  head: ({ loaderData }) => {
    const head = seoHead(loaderData.entry.seo, { titleTemplate: '%s · Acme' })
    return { meta: head.meta, links: head.links, scripts: head.scripts }
  },
})

seoHeadHtml

Does what seoHead does, and returns the result as an HTML string. Use it when your framework takes raw HTML for the head.

function seoHeadHtml(seo: SeoMeta | null | undefined, options?: SeoHeadOptions): string

Takes the same parameters as seoHead.

Returns: the title, meta, link and script tags, one per line. Text and attribute values are HTML-escaped. JSON-LD is escaped so it cannot close its <script> tag early. For null input the result is an empty string.

Throws: nothing.

seoJsonLd

Returns the entry's JSON-LD objects as escaped JSON strings, ready to put inside <script type="application/ld+json">.

function seoJsonLd(seo: SeoMeta | null | undefined): string[]

Returns: one string per object in seo.jsonLd. Returns [] for null, undefined, or a missing list. Each string is safe to place inside a script element.

Throws: nothing.

richTextDocument

Returns the parsed document of a richtext value, ready to render with @tanstack/markdown.

function richTextDocument(value: RichTextValue | null | undefined): MarkdownDocument
ParameterTypeDescription
valueRichTextValue or null or undefinedThe field value from an entry.

Returns: the document from the value. For older snapshots that only have markdown, the SDK parses the Markdown. For null or undefined, it returns an empty root node.

Throws: nothing.

import { Markdown } from '@tanstack/markdown/react'
import { richTextDocument } from '@areca-cms/site-sdk/rich-text'
import type { RichTextValue } from '@areca-cms/site-sdk'

export function ArticleBody({ body }: { body: RichTextValue }) {
  return <Markdown>{richTextDocument(body)}</Markdown>
}

The document type comes from @tanstack/markdown, which the SDK pins to an exact version. Use the same version of @tanstack/markdown in your site so the renderer and the document format match.

richTextMarkdown

function richTextMarkdown(value: RichTextValue | null | undefined): string

Returns: value.markdown, or '' for null and undefined.

richTextHtml

function richTextHtml(value: RichTextValue | null | undefined): string

Returns: value.html, or '' for null and undefined. The HTML is rendered by the CMS and is not sanitized again by the SDK. Only render it when you trust the content.

Types

These types are exported from the main entry point, and most of them describe the typed content object. Types from the subpaths are listed with their functions above. The generated module registers the schema with Register, so the types below know your collections and fields without an import from the generated file.

TypeDescription
RegisterAn empty interface that areca.gen.ts augments with schema.
ContentSchema, ContentSchemaCollection, ContentSchemaFieldThe shape of a schema, as the generated module declares it.
RegisteredSchemaThe registered schema, or ContentSchema when nothing is registered.
CollectionKey<S>The camelCase keys of the collections.
FieldKey<C, S>The field keys of collection C.
FieldValue<C, F, S>The value type of one field. See field types.
EntryData<C, S>The data of an entry of collection C.
Entry<C, S>One entry of collection C. Its collection is C.
AnyEntry<S>An entry of any collection. Narrow it with collection.
ReferenceValue<C>A reference to collection C. Without a type argument, C is string.
Where<C, S>, OrderBy<C, S>, QueryOptions<C, S>, QueryResult<C, O, S>The options of all and first, and the entries they return.
RoutePath<R>, Href<C, S>A route with $slug replaced by ${string}, and the route of a collection.
Content<S>, CollectionApi<C, S>The content object, and the methods of one collection.
DraftEntryData<C, S>, DraftEntry<C, S>The data and entry of an incomplete preview draft. Every field has its optional type.
PreviewEntry<S>The result of content.preview: a complete Entry with no issues, or a DraftEntry with issues. Narrow it with collection and complete.
SiteContentConfigThe shape of areca.config.ts: siteKey and collections.
CollectionConfigOne collection: route, optional label, and fields.
FieldOne field definition, as a union discriminated on type.
FieldTypeThe 12 field type names. See Field types.
ArecaClientThe object returned by createArecaClient.
ArecaClientOptionsOptions for createArecaClient.
SiteManifestThe request and response body for /api/sites/manifest: { collections }.
SnapshotThe publication: publicationId, siteKey, createdAt, hash, collections, and the optional assets, siteUrl and schema.
SnapshotEntryOne entry as the CMS sent it: id, slug, publishedAt, data, and optional seo.
PreviewResult{ collection, slug, entry } from getPreviewEntry.
PulledContentManifestThe shape of manifest.json written by pullContent.
RichTextValueA richtext value: json, markdown, document and html.
ImageValueAn image value: url, width, height, alt and mimeType.
SeoMetaThe seo object on an entry.
HeadMeta, HeadLink, HeadScript, SeoHeadOptions, SeoHeadResultTypes for the SEO helpers. Also exported from @areca-cms/site-sdk/seo.
interface Register { schema: typeof schema } // set by areca.gen.ts
type Entry<C extends string, S = RegisteredSchema> = {
  id: string
  slug: string
  publishedAt: string | null
  collection: C
  data: EntryData<C, S>
  seo?: SeoMeta
}
type ReferenceValue<C extends string = string> = { collection: C; slug: string; id: string }

type SiteContentConfig = {
  siteKey: string
  collections: Record<string, CollectionConfig>
  assets?: AssetsConfig // local only, never sent to the CMS
}
type AssetsConfig = {
  download?: boolean   // default true
  dir?: string         // default 'public/areca'
  publicPath?: string  // derived for folders under public/
}
type CollectionConfig = { route: string; label?: string; fields: Field[] }
type Field =
  | { key: string; label: string; type: 'text' | 'textarea' | 'richtext' | 'image' | 'slug' | 'boolean' | 'number' | 'url'; required?: boolean; description?: string }
  | { key: string; label: string; type: 'select'; options?: string[]; required?: boolean; description?: string }
  | { key: string; label: string; type: 'reference'; collection: string; required?: boolean; description?: string }
  | { key: string; label: string; type: 'date' | 'datetime'; after?: string; required?: boolean; description?: string }

type SiteManifest = { collections: Record<string, CollectionConfig> }

type Snapshot = {
  publicationId: string
  siteKey: string
  createdAt: string
  hash: string
  collections: Record<string, SnapshotEntry[]>
  assets?: SnapshotAsset[]  // missing from older CMS versions
  siteUrl?: string | null   // the site's production URL
  schema?: Record<string, CollectionConfig> // raw keys; missing from publications made before schemas were embedded
}

type SnapshotAsset = {
  id: string
  storageKey: string // content URLs end in /api/assets/file/<storageKey>
  url: string
  filename: string
  mimeType: string
  size: number
}

type SnapshotEntry = {
  id: string
  slug: string
  publishedAt: string | null
  data: Record<string, unknown> // the raw field values, keyed by the raw field keys
  seo?: SeoMeta               // missing in snapshots from older CMS versions
}

type PreviewResult = { collection: string; slug: string; entry: SnapshotEntry }

type PulledContentManifest = {
  publicationId: string
  hash: string
  createdAt: string
  collections: Record<string, number> // entry counts per camelCase collection key
  generation?: string                 // folder under .publications
  assets?: { publicPath: string; count: number } // when media was copied
}

type RichTextValue = {
  json: unknown
  markdown: string
  document: MarkdownDocument
  html: string
}

type ImageValue = {
  url: string
  width: number | null
  height: number | null
  alt: string
  mimeType: string
}

SeoMeta has these top-level keys: title, description, canonicalUrl, robots, focusKeyphrase, openGraph, twitter, author, summary, keyTakeaways, faq, about, and jsonLd. The CMS fills them in. The SDK does not render summary, keyTakeaways or faq. Render them on the page yourself, as SEO explains.

SnapshotEntry is the raw entry. The typed entries of your site are Entry<C>, from content or readContent. See typed content.

On this page