---
title: Model your content
status: current
phase: 2
order: 3
---

# Model your content

A **content type** is the shape of a piece of content: which fields it has. You define it in the dashboard, with no code. Usually your agent does it in [step 2](invite-your-agent.md); this guide explains what it does, so you can review it or do it by hand.

## Create a content type

In your site, go to **Model → New type**:

| Field | What it is | Example |
| --- | --- | --- |
| Name | How people see it | "Event" |
| API ID | How the API asks for it. Lowercase letters, numbers and underscores; starts with a letter | `event` |
| Description | Optional. Your agent reads it to know what the type is for | "Events on the school calendar" |
| Singleton | Whether it has **a single entry**. For site settings | `site_settings` |

The API ID can't be changed later: your site already uses it to fetch the content.

## Agent guidance

Each type can carry instructions for whoever adds content, a person or your agent. You edit them on the type's page, under **Agent guidance**:

- **When to use it and when not to.** One or two sentences. "Use Pricing for tuition and fees. Not for event lists." Up to 2000 characters.
- **What it looks like.** Up to 4 previews of the site, picked from your [files](files.md). They must be images.
- **Help per field.** One line: format, unit or example. "In USD, no symbol: 12500".

Your agent reads them before creating anything, in `describe_space` in the [MCP](../mcp/tools.md) and in your site's `llms.txt`. Over the API they come in `guidance`, `previews` and `fields[].help` on each content type. They help most when there are several similar types, like the sections of a page: without guidance, the agent guesses which one to use. See [Composable pages](composable-pages.md).

When an agent models a site, these make the dashboard easy for the person who edits it later:

- **Write `help` for a person, in one line:** the format, the unit or an example ("In USD, no symbol: 12500"; "Shown on the home page under the title"). Up to 300 characters. It shows under the field's label.
- **Prefer a precise type over free text:** `url` for links, `email`, `color`, `select` when there's a fixed set of values, `reference` when it points to other content. The form then gives the right control and rejects wrong values.
- **No key/value lists for copy.** A `list` of `key`, `value` rows ("heroTitle", "Welcome") hides what each text is. Make real fields with a clear name and `help`, and put them together with [`group`](#groups).
- **Set `kind`, `subtitle_field` or `image_field`** when the [automatic choice](#how-content-is-organized) is wrong, like a section type that a page doesn't reference through a field named `sections`.

## Page URL

Tell Pluma where each entry of the type lives on your site: `/blog/{slug}`, `/events/{id}`. With it, every entry gets **View live** and **View on staging** links, and the API returns its `sys.url`. See [Staging and previews](staging.md).

## Add fields

Each field has a name, an API ID, a type, and three options:

- **Required:** an entry can't be published without this field.
- **Localized:** it has one value per locale. If not, the same value applies to all of them.
- **Title field:** the one shown in lists. One per type. Pluma picks a short text named `title`, `name`, `question`, `heading`, `headline`, `label` or `caption`, else the first short text; you can change it on the Model page.

And these settings, all optional:

- **Help:** one line under the label for whoever fills it in. See [Agent guidance](#agent-guidance).
- **Group:** a short heading that groups fields in the form. See [Groups](#groups).
- **Options:** for **One of a list** and **Several of a list**, the values to pick from (up to 50, one per line or separated by commas).
- **Accepts:** for files, **Images**, **Videos** or **Any file**. The file picker opens showing only those. When not set, Pluma reads it from the API ID: `video`, `clip` or `reel` take videos; `image`, `photo`, `logo`, `cover`, `poster`, `thumbnail`, `avatar`, `icon` or `banner` take images.

On the Model page, **Edit** next to each field changes its help, group, options, what it accepts and, for text fields, its type.

## Field types

| Type | API | What it stores | Example |
| --- | --- | --- | --- |
| Short text | `symbol` | Up to 256 characters | Title |
| Long text | `text` | Plain text with no limit | Summary |
| Rich text | `rich_text` | JSON blocks: paragraphs, headings, lists, images. The API also returns it as markdown and HTML | Post body |
| Integer | `integer` | 42 | Seats |
| Decimal number | `number` | 3.5 | Price |
| Yes / no | `boolean` | true or false | Featured |
| Date | `date` | Date only (`2026-08-14`), date and time with its time zone (`2026-10-12T09:00:00-06:00`) or without a zone (`2026-10-12T09:00:00`). Stored as is, not converted to UTC | Event date |
| Link (URL) | `url` | A full link that starts with `https://` or `http://`, up to 2048 characters | Booking link |
| Email | `email` | An email address | Contact email |
| Color | `color` | A hex color: `#rgb`, `#rrggbb` or `#rrggbbaa`. Stored lowercase; `1f6feb` gets its `#` | Brand color |
| One of a list | `select` | One of the field's `options` | Level: Beginner, Intermediate, Advanced |
| Several of a list | `multi_select` | Some of the field's `options`, as a list | Dietary: vegan, gluten-free |
| JSON | `json` | Any JSON object | Extra data |
| Slug | `slug` | Lowercase letters, numbers and hyphens. Unique within the type | `open-house` |
| Tags | `tags` | List of short texts | `["families", "2026"]` |
| Reference | `reference` | Another entry. Can be limited to certain types (even types you haven't created yet) | The post author |
| References | `references` | Several entries | Related posts |
| File | `asset` | An image or document | Cover photo |
| Files | `assets` | Several | Gallery |
| List | `list` | Rows of simple item fields, in order. See [Lists](#lists) | Menu, opening hours, social links |

## Lists

For things that are rows of the same small shape: the site menu (label, url), opening hours (days, open, close), social links (network, url). Each row is edited as a row in the dashboard, with add, move up and remove, instead of raw JSON.

- A list has **1 to 10 item fields**. Each has an `api_id`, a `name` and a type: `symbol` (default), `text`, `integer`, `number`, `boolean`, `date`, `url`, `email`, `color`, `select` (with its own `options`) or `reference` (with optional `link_content_types`). An item field can carry its own `help`.
- Up to **100 rows**. Empty rows are dropped when saving. The order you see is the order you get.
- In the dashboard, when you add a field of type List, write the item fields separated by commas: `Label, URL` or `Days, Open, Close, Closed:boolean`.
- By API or MCP, define them with `item_fields` and send the value as a list of objects:

```json
{ "api_id": "navigation", "name": "Menu", "type": "list",
  "item_fields": [ { "api_id": "label", "type": "symbol" }, { "api_id": "url", "type": "url" } ] }
```

```json
{ "fields": { "navigation": [ { "label": "Menu", "url": "https://acton.example/menu" }, { "label": "Visit", "url": "https://acton.example/visit" } ] } }
```

A row with a key that isn't an item field is rejected, with the name of the field it expected. If each row needs its own page, a photo or references, use a content type of its own instead.

## How content is organized

The dashboard's **Content** section is built from the model. It opens on an overview with three groups, each type with how many entries it has:

| Kind | What it is | In Content |
| --- | --- | --- |
| **Page** | Has a URL and is built from sections | Under **Pages**. Its sections are edited inside it |
| **Section** | A piece of a page: hero, pricing, call to action | Not in the menu: inside its pages, and under **Page sections** with where each one is used |
| **Collection** | Many entries of the same thing: posts, events, people | Under **Collections** |
| **Settings** | One entry with the site's settings | Under **Settings** |

Pluma infers the kind: a singleton is settings; a type with a `references` field named `sections`, `blocks`, `modules` or `components` is a page; a type that such a field accepts, and that has no page URL, is a section; anything else is a collection. If it guesses wrong, change it on the type's Model page, under **How it shows in Content**, or with `kind` by [API](../api/management.md#edit-a-content-type) or MCP.

The same panel sets how each row of a list looks, and the order:

- **Title:** the title field.
- **Second line:** the field under the title. By default a parent (a single reference), else a short or long text that isn't the title, a slug or a link. Pages show their path.
- **Thumbnail:** the file field shown as a small image. By default the first one that takes images.
- **Order:** how entries are listed: by hand (drag them), newest change, newest created, newest published, or by a field. By default, a number field named `order`, `position`, `sort`, `sortOrder`, `weight` or `rank` if the type has one, else newest change first. See [Order](entries.md#order).

Admins also set the **menu order**: **Arrange menu** in Content moves the types and the three groups. By API: [Order](../api/management.md#order).

### Quick access

The things the owner opens most can go first, above Pages: **Quick access**. An admin presses **Quick access** on an entry (the site settings, the home page) or on a list (Events), and it shows at the top of the menu; pressing it again removes it. Up to 8. With none, the menu has no such group. The agent sets them with `set_quick_access` (MCP) or [`PUT /quick_access`](../api/management.md#quick-access): a good default after building a site is the settings and the home page.

### Groups

A field's **group** is a short heading, up to 40 characters: `Contact`, `Hours`, `SEO`. In the entry form, fields with the same group show together in a section that can collapse; fields without one go under **General**. Use it for long types, above all the site's settings.

A settings type with more than 8 fields and no groups is grouped by the first word of each API ID (`contactPhone` and `contactEmail` → **Contact**), and each list gets a section of its own. Setting `group` gives better names.

## Rules that protect your content

- **Deleting a field doesn't delete data.** The field is hidden and the data stays in each entry's previous versions.
- **Changing the type of a field that already has data** works only between text types (short text, long text, link, email, color, one of a list, slug) and only if every value fits the new type: a short text full of `https://` links can become a link. Otherwise it's rejected, naming the value that doesn't fit; to migrate, create a new field, copy the data, and hide the old one.
- **An API ID is never reused** within the same type, even if the field is hidden.
- A type with entries can't be deleted until you archive them.

## Possible errors

| Message | What to do |
| --- | --- |
| "That API ID already exists in this site" | Choose another one, or use the type that already exists. |
| "The API ID must start with a letter and contain only lowercase letters, numbers and underscores" | `blog_post` works; `Blog-Post` doesn't. |
| "You can't change the type of a field that has data" | Create a new field and hide the old one. Between text types, fix the value the message names, then try again. |
