The build folder

build/ is the only folder structure we document publicly, because it’s the only one you work in. Everything below it is yours to shape; everything above it is platform machinery. That single boundary is what lets a non-technical editor change content all day without ever risking your code, and lets you ship a template change without ever touching their content.

What’s inside

build/
├── pages/        full routable pages   (a node with an @path renders here)
│   ├── place.php
│   └── home.php
├── blocks/       embeddable fragments   (a node without a path renders here)
│   ├── place-card.php
│   └── stop.php
├── menus/        navigation elements
│   └── mainmenu.php
├── forms/        structured edit/submit forms
│   └── place.php
├── templates/    shared partials and layout wrappers
└── assets/
    ├── css/      site styling — fully yours
    └── js/       browser modules — yours, behind the lint-gate

A node’s @build property names its template. @build: pages/place resolves to build/pages/place.php; @build: blocks/stop resolves to build/blocks/stop.php. The mapping is direct — no router config, no registration step.

Routable vs embedded

The folder a template lives in answers one question: can this node be visited at a URL?

has an @path  →  pages/    →  a full HTML page at /that-path
no @path      →  blocks/   →  rendered only inside another node

A routable node has a @path and a pages/* build — together they grant it a URL. An embedded node has no path; it lives inside other nodes as a card, a stop, a list item. The same type can be either: a Place can be a full page in one space and a card in another. Type describes what something is; build and path decide how it’s reached. (More in Nodes & types.)

Push and pull, with a gate

You work on build/ the way you’d expect — locally, in your editor, in version control. Pushing it to a space and pulling it back is a Git-like sync. But unlike a raw git push, every push passes through a lint-gate before it’s accepted. The gate is what makes the code/content boundary safe rather than merely conventional.

The gate rejects anything that could let a template escape its sandbox:

The gate blocks Why
Dangerous functions (exec, eval, system, raw file_* on absolute paths) A template renders data; it must not run arbitrary code or touch the host
Out-of-bounds reads A template may read its own space’s graph, nothing outside it
External dependencies No Composer, no vendor/, no npm install — templates use the platform’s helpers and PHP’s standard library only

If the push lints clean, it lands. If it doesn’t, you get a specific error and nothing changes on the live space.

Why the boundary holds both ways

The split runs deeper than “don’t break each other’s stuff.” Two folders that aren’t in your reach — data/ (the node graph) and config/ (space settings) — are managed entirely at runtime, by the editor and the API. You never read or write them as files.

code-push    →  changes build/        →  never touches data/ or config/
runtime edit →  changes data/ config/ →  never touches build/

That clean separation is why an editor approving a draft can’t corrupt a template, and why deploying a new template can’t lose a single edit. The lint-gate guards the code side; the lifecycle and API guard the data side. Neither can reach across.

The next page covers the one thing that does span the boundary on purpose — the helper API that lets your static markup become editable content.