---
title: The content model
description: Understand the handful of building blocks a Quilium site is made of, and learn a reliable rule for picking the right one each time, so you model content once and never have to unpick it later.
updated: 2026-06-22
---

## Why this matters

Almost every modelling mistake in Quilium comes from one confusion: reaching for the wrong building block. You put a list of features inside a collection when a single rich-text field would have done, or you bury a real catalogue of articles inside one page where it can never be reused. Both work for a while, then they don't, and unpicking them means migrating content by hand.

This page exists to prevent that. It defines each building block, gives you a decision table that maps a need to a block, and names the traps that catch people most. Read it once and the rest of the docs, the templates, the CLI, the agents, will make sense, because they all speak in these same terms.

The payoff of getting this right is real: content you model cleanly stays cheap to edit, reuse, query, and translate. Content modelled in the wrong block fights you on every one of those.


<div class="q-callout q-callout-warn"><i class="fa-solid fa-triangle-exclamation lead"></i>
  <div><b>One asymmetry to carry with you.</b> A page has a draft and a published revision; a collection entry has neither. Saving an entry publishes it, on every page whose query matches. Staging entries is something you build, with your own field and a condition in the listing queries. See <a href="/en/docs/publish-and-go-live">Publish and go live</a>.</div></div>

## The shape of a site

A Quilium site is structured, not a pile of pages with free-text bodies. Three objects carry that structure, and they nest in a predictable way.

A `page-type` is the schema for a page: it declares the layout zones (its sections) and any route-aware [queries](/en/docs/custom-fields) the page needs. A `page` is one instance of a `page-type`, the same way a row is an instance of a table.

Into a page's zones you drop **content blocks**. A content block is an instance of a `content-type`, a reusable, typed block like a hero, a gallery, or a call to action. The `content-type` is the schema; the content block is what an editor actually places and fills on a given page.

Standing apart from both is the collection: a managed set of structured entries, each editable on its own, listable, searchable, and optionally addressable by its own URL. Articles, members, events, products. A page-type or a content-type *consumes* a collection through a [query](/en/docs/custom-fields) or a relation field; it never copies the data in.

<div class="q-ph"><i class="fa-regular fa-image"></i><span><b>Placeholder:</b> content-model diagram. Show a page-type holding named zones; content-types dropping into those zones as content blocks; a collection sitting outside, feeding a query that lands results back into a page-type or content-type. To produce from the fil-rouge demo site.</span></div>

## Tell the three building blocks apart

These three are the load-bearing objects. Keeping them distinct is what prevents most modelling mistakes.

| Object | What it is | Lives | Reusable across pages | Has its own URL | Example |
|---|---|---|---|---|---|
| `page-type` | The schema for one page: its layout zones and route-aware queries. | One file per page-type. | A page-type is reused by every `page` built from it. | A page does, via its route. | Home, Article detail |
| `content-type` | The schema for a reusable block dropped into a page zone. An instance is a **content block**. | One file per content-type. | Yes, the same content-type is placed on many pages. | No, it renders inside a page. | Hero, Gallery, Quote |
| `collection` | A managed set of structured entries, each independently editable. | Declared once under `item-set-types`. | Yes, consumed by queries from many places. | Optionally, per entry, via a route. | Articles, Members, Events |

Two distinctions inside that table are worth stating plainly:

- **`page` is not `page-type`.** A `page-type` is the template and schema; a `page` is an instance filled with content. Five "About", "Contact", and "Pricing" pages can all share one `default` page-type, because they share a *structure*, even though their content differs. Page-types describe layout, not purpose.
- **A content block is not a collection entry.** A content block belongs to the single page it sits on. A collection entry belongs to the collection and can surface anywhere a query reaches for it. This difference drives the wrapper rule below, and it is the single most common source of errors.

<div class="q-callout q-callout-warn"><i class="fa-solid fa-triangle-exclamation lead"></i>
  <div><b>The wrapper rule.</b> A content block's data goes in <code>custom: {}</code>; a collection entry's data goes in <code>data: {}</code>. Never mix the two. Reading an entry as <code>content.custom.&lt;field&gt;</code> when it lives in <code>data: {}</code>, or writing a block under <code>data:</code>, is the error you will hit most often.</div></div>

## The smaller pieces: how a block holds a list

The three objects above answer "where does this content live". A second, finer question comes up constantly inside a single content-type: *how should this block hold a list, or a value, or a body of text?* Quilium gives you a few field-level primitives, and choosing among them is where most day-to-day modelling decisions actually happen.

- A **`wysiwyg`** field is one rich-text body. It handles bullet and numbered lists, links, headings, bold and italic, plus site-wide custom styles (buttons, callouts) through the `wysiwyg.styles` palette. It is the right answer for any list of *styled text*, which is far more often than people expect.
- A **`repeat`** field holds N items of one fixed shape, inline and bounded. Use it only when each item is a typed mini-record whose shape cannot be expressed in HTML an editor can type: icon plus title plus image, avatar plus name plus role.
- A **`choice`** field lets an editor compose an open, ordered *flow* of typed blocks, where each block picks one of several declared variants, each with its own field shape. An article body of paragraph, image, quote, embed.
- A **`select`** field is one value picked from a fixed, closed list. A size, a theme, an alignment.
- A **`collection`** (consumed by a query or relation, not embedded) is a listable, queryable set of entries that lives outside the block.

The golden rule that resolves most of these at a glance: **`repeat` is for non-standard HTML shapes; `wysiwyg` is for standard HTML.** If a candidate `repeat`'s fields would only contain `text`, `wysiwyg`, or `link`, it is a `wysiwyg` in disguise. Collapse it.

## I need this → I use X

This is the table to keep. Read the left column as the need you actually have, the right column as the block to reach for.

| I need… | I use | Why, and not the others |
|---|---|---|
| A single body of rich text: a bullet list, a link list, a paragraph followed by a CTA button, "why us" prose with a checklist. | **`wysiwyg`** | One field. The editor types `<ul><li>…</li></ul>` natively and styles the CTA with a site-wide palette class. A `repeat` here just turns prose into a stack of mini-forms and loses copy-paste, sub-bullets, and inline links. |
| A fixed, bounded set of same-shaped mini-records, always shown together, each carrying more than plain text: feature cards (icon + title + description), team avatars (image + name + role), gallery items (image + caption + link), pricing tiers. | **`repeat`** | A typed shape HTML can't express, shown as a group, never browsed or filtered on its own. Not a `wysiwyg` (shape is non-standard), not a collection (not reused, not listable, bounded and inline). Fields live under `config.fields`. |
| Mutually-exclusive block variants the editor composes into an open flow: an article body that mixes paragraphs, images, quotes, and embeds, each with its own fields and template. | **`choice`** | Several *different* shapes in one ordered sequence, picked per block. A `repeat` can't, it is one shape only. A `select` can't, it carries no nested fields. Variants live under `config.choices`, each with `name` and `fields`; templates switch on `_choice`. |
| One value from a closed list: a size, a theme, an opacity, an alignment, an icon name. | **`select`** | A single enum value, no list, no nested fields. Not a `choice` (that's a flow of typed blocks, not a value picker). Options are a `key: Label` map under `config.options`. |
| A real, listable, queryable collection: items editors manage on their own, that appear in more than one place, may have their own URL, their own draft/published state, and grow without bound. Articles, members, events, products. | **`collection`** | The only block that is reusable across pages, searchable, route-addressable, and stateful per entry. A `repeat` traps the data inside one content block where nothing else can reach it; a `wysiwyg` can't be queried at all. Consumed via a [query](/en/docs/custom-fields) or relation, never copied in. |

The two boundaries people cross most are the last two rows of this table, in both directions. The next section names them directly.

## The common pitfalls

### Wrapping a collection entry like a content block

The wrapper rule again, because it is the error you will meet most. A content block's fields are read and written under `custom: {}`; a collection entry's under `data: {}`. Mixing them, reading `content.custom.title` when the value lives in an entry's `data`, or writing an entry under `custom:`, fails quietly or returns nothing. Decide *which object you are touching* first, then use its wrapper.

### Choosing a collection when a `repeat` suffices

A static set of three social links, or four pricing tiers shown only on one page, is not a collection. Modelled as a collection it bloats the backoffice with a near-empty managed list that no one browses. If the items are bounded, always shown together, and never reused or searched, a `repeat` inside the content-type (or a small `select`/global field for site-wide singletons) is the lighter, correct choice.

### Choosing a `repeat` when you actually need a collection

The mirror mistake, and the more painful one to undo. You model team members as a `repeat` on the About page. Months later: "can we show the team on the home page too?" You can't, the data is locked inside one content block. Or the list grows past a dozen articles and the inline form becomes unmanageable. The test is a single question: *might the editor ever want to reuse, search, or individually publish these?* If yes, it is a collection, not a `repeat`. Migrating later means moving every record by hand.

### Forking a content-type because a style is missing

When a visual variant (a button, a styled list, a callout) isn't in the editor's palette, the temptation is to model it as a typed `repeat` or a new content-type. Don't. Add the variant once to the site-wide `wysiwyg.styles` palette and every editor on the site gains it, with no new schema. A missing *style* is a palette gap, not a modelling problem.

<div class="q-callout q-callout-tip"><i class="fa-solid fa-lightbulb lead"></i>
  <div><b>The question that resolves most of these.</b> Before you pick a block, ask: <i>is each item just styled text, a bullet line, a link, a paragraph with a button?</i> If yes, it is one <code>wysiwyg</code> field and you can stop. Only if the answer is no do the other blocks come into play.</div></div>

## How the pieces relate at render time

Modelling decisions show up again when a template renders. A `page-type` declares its [queries](/en/docs/custom-fields); the engine resolves them (route-aware) and lands the results on `page.custom.<key>` as arrays. Content blocks expose their own fields on `content.custom.<field>` inside the block's element template. A collection never renders itself, it is *pulled in* by a query or relation and rendered through whichever template consumes it. The wrapper rule you follow when writing data, `custom` for blocks, `data` for entries, is the same boundary you read across in templates. See [Templating philosophy](/en/docs/templating-philosophy) for how that reading works, and [How Quilium renders the site](/en/docs/how-quilium-builds) for the full request-to-HTML path.

## Where this fits

- [Architecture](/en/docs/architecture), the system around these objects, the CMS, the CLI, the MCP server.
- [Routing and URLs](/en/docs/routing-and-urls), how a collection entry earns its own page and URL.
- [The multilingual model](/en/docs/multilingual-model), how fields and entries carry their translations.
- [Templating philosophy](/en/docs/templating-philosophy), why templates only ever read the model, never reshape it.

## Where to go next

<div class="q-cards">
  <a class="q-card reco" href="/en/docs/routing-and-urls"><span class="k">Recommended</span><span class="t">Routing and URLs</span><span class="d">Give a collection entry its own page and address.</span></a>
  <a class="q-card" href="/en/docs/custom-fields"><span class="t">Field types reference</span><span class="d">Every field, with its config, once you've picked the block.</span></a>
  <a class="q-card" href="/en/docs/collection-from-a-list"><span class="t">Tutorial: turn a list into a collection</span><span class="d">Do the move end to end on the quickstart site.</span></a>
</div>
