Pages vs CMS
A ChaiBuilder site holds content in two different shapes, and picking the right one for each piece of content is the single highest-leverage modelling decision you will make.
- Visual pages live in the builder. The layout is the content: a tree of blocks, styled in place, unique per page.
- Structured content lives in the CMS as collections. The fields are the content: many records of the same shape, with no layout of their own.
The rule of thumb: if you would design it once and fill it in many times, it is structured content. If each one is its own composition, it is a visual page.
Side by side
| Visual pages | Structured content | |
|---|---|---|
| Edited in | The visual builder | The CMS admin panel |
| Shape | A tree of blocks | Typed fields on a collection |
| Layout | Unique per page, editor-controlled | None; supplied by whatever renders it |
| Good for | Home, landing, pricing, campaign pages | Blog posts, docs, legal, jobs, releases, team members |
| Stored in | ChaiBuilder's own page tables | Payload collections |
| Draft/publish | Draft row and published row, published on demand | Payload's own draft and publish |
| Scales by | Adding pages by hand | Adding records |
Both live in the same database, and neither one owns the other. There is no hard relation between a page row and a collection record - the connection is made at render time by the bridges below.
The four bridges
Structured content is useless if it cannot reach a page, and a page is limited if it can only show hand-typed text. Four mechanisms connect them.
1. Page types - one layout, many records
A page type is a visually designed page that acts as the layout for every record in a
collection. You build /blog/[slug] once in the builder; a data provider resolves the incoming
slug to a record and hands its fields to the render. See Page Types
for the full model.
Two things follow from this. First, the layout is edited by editors in the builder, not by developers in code. Second, a page type does not have to be backed by a CMS collection at all - it can read from a git repository, an external API, or anything else you can query on the server. The contract is the data provider, not the CMS.
2. Data binding - fields into blocks
Inside a page, {{ }} bindings pull values out of the current record, the site-wide global
data, or the surrounding loop:
{{ blog.title }}
{{ global.companyName }}
Bindings resolve on the server at render time. A missing value resolves to an empty string rather than an error, which is forgiving in production and easy to miss while authoring. See Data Binding.
3. Repeaters - many records inside one page
A Repeater block iterates a collection and renders its children once per record. This is how a
visual page shows a list of posts, a card grid of team members, or a table of releases. Bindings
inside a repeater resolve against the current record.
4. Global data - the site-wide singleton
A global data provider supplies one object to every page
render, available as global.*.
Site name, contact details, social links, analytics snippets, and header or footer HTML belong
here rather than being retyped on each page.
Choosing where content goes
| Content | Put it in | Why |
|---|---|---|
| Home, pricing, campaign, product pages | A visual page | Every one is a different composition |
| Blog posts, docs, legal pages, job posts | A collection plus a page type | Same shape, many records, one layout to maintain |
| A repeated header, footer, or CTA band | A global block, referenced from pages | Edited once, updates everywhere |
| Navigation menus | The menus collection | Structured links, editable without touching layout |
| Site name, socials, tracking scripts | The global data provider | One value used by every page |
| Form submissions, leads | A collection | Data in, never authored by hand |
If you find yourself duplicating a visual page and changing only the words, you wanted a page type. If you find yourself adding fields to a collection so it can control its own layout, you wanted a visual page.
Publishing is separate on each side
The two models publish independently, which is usually what you want and occasionally surprising:
- A page has a draft row and a published row. Visitors only ever see the published row. Publishing copies one to the other and invalidates that page's cache.
- A CMS record follows Payload's own draft and publish. Saving a record does not touch page rows at all; it invalidates the pages that render that collection, which is configured by listing the collection in the ChaiBuilder Payload plugin.
So a blog post can go live without anyone publishing a page, and redesigning the blog layout can go live without touching a single post. See Caching & Revalidation.
The CMS is swappable
Payload is the default because the starter ships it wired in, but the builder does not require it. What the builder actually depends on are two config contracts: page types and collections, each backed by a provider function you write. Point those at Supabase, a headless API, flat files in the repository, or anything else queryable from the server, and the builder does not know the difference.
What you would give up is the wiring the starter provides for free: the CMS admin UI, collection scoping per site, and automatic revalidation on record changes. See Swapping the CMS.
Related
- Architecture Overview - the layers and surfaces around both models.
- Page Types - the bridge in depth.
- Data Providers - the three kinds and where each lands.
- Data Binding -
{{ }}syntax, scope, and repeaters. - Payload Overview - the CMS side in depth.
- Glossary - one-line definitions for every term used here.

