<!-- demo:embed example="basic/1-quickstart" -->

You can use BuilderJS without understanding its architecture — drop the scripts in, point a save URL at your backend, ship. But the moment you want to *customise* anything (theme, widget, control, save shape), you'll hit one of three concepts you're now ready to internalise. This page walks you through them in the order the engine itself does the work.

## The three containers

Every BuilderJS instance is one engine talking to **three DOM containers** you give it via the constructor. Open `/builder.php` (you saw it in the [Quickstart](/docs/01-quickstart/)) and look at the layout — left rail, centre canvas, right rail. That's it.

```
┌────────────┬───────────────────────────┬────────────┐
│            │                           │            │
│  Widgets   │          Canvas           │  Settings  │
│  palette   │     (live page tree)      │   panel    │
│            │                           │            │
│  drag-out  │  ←  selection / drop  →   │  edit the  │
│            │                           │  selected  │
│            │                           │  element   │
└────────────┴───────────────────────────┴────────────┘
```

Construction is one line per container plus the bundle ingredients:

```javascript
const builder = new Builder({
    mainContainer:     '#MyCanvas',     // required — the page tree renders here
    widgetsContainer:  '#MyWidgets',    // optional — drag palette
    settingsContainer: '#MySettings',   // optional — properties panel
});
builder.load(THEME_JSON, THEME_TEMPLATES, THEME_CONFIG_DATA, MEDIA_URL);
```

`widgetsContainer` and `settingsContainer` accept `null` (or a missing key) — pass `null` for both and you get a "preview-only" canvas with no editor chrome (the [`cookbook/6-canvas-only/`](/examples/cookbook/6-canvas-only/) example does exactly this). When the buyer picks a non-default chrome shape — split-pane, mobile tabs, floating settings — they pick which containers exist and the engine adapts. The 8 cookbook variants cover every shape we've seen buyers want.

The four globals `THEME_JSON · THEME_TEMPLATES · THEME_CONFIG_DATA · MEDIA_URL` are the ingredients `builder.load()` needs to render *anything*. They come from a theme bundle — the next section explains where they live.

## The page hierarchy

Inside the canvas, every page is a tree of typed nodes. The shape is small: four kinds of nodes, three nesting rules.

```
PageElement
└── BlockElement (= one row in the page)
    └── ElementGroup
        ├── PElement, HeadingElement, ImageElement, ButtonElement, …
        └── GridElement
            └── CellElement (= one column)
                └── BlockElement (= one row inside that column)
                    └── ElementGroup
                        └── PElement, HeadingElement, …
```

You'll never edit this tree by hand — drag-drop in the canvas mutates it; selecting an element + tweaking a control mutates it. But understanding the shape pays off the moment you author a sample, write a custom widget, or read a saved JSON file.

Three rules govern the tree:

1. **A `BlockElement` is a row** — it carries vertical rhythm (top + bottom padding) and a single inner column of content. Nothing else owns vertical spacing; never put margin on a Block.
2. **A `GridElement` plus its `CellElement` children give you N-up columns** — a 3-column hero, a 50/50 image-text split. Each cell is itself a stack of Blocks (so cells can hold rich content, not just one element).
3. **Most leaf nodes are typed** — `PElement` (paragraph), `HeadingElement`, `ImageElement`, `ButtonElement`, `DividerElement`, `ListElement`, `IconElement`, plus a dozen more. The full catalogue lives in `docs/core/ELEMENT_DEFINITION.md`; the canonical "what's the JSON shape?" answer is in [JSON structure](/docs/03-json-structure/).

This shape is **not** the rendered HTML. It's the *editable model* that the engine serialises, deserialises, and renders into HTML via the active theme's templates.

## Themes vs samples

Two related concepts, often confused — get this distinction wrong and the next docs won't land.

**A theme** is a folder under `demo/themes/<name>/`. It ships:

- `index.json` — manifest (name, displayTitle, configData, formats baseline).
- `*.template.html` — one EJS template per element type (`P.template.html`, `Heading.template.html`, `Image.template.html`, …). The engine renders an `ImageElement` JSON node by piping it through `Image.template.html`.
- `master/sample/email/<name>.json` and `master/sample/page/<name>.json` — pre-authored sample pages.
- `master/assets/image/` — content images (the AI-generated photo / illustration library).

**A sample** is one of those `*.json` files. Each sample is a complete page tree (PageElement + its descendants) ready to paste into a `builder.load(themeJson, …)` call. The shipped `default` theme carries 70+ samples — every email + page layout you see on `/gallery.php` is one.

When `demo/builder.php` loads, it picks one theme and one sample (`?theme=default&sample=master/sample/email/Minimal` by default). The PHP-side `ThemeRegistry::resolveBundle()` reads the four pieces — sample JSON, every template, the theme's `index.json`, the media URL — and emits them as four named globals (`THEME_JSON`, `THEME_TEMPLATES`, `THEME_CONFIG_DATA`, `MEDIA_URL`). The JS calls `builder.load()` with those four arguments **in that order**. Always four, named, never wrapped (BUILDER.md `RULE I`).

To author your own theme: copy `themes/default/` to `themes/<your-brand>/`, swap the templates + samples, register the new theme dir in the gallery seed. To author *just a sample*: drop a JSON file under `themes/default/master/sample/email/` and add a thumbnail. [Theme overview](/docs/04-theme-overview/) and [Create a sample](/docs/06-create-sample/) cover both.

## The save / load round-trip

The engine never writes to disk on its own. Persistence is the host's job — the engine just hands you a JSON object on demand.

```
                  builder.getData()
   ┌──────────┐       (no I/O)         ┌────────────────┐
   │  Engine  │  ──────────────────►   │  your handler  │
   │  (JS)    │                        │   (any stack)  │
   └──────────┘                        └────────────────┘
        ▲                                       │
        │                                       │
        │   builder.load(json, ...)             ▼
        │   ◄────────────────────────  POST/PUT/etc.
        │                              persist however you like
        │
   ┌──────────┐
   │  reload  │
   └──────────┘
```

`builder.getData()` returns a plain JSON-serialisable object (the page tree, plus the `theme` + `formats` baseline). What you do with it is yours: write it to a file (the demo's `save.php`), POST to your API, drop it in a database column, sync over WebSocket. On reload, you re-feed it via `builder.load(themeJson, themeTemplates, themeConfigData, mediaUrl)` — same four globals, same order.

Two methods you'll meet a lot: `getData()` returns the data object; `getHtml()` returns the rendered HTML string. They are **separate methods**. Never write `const { html, data } = builder.getData()` — that destructure doesn't exist (regression-scar locked in `docs/BUILDER.md` Lesson 73).

The shipped `demo/backend/save.php` shows one minimum-viable backend handler: it checks the access gate (the demo's write endpoints ship switched off), validates the JSON shape, writes it back next to the sample under `demo/themes/`, returns 200. [Wire a real backend](/docs/13-backend/) walks you through replacing it with MySQL / Postgres / S3 / your auth + tenant-scoping pattern.

### The events bus

When a user types in the canvas, drags a widget, undoes a change, or clicks Save, the engine emits events on its public bus. You subscribe to react — autosave, dirty-state badge, "last saved 2 minutes ago" pill, multi-window sync, telemetry. The pattern in one line:

```javascript
builder.events.on(Builder.EVENTS.DOCUMENT_CHANGED, () => updateLastModifiedPill());
```

`Builder.EVENTS` lists the constants: `DOCUMENT_CHANGED` (any mutation, debounced 120 ms), `MODE_CHANGED` (design ↔ preview flip), `DOCUMENT_SAVED` (after a successful Save call), `ELEMENT_ADDED` / `ELEMENT_REMOVED` (structural mutations). The five Phase-C events examples under [`/examples/events/`](/examples/#events) walk each one with a tiny consumer that's safe to copy verbatim. Most production hosts wire one or two of these — `DOCUMENT_CHANGED` for autosave + `DOCUMENT_SAVED` for the toast.

## Where the source lives

These docs are a buyer-tier distillation. When you need to read deeper, head into `src/includes/`:

- `Builder.js` — constructor, `load()`, `save()`, `getData()`, `getHtml()`, `setMode()`, `selectElement()`, the events bus.
- `BaseElement.js` — base contract every Element subclasses ([Build a custom Element](/docs/09-element-dev/)).
- `BaseControl.js` — base contract every Control subclasses ([Build a custom Control](/docs/10-control-dev/)).
- `BaseWidget.js` — palette glue ([Build a custom Widget](/docs/11-widget-dev/)).
- `<Type>Element.js` — concrete subclasses: `PElement`, `HeadingElement`, `ImageElement`, `ButtonElement`, … each ~150-300 LOC.

Engine source is **not** required reading to ship — the docs + examples cover the buyer-tier surface. But it's the canonical truth when you hit a contract that the docs describe in plain English and you want the exact line-by-line behaviour.

## Did you make it?

You should now be able to:

- ✅ Name the three containers and explain what each one shows the buyer.
- ✅ Walk a page-tree shape from `PageElement` down to a leaf element + draw the layout that JSON would render.
- ✅ Distinguish *theme* from *sample* and explain what `builder.load()` consumes.
- ✅ Describe the save / load round-trip in terms of `getData()` + `load()` + your own persistence.

If a step didn't make sense:

- 🩹 *Hierarchy still feels abstract* → open the builder, click the canvas, watch the breadcrumb at the top of the engine chrome. Each crumb is one node in the tree. Clicking any crumb selects that ancestor.
- 🩹 *Theme vs sample still blurry* → `tree demo/themes/default/master/sample/email | head -10` lists 30+ samples in one theme. The theme is the dir; each `.json` is one sample.
- 🩹 *Round-trip doesn't fit your stack* → [Wire a real backend](/docs/13-backend/) covers MySQL, Postgres, S3, multi-tenant via closure capture — same engine, your persistence.

What's next?

- → [JSON structure](/docs/03-json-structure/) — concrete: read your first saved page and recognise every node.
- 🔗 [`examples/basic/1-quickstart/`](/examples/basic/1-quickstart/) — the smallest runnable host for everything on this page.
- 🔗 [`examples/cookbook/3-compact-3col/`](/examples/cookbook/3-compact-3col/) — a 200 / canvas / 240 px chrome shape that uses **all three** containers.

Stuck?

- 🐛 grep `src/includes/Builder.js` for `mainContainer`, `widgetsContainer`, `settingsContainer` to see the exact constructor option contract.
- 🐛 inspect `demo/builder.php`'s `$builderConfig` block — every option a buyer changes is one labelled line.
- 🐛 file an issue with: page URL, the JSON output of `builder.getData()`, what you expected vs what you got.
