---
title: Collection-type schema
description: The YAML keys that define a collection of structured, listable entries, including displays, filters, ordering and detail-route previews.
updated: 2026-09-01
prev: page-types
---

A collection is a set of structured, listable entries (Articles, Members, Events). In the configuration it is declared under `item-set-types`, and the MCP tools that read and write its entries carry `itemset` in their names, which is the one place the old object name still shows. Entries are queried into pages by the `query` field declared on a [page-type](/en/docs/page-types). Field definitions live exclusively inside `tabsets`, never at the type root. A collection entry's data is written under a `data: {}` wrapper, not `custom: {}`.

## Top-level keys

| Key | Type | Notes |
|---|---|---|
| `name` | string | Collection name (backoffice section). |
| `objectName` | string | Singular item label ("Add a …"). Live: `objectName: Article`. |
| `icon` | string | Font Awesome Pro id. |
| `tabsets` | object | `tabsets.<key>.{name, fields}`. All fields live here. |
| `displays` | array | `{type, config}` entries. `type` is `datatable`, `calendar` or `kanban`. |
| `filters` | array | field keys exposed as backoffice filters. Allowed types only: `checkbox`, `page`, `select`, `color`, `number`. |
| `sortable` | bool or array | `true` for global manual drag-drop, or an array of category field keys for per-category. Stored in system `_order`. |
| `retention` | string or object | ISO-8601 duration (`P1Y6M`), or `{duration, basedOn}` where `basedOn` is `created_at`, `updated_at` (default) or a `date` field key — see [Retention](#retention). |
| `bulkEditGroups` | object | optional `{group: label}` labels for the bulk-edit groups declared on fields via `config.bulkEditable` — see [Bulk edit](#bulk-edit). Each key must match a group used by at least one field. |
| `preview` | array | `{label, route, params}` entries; mandatory wherever a detail route exists. |
| `fieldsets` | object | visual field grouping. |

There is **no** root-level `label`, `fields`, `options` or `translations`. Deprecated: root `cols:` (now `displays[].config.cols`) and root `display:` (now `displays`).

```yaml
articles:
  name: Articles
  objectName: Article
  icon: file
  tabsets:
    main:
      name: Article
      fields:
        title:
          type: text
          label: Title
          translatable: true
          searchable: true
        slug:
          type: slug
          label: Slug
          config:
            linkedField: title
        excerpt:
          type: textarea
          label: Standfirst
          translatable: true
        body:
          type: wysiwyg
          label: Body
          translatable: true
        category:
          type: select
          config:
            options:
              vivant: Vivant
              terrains: Terrains
              idees: Idées
              entretiens: Entretiens
        author:
          type: text
          label: Author
          translatable: false
        date:
          type: date
          label: Publication date
        image:
          type: image
          config:
            versions:
              card:
                label: Card
                width: 800
                height: 600
              cover:
                label: Cover
                width: 1600
                height: 900
        readingTime:
          type: number
          label: Reading time (min)
          translatable: false
        featured:
          type: checkbox
          config:
            checkedValue: oui
            uncheckedValue: non
  displays:
    - type: datatable
      config:
        cols: [image, title, category, date]
        order:
          column: date
          sort: desc
  filters: [category, featured]
  preview:
    - label: View on the site
      route: article
      params:
        article.slug: '{{slug}}'
```

<div class="q-callout q-callout-note"><i class="fa-solid fa-circle-info lead"></i>
  <div><b>Field identifiers stay verbatim.</b> Keys like <code>title</code>, <code>category</code> and <code>featured</code> are real content identifiers from the live site and read back unchanged in queries and templates. They are data, not UI labels — and note that <code>category</code> stores <code>vivant</code> while it <i>displays</i> "Vivant": a <code>where</code> clause matches the stored key, never the label.</div></div>

## Displays

`displays` is an array of `{type, config}`; each entry is a backoffice list view. When several are defined, `config.label` is the switcher button label.

### datatable

| `config` key | Notes |
|---|---|
| `cols` | array of 3 to 5 field keys. Width hints inline: `pic (70px)`, `year (15%)`. |
| `size` | `default`, `sm` or `xs`. |
| `order` | `{column: <fieldKey>, sort: asc\|desc}`, the **backoffice default sort** only. |
| `label` | switcher button label when multiple displays exist. |

### calendar

| `config` key | Notes |
|---|---|
| `labelField` | the field used as the event label. |
| `label` | switcher button label. |

### kanban

A Trello-style board. The columns are the options of one `select` (or `smartselect` with
`source: static`) field of the type, in their YAML order; dragging a card to another column
updates that field on the item.

| `config` key | Notes |
|---|---|
| `columnsField` | **required** — key of the field whose options define the columns. Must be a `select` or a `smartselect` with `source: static`, and not `multiple` (a card belongs to a single column). |
| `card` | `{title, text, image}` field keys shown on each card — all optional, each falls back to the first field of the matching type (`title`: `text`; `text`: `textarea`/`wysiwyg`; `image`: `image`). |
| `order` | `{column, sort}`, the sort of the cards inside each column (default `created_at desc`). |
| `label` | switcher button label. |

```yaml
displays:
  - type: datatable
    config:
      cols: [image, title, category]
  - type: kanban
    config:
      label: Board
      columnsField: category
      card:
        title: title
        text: excerpt
        image: image
```

Items whose value is empty (or no longer matches an option) are grouped in a leading
"Non classé" column; dragging a card into it clears the field.

<div class="q-callout q-callout-warn"><i class="fa-solid fa-triangle-exclamation lead"></i>
  <div><b>A kanban is never the only display.</b> The configuration is rejected unless a
  <code>datatable</code> or <code>calendar</code> display is declared alongside it, and the kanban
  cannot be used as the legacy scalar <code>display:</code>. Avoid placing it first in
  <code>displays</code>: the first entry is the default view.</div></div>

## Filters

`filters` is an array of field keys exposed as backoffice filters. Only `checkbox`, `page`, `select`, `color` and `number` fields are allowed.

## Inline edit

A `select` (or `smartselect` with `source: static`) field declared with `config.inlineEditable: true`
becomes editable **directly in the datatable cells** of the listing: changing the value saves the
item immediately, without opening it. The kanban display relies on the same partial-update contract
when a card is dragged to another column. For editing several items at once, or fields of other
types, see [Bulk edit](#bulk-edit) below.

## Bulk edit

Fields opt into bulk edit with `config.bulkEditable`. In the backoffice listing, the right-click
menu of a selection then shows one entry **per group**; each entry opens a modal scoped to that
group's fields and applies the same values to every selected item. Groups keep business actions
separate — a "schedule publication" modal edits its two dates and nothing else.

- `bulkEditable: true` — the field forms its own implicit single-field group (menu entry labelled
  after the field).
- `bulkEditable: <group>` — a group name (`[a-zA-Z0-9_-]+`); all fields sharing it are edited in
  one modal. The menu label is `bulkEditGroups.<group>` when declared at the type root, otherwise
  the capitalised group name.

Accepted **only** on `text`, `textarea`, `number`, `date`, `select`, `color`, `checkbox`, `switch`
and `smartselect` with `source: static`. On any other type (including `slug`) the configuration is
rejected.

```yaml
news:
  bulkEditGroups:
    planning: Schedule publication      # label of the context-menu entry
  tabsets:
    main:
      fields:
        category:
          type: select
          config:
            options: { … }
            bulkEditable: true          # own entry, own single-field modal
        publishFrom:
          type: date
          config:
            bulkEditable: planning      # same modal as publishUntil
        publishUntil:
          type: date
          config:
            bulkEditable: planning
```

In the modal, each field has a "modify this field" toggle: an untouched field leaves the items'
values alone, an activated empty field clears them — the two are never confused. Values are
applied through one call for the whole selection, and each item's history records the edit.

## Ordering, three independent axes

Do not confuse the three ordering mechanisms; they apply to different surfaces.

| Axis | Where | Effect |
|---|---|---|
| `displays[].config.order` | a display | the CMS list's default sort. |
| `sortable` | the type | manual drag-drop in the backoffice, persisted to system `_order`. |
| runtime `sort:` | a `query` (or `items` field) | ordering on the public site at render time. |

## Slugs and archiving

- **Slug** via `config.linkedField` (not `source:`), auto-generated from another field. One slug field per type maximum.
- **Archive** via a `checkbox` field with `config.{checkedValue, uncheckedValue}`, the canonical soft-archive (not `choice`). This is orthogonal to the system `state` (draft/active) controlled by the MCP `update-itemset-item` `state` param.

## Retention

`retention` deletes items automatically once they expire. Two shapes:

```yaml
# Legacy: duration only — basedOn defaults to updated_at
retention: P6M

# Full: explicit basedOn — created_at, updated_at, or a date field of the type
retention:
  duration: P0D
  basedOn: displayUntil   # a field of type `date` declared in the tabsets
```

The planned deletion date is `basedOn + duration`. With a custom date field, an item whose field is
empty **never expires**, and the `basedOn` day is inclusive: the item survives the whole day and
expires at its end (`P0D` = "display until this day included"). Expired items are
**soft-deleted** to the collection trash, then purged for good 30 days later.

As soon as the item is trashed (manual delete or retention), each `image`/`file` field applies its
`config.onDelete` rule to the media it references: `keep` (default) leaves the media alone,
`cascade` sends it to the media trash systematically, `orphan` sends it only when no other element
references it. Trashed media follow the media-trash lifecycle (quarantined file, CDN purge, hard
delete 30 days later). Restoring the item from its trash restores the media it dragged along, as long
as they have not been purged yet.

In the backoffice, upcoming deletions appear at the top of the dashboard and in the media library
("Suppressions prévues"), and a special `retention` column (the computed deletion date, sortable)
can be added to the collection `cols`.

## Preview for detail routes

`preview` declares backoffice "view on site" links. Each entry names a `route` and supplies `params`, whose values are template-evaluated per item. A `preview` is mandatory whenever a detail route exists for the collection.

<div class="q-callout q-callout-warn"><i class="fa-solid fa-triangle-exclamation lead"></i>
  <div><b>Params keys are flat and verbatim.</b> A <code>params</code> key must mirror the route token exactly, dots included: route path <code>/{{article.slug}}</code> means flat key <code>article.slug: '{{slug}}'</code>. A nested map (<code>article:</code> then <code>slug:</code>) silently breaks the backoffice list.</div></div>

See [Routing and URLs](/en/docs/routing-and-urls) for the route definition and [Custom fields](/en/docs/custom-fields) for the `query` field that reads the collection.

## Related

<div class="q-cards">
  <a class="q-card reco" href="/en/docs/custom-fields"><span class="k">Recommended</span><span class="t">Custom fields</span><span class="d">Field types, including <code>query</code>, <code>slug</code> and <code>checkbox</code>.</span></a>
  <a class="q-card" href="/en/docs/routing-and-urls"><span class="t">Routing and URLs</span><span class="d">Detail routes and preview params.</span></a>
  <a class="q-card" href="/en/docs/content-model"><span class="t">The content model</span><span class="d">Collection vs page-type vs content-type.</span></a>
</div>
