← Files BrainerceARCHIVED FILE

skills/brainerce-content-and-navigation/references/content-shapes.md

3.41 KB · Oct 4, 2026 · 12:21 UTC

↓ Download file

# Content payload shapes

Distilled from the Brainerce SDK type definitions. For a developer wiring
content into a storefront; a merchant never sees any of this.

Every entry is keyed on `(type, key)` per store and carries a `data` payload
whose shape depends on the type, plus a merchant-defined `customFields` map of
free-form string extras available on every row regardless of type.

```ts
type ContentType = 'FAQ' | 'FOOTER' | 'HEADER' | 'ANNOUNCEMENT' | 'RICH_TEXT' | 'PAGE';
type ContentStatus = 'DRAFT' | 'PUBLISHED';
```

Only `PUBLISHED` entries reach a storefront.

## ⛔ Sanitize before rendering

`RICH_TEXT.html`, `PAGE.html` and every FAQ `answer` are **merchant-authored
HTML that the server does not pre-sanitize**. That is deliberate: some merchants
embed iframes, a YouTube video for instance, which a strict sanitizer strips.
The policy is the storefront's to choose, which means the storefront has to
choose one. Use `isomorphic-dompurify` or equivalent, always, before injecting.

## The payloads

```ts
interface FaqContent {
  items: Array<{ question: string; answer: string }>; // answer is HTML
}

interface FooterContent {
  columns: Array<{ title: string; links: Array<{ label: string; url: string }> }>;
  copyright?: string;
  social?: Array<{ platform: string; url: string }>; // 'instagram' | 'facebook' | 'x' | ...
}

interface HeaderContent {
  logo?: { src: string; alt: string };
  navItems: Array<{ label: string; url: string }>;
  cta?: { label: string; url: string };
}

interface AnnouncementContent {
  message: string;
  severity: 'info' | 'warning' | 'success';
  dismissible: boolean;
  startsAt?: string; // ISO 8601
  endsAt?: string;   // ISO 8601
  ctaLabel?: string;
  ctaHref?: string;
}

interface RichTextContent {
  html: string; // raw, sanitize
}

interface PageContent {
  slug: string;  // lower-kebab
  title: string;
  html: string;  // raw, sanitize
  seo?: { title?: string; description?: string; ogImage?: string };
}
```

## Two things a storefront has to do that nothing does for it

**Filter the announcement window.** `startsAt` and `endsAt` are advisory: the
storefront filters on them client side. Ignore them and the banner runs forever,
which is how a Black Friday bar is still up in January.

**Route the pages.** A `PAGE` entry is not automatically served at its slug.
Fetch the entries, build the routes. A merchant reporting that their published
About page 404s has a routing gap, not a content problem.

## Navigation, assembled from two sources

There is no navigation entity. A menu is:

- **The category tree**, fetched as a nested structure of `{ id, name, slug,
  children[] }`. Each node links its own page at `/category/{slug}`. This is the
  browsable half.
- **`HEADER.navItems`**, a flat list of label and URL pairs. This is where links
  that are not categories go.

Render them together. Which of the two a given link belongs in is a merchant
decision, and `brainerce-store-architecture` covers how to make it.

## Category pages are the SEO surface

Worth knowing while wiring content: category landing pages rank for the broad
research queries ("running shoes") that individual product pages never capture.
`getCategoryBySlug` returns the merchant-authored landing-page description and
meta written in the dashboard SEO hub; the products come separately from
`getProducts({ categories: [id] })`. A storefront that renders a category as a
bare product grid with no copy is leaving that traffic on the table.

SHA-256: 018d6442df13545e73523e47b35e66c26ccc92cb2cc99345983aee8cf118a45a