<!-- demo:embed example="api/2-get-data-load" -->

In the [Architecture doc](/docs/02-architecture/) you met the page hierarchy as a concept. Now you'll meet it as a JSON file — and learn to read one without flinching. Every saved page is one of these shapes; every theme sample shipped under `master/sample/` is one of these shapes; the engine's `getData()` returns one of these shapes. Master this and you can debug almost anything.

## The page JSON shape

Open `demo/themes/default/master/sample/email/Minimal.json` (or run `builder.getData()` in DevTools while on `/builder.php`). The top-level object has three first-class keys plus the page tree:

```json
{
    "theme": "default",
    "name": "PageElement",
    "template": "Page",
    "formats": {
        "background_color": "#F4F4F4",
        "padding_top": 0,
        "padding_right": 0,
        "padding_bottom": 0,
        "padding_left": 0
    },
    "page_title": null,
    "blocks": [
        /* …an array of BlockElement nodes… */
    ]
}
```

Five fields you'll see on **every** page tree:

- `theme` — string. The theme dir under `demo/themes/`. Determines which template family (`P.template.html`, `Heading.template.html`, …) renders this tree.
- `name` — string. The class name of this node — for the root, always `"PageElement"`. The engine uses this to look up the right Element class when rehydrating.
- `template` — string. Which `*.template.html` file to use. Often matches `name` minus the `Element` suffix (`PageElement` → `Page`).
- `formats` — object. CSS-like properties (`text_color`, `padding_top`, `border_radius`, …). Always lower-snake-case, never camelCase. Different element types declare different format keys; the union is documented in `docs/core/ELEMENT_DEFINITION.md`.
- `blocks` — array (Page only). The page's rows. Each entry is a `BlockElement` — see next section.

That `formats` separation is the engine's core abstraction: presentational properties are in `formats`, structural / behavioural properties are top-level. A heading's text lives at `text`, but its colour lives at `formats.text_color`. Every element follows that split.

## BlockElement — one row

Inside the page's `blocks` array, every entry is a row. The shape:

```json
{
    "name": "BlockElement",
    "template": "Block",
    "formats": {
        "padding_top": 20,
        "padding_bottom": 20
    },
    "elements": [
        /* an array of element nodes — paragraphs, headings, images, etc. */
    ]
}
```

A block carries **vertical rhythm only** — `padding_top` and `padding_bottom`. Margins on Block are forbidden (regression-scar in `docs/BUILDER.md`'s Hardened Rules — collapsing margins inside email-client environments produce subtle visual bugs). Width comes from the parent Page's container width; horizontal alignment comes from the elements inside.

The `elements` array holds the actual content of that row. Each entry is either a leaf element type (`PElement`, `HeadingElement`, `ImageElement`, …) or a `GridElement` for N-up layouts.

## Leaf elements — by type

The shipped engine surfaces around 30 element types. The four you'll see most often:

### PElement (paragraph)

```json
{
    "name": "PElement",
    "template": "P",
    "formats": {
        "text_color": "#212121",
        "text_align": "left",
        "font_size": 14,
        "line_height": 21
    },
    "text": "Welcome to the team — let's get you set up."
}
```

`text` is plain text; rich-text styling (bold, italic, link) flows through `text-formats` for inline ranges (a separate, lazier-to-debug shape — covered in [Layout best practice](/docs/08-layout-best-practice/)).

### HeadingElement

```json
{
    "name": "HeadingElement",
    "template": "Heading",
    "formats": {
        "text_color": "#000000",
        "text_align": "center",
        "font_size": 32,
        "font_weight": 700
    },
    "text": "Welcome aboard"
}
```

Same shape as `PElement`, with bigger default font sizes and stronger weight.

### ImageElement

```json
{
    "name": "ImageElement",
    "template": "Image",
    "formats": {
        "width": 130,
        "align": "center",
        "border_radius": 0
    },
    "src": "master/assets/image/email/logo.png",
    "alt": "Brand logo",
    "url": "",
    "target": "",
    "rel": ""
}
```

`src` is path-relative to the theme's `assets/` (the engine prefixes `MEDIA_URL` at render time). Width is in pixels — never percentage strings (typed-units rule, `THEME_BEST_PRACTICE.md` RULE-H1).

### ButtonElement

```json
{
    "name": "ButtonElement",
    "template": "Button",
    "formats": {
        "background_color": "#000000",
        "text_color": "#FFFFFF",
        "border_radius": 4,
        "padding_top": 12,
        "padding_bottom": 12,
        "padding_left": 24,
        "padding_right": 24
    },
    "text": "Get started",
    "url": "https://example.com/welcome",
    "target": "_blank"
}
```

Same `formats` family as paragraph + heading, with the spatial padding keys for the button shape.

## GridElement — N-up columns

When a row needs columns (image+text split, 3-up feature grid), wrap the row's content in a `GridElement`:

```json
{
    "name": "BlockElement",
    "template": "Block",
    "formats": { "padding_top": 30, "padding_bottom": 30 },
    "elements": [
        {
            "name": "GridElement",
            "template": "Grid",
            "formats": { "gap": 16 },
            "cells": [
                {
                    "name": "CellElement",
                    "template": "Cell",
                    "formats": { "width": 50 },
                    "blocks": [
                        {
                            "name": "BlockElement",
                            "template": "Block",
                            "formats": { },
                            "elements": [ /* image */ ]
                        }
                    ]
                },
                {
                    "name": "CellElement",
                    "template": "Cell",
                    "formats": { "width": 50 },
                    "blocks": [
                        {
                            "name": "BlockElement",
                            "template": "Block",
                            "formats": { },
                            "elements": [ /* heading + paragraph */ ]
                        }
                    ]
                }
            ]
        }
    ]
}
```

The shape repeats: a `CellElement` holds `blocks` (just like `PageElement`); each block holds `elements` again. That recursion is why the page hierarchy in [Architecture](/docs/02-architecture/) drew Block twice. `formats.width` on a cell is a percentage (sums to 100 across the cells in one Grid).

## The round-trip rule

A page's JSON is its single source of truth. Two engine guarantees fall out of that:

1. **`builder.getData()` is idempotent.** Calling it twice in a row returns identical JSON.
2. **`load(getData(x))` is the identity function.** If you `getData()` a page, then `load()` the result back, the canvas is byte-identical to before.

That round-trip is what lets you debug fearlessly — copy the JSON, mutate it in your editor, paste it back into `builder.load()` and you'll see the change in the canvas. The [`examples/api/2-get-data-load/`](/examples/api/2-get-data-load/) example walks the round-trip end-to-end with two side-by-side Builder instances copying state between each other.

The pattern that breaks the rule:

```javascript
// ⚠️ DON'T — this destructure does NOT exist
const { html, data } = builder.getData();

// ✅ DO — they are TWO separate methods
const data = builder.getData();
const html = builder.getHtml();
```

`getData()` returns the page tree; `getHtml()` returns the rendered HTML string. Conflating them is regression-scar #73 in BUILDER.md — the kind of bug a fresh buyer hits at boot when they paste an outdated snippet from a forum thread.

## Common debug recipes

> **"My save isn't persisting"** — open DevTools, run `builder.getData()` in the Console. If it returns something sensible, the engine is fine — your handler is dropping it. If it returns `undefined`, the engine isn't loaded yet (script error before boot).

> **"I want to load a saved JSON without my backend"** — paste it into `localStorage`, then on reload run `builder.load(JSON.parse(localStorage.getItem('myPage')), THEME_TEMPLATES, THEME_CONFIG_DATA, MEDIA_URL)` in the Console. The `THEME_*` globals are populated by the page; you only need to substitute the page tree.

> **"I edited the JSON manually and now it won't load"** — the engine validates `name` + `template` + `formats` keys against the active theme. A `console.warn` in DevTools tells you which node is invalid. Most often: a typo in `template` (`"Heading "` with a trailing space, or `"heading"` instead of `"Heading"`).

> **"Something I expected isn't in the JSON"** — open the canvas, right-click the element, choose "Inspect" in your browser to see the rendered DOM, then `builder.getData()` to see the JSON. The two are connected by templates; what's missing in JSON wasn't part of the editable model in the first place.

## Did you make it?

You should now be able to:

- ✅ Read a page JSON top to bottom and identify each node's `name` + `template` + `formats`.
- ✅ Distinguish a `BlockElement` (row, vertical padding only) from a `GridElement` (N-up columns) from a leaf element (`PElement`, `HeadingElement`, …).
- ✅ State the round-trip rule: `load(getData(x)) === x`, `getData()` and `getHtml()` are separate methods.
- ✅ Open DevTools and use `builder.getData()` to debug a save.

If a step was murky:

- 🩹 *Real samples look bigger than the snippets here* → that's expected. `tree demo/themes/default/master/sample/email/Minimal.json` is ~250 lines once you include all the rows. Read top to bottom — each chunk fits one of the shapes above.
- 🩹 *Round-trip not working* → confirm you saved the JSON output of `builder.getData()`, not `JSON.stringify(builder)` (which captures internal state, not the page tree).
- 🩹 *I have a custom element type whose shape isn't here* → see [Build a custom Element](/docs/09-element-dev/). Custom Elements declare their own JSON shape via `getData()` + `static parse()` overrides.

What's next?

- → [Theme overview](/docs/04-theme-overview/) — what a theme provides, when to author one.
- 🔗 [`examples/api/2-get-data-load/`](/examples/api/2-get-data-load/) — round-trip in action with two live Builders.
- 🔗 [`examples/flows/2-import-bundle/`](/examples/flows/2-import-bundle/) — drag-drop a JSON file and watch the canvas swap.

Stuck?

- 🐛 grep `src/includes/<TypeName>Element.js` (e.g. `PElement.js`) — every leaf element class declares its `getData()` shape. The JSON is whatever that method returns.
- 🐛 the canonical schema spec is `docs/core/STRUCTURE.md`. Heavier than this doc — read it when you need the full surface, not the buyer 80%.
