> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# The page document

> The top-level JSON shape — version, metadata, schema, tree — and how a document moves from draft to published.

export const APIBadge = ({method = "GET", path, scope}) => {
  const m = method.toUpperCase();
  return <span className="gv-api-badge">
      <span className={`gv-api-badge__method gv-api-badge__method--${m.toLowerCase()}`}>
        {m}
      </span>
      <code className="gv-api-badge__path">{path}</code>
      {scope && <span className="gv-api-badge__scope">
          <span className="gv-api-badge__scope-label">scope</span>
          <code>{scope}</code>
        </span>}
    </span>;
};

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const FeatureTable = ({columns, rows, featuredRow, caption, variant}) => <div className={`gv-feature-table ${variant ? `gv-feature-table--${variant}` : ""}`}>
    <table>
      {caption && <caption className="gv-feature-table__caption">{caption}</caption>}
      <thead>
        <tr>
          {columns.map(col => <th key={col.key} scope="col">
              <span className="gv-feature-table__header">{col.label}</span>
            </th>)}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, i) => {
  const rowClass = [i === featuredRow ? "gv-feature-table__row--featured" : "", row.chosen ? "gv-feature-table__row--chosen" : ""].filter(Boolean).join(" ");
  return <tr key={i} className={rowClass}>
              {columns.map(col => {
    const v = row[col.key];
    if (variant === "tradeoff" && Array.isArray(v)) {
      const pill = col.key === "pros" ? "gv-feature-table__pill--pro" : col.key === "cons" ? "gv-feature-table__pill--con" : "";
      return <td key={col.key}>
                      {v.map((item, j) => <span key={j} className={`gv-feature-table__pill ${pill}`}>{item}</span>)}
                    </td>;
    }
    return <td key={col.key}>{v}</td>;
  })}
            </tr>;
})}
      </tbody>
    </table>
  </div>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const Explanation = ({children}) => <div className="gv-code-explainer__explanation">{children}</div>;

export const Code = ({children}) => <div className="gv-code-explainer__code">{children}</div>;

export const CodeExplainer = ({eyebrow, title, children}) => <div className="gv-code-explainer">
    {(eyebrow || title) && <header className="gv-code-explainer__header">
        {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
        {title && <h3 className="gv-code-explainer__title">{title}</h3>}
      </header>}
    <div className="gv-code-explainer__grid">{children}</div>
  </div>;

export const NumberedStep = ({n, title, children}) => <li className="gv-step">
    <span className="gv-step__index">{n}</span>
    <div className="gv-step__body">
      <h3 className="gv-step__title">{title}</h3>
      <div className="gv-step__content">{children}</div>
    </div>
  </li>;

export const NumberedSteps = ({children}) => <ol className="gv-steps">{children}</ol>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="MODEL" />

A gavAI page is one JSON document. Fetch it, edit it locally, patch it back, publish it — no build, no deploy. By the end of this page you will understand the four top-level fields (`version`, `metadata`, `schema`, `tree`), what each one is for, and how a draft becomes a live page.

## The shape

<CodeExplainer>
  <Code>
    ```json page.json theme={null}
    {
      "version": "v0.4",
      "metadata": {
        "slug": "landing",
        "title": "Acme Co."
      },
      "schema": {
        "leads": {
          "fields": {
            "id":    { "type": "uuid", "primary": true },
            "name":  { "type": "string", "required": true },
            "email": { "type": "string", "required": true }
          }
        }
      },
      "tree": {
        "block": "NavShell",
        "props": { "brand_name": "Acme Co.", "variant": "topbar", "items": [] },
        "children": [
          { "block": "Hero", "props": { "headline": "Welcome to Acme" } },
          { "block": "Footer", "props": { "copyright": "© 2026 Acme Co.", "groups": [] } }
        ]
      }
    }
    ```
  </Code>

  <Explanation>
    `version` identifies the document schema revision. The runtime uses it to apply any necessary migrations before rendering.

    `metadata` carries the page's public identity — the URL slug and the HTML `<title>`. Optional SEO fields nest here.

    `schema` declares the data tables this page reads and writes. Data-bound blocks (Form, Table, DetailPanel, KanbanBoard) reference table names declared here.

    `tree` is the block tree. A single root block — typically NavShell — wraps every section as nested `children`.
  </Explanation>
</CodeExplainer>

## Why JSON, not code

The document being data is the whole product. Three things follow from it:

<ArrowList>
  <ArrowItem>**Portability** — a page is one file. Move it between workspaces, version it in git, snapshot it for rollback, or generate it from a template.</ArrowItem>
  <ArrowItem>**AI legibility** — a language model can read a tree, propose a JSON Patch, and round-trip an edit without hallucinating a build step. The Builder MCP relies on this.</ArrowItem>
  <ArrowItem>**Multi-renderer** — the same document drives SSR on first load and a WebAssembly hydration bundle for interactivity. One source of truth, two delivery modes.</ArrowItem>
</ArrowList>

## Field reference

<DefList>
  <Def term="version" type="string" required>
    Identifies the document schema revision (e.g. `"v0.4"`). Pins the document against silent reinterpretation across revisions. The API sets this for you on create — leave it alone unless you are intentionally targeting a different revision.
  </Def>

  <Def term="metadata.slug" type="string" required>
    URL path at which the page is served. `"landing"` → `/landing`.
  </Def>

  <Def term="metadata.title" type="string" required>
    HTML `<title>` tag value.
  </Def>

  <Def term="schema" type="object">
    Table declarations for data-bound blocks. Empty object on presentational pages.
  </Def>

  <Def term="tree" type="block" required>
    The root block. Typically `NavShell`, with sections as `children`.
  </Def>
</DefList>

Optional SEO fields may nest alongside `slug` and `title`. To see the current field set for your runtime version, `GET` an existing page or check the Builder's metadata panel.

### schema

`schema` declares the data tables for blocks that bind to data. Four blocks reference tables declared here:

<FeatureTable
  columns={[
{ key: "block", label: "Block" },
{ key: "prop", label: "Schema prop" },
{ key: "purpose", label: "Purpose" }
]}
  rows={[
{ block: "`Form`", prop: "`schema_table`", purpose: "Writes a new row on submit." },
{ block: "`Table`", prop: "`schema_table`", purpose: "Reads rows for the paginated list." },
{ block: "`DetailPanel`", prop: "`schema_table`", purpose: "Reads (and optionally writes) one row." },
{ block: "`KanbanBoard`", prop: "`schema_table`", purpose: "Reads rows; writes the `group_by` field on drag." }
]}
/>

The runtime validates every read and write against the field shapes you declare. On purely presentational pages — Hero, FeaturesGrid, Cta, Footer, SectionDivider — leave `schema` as an empty object. See [Schema](/concepts/schema) for the full table and field declaration model.

### tree

`tree` holds the root block. For almost all pages the root is a NavShell, which provides the nav chrome and renders its `children` array in the main content area. Children can be any other block type. NavShell does not nest inside itself.

A minimal tree:

```json theme={null}
{
  "block": "NavShell",
  "props": { "brand_name": "Acme", "variant": "topbar", "items": [] },
  "children": [
    { "block": "Hero",   "props": { "headline": "Hello" } },
    { "block": "Footer", "props": { "copyright": "© 2026", "groups": [] } }
  ]
}
```

Each node names a block type in `block`, carries its configuration in `props`, and may contain further nested blocks in `children`. The structure is recursive — any block that accepts children can nest further sections.

## Document lifecycle

A page document moves through four stages, draft to served.

<NumberedSteps>
  <NumberedStep n="01" title="Draft">
    Create or fetch a page. <APIBadge method="GET" path="/v1/workspaces/:slug/pages/:id" scope="pages:read" /> returns the current draft. Changes you apply — via the Builder UI, the CLI, or the API — write to the draft and do not affect the live page.
  </NumberedStep>

  <NumberedStep n="02" title="Patch">
    Apply RFC 6902 JSON Patch operations to the draft via <APIBadge method="PATCH" path="/v1/workspaces/:slug/pages/:id" scope="pages:write" />. Each call updates one or more paths in the tree. See [Editing pages with JSON Patch](/pageblocks/json-patch).
  </NumberedStep>

  <NumberedStep n="03" title="Publish">
    Promote the draft to live with <APIBadge method="POST" path="/v1/workspaces/:slug/pages-publish" scope="pages:publish" />. The runtime creates a version snapshot; the previous live version remains accessible for rollback.
  </NumberedStep>

  <NumberedStep n="04" title="Served">
    The runtime serves the published document as server-rendered HTML on first load, then rehydrates it into an interactive WebAssembly bundle in the browser. Subsequent patches to the draft have no effect on visitors until the next publish.
  </NumberedStep>
</NumberedSteps>

Full endpoint details: [API reference → Pages](/api-reference/resources/pages).

## Where to go next

<Columns cols={2}>
  <BrandCard eyebrow="EDITING" title="Editing pages with JSON Patch" href="/pageblocks/json-patch">
    Operation types, path syntax, and worked examples for changing a headline, inserting a block, or removing a section.
  </BrandCard>

  <BrandCard eyebrow="THEME" title="Theme" href="/pageblocks/theme">
    The workspace-wide token model that blocks resolve their colors, spacing, and type scale against.
  </BrandCard>
</Columns>
