Manual · 9

Data: Collections and REST sources

You use both kinds of data sources the same way in templates: data("slug").

9.1 Collections

A Collection is your own data structure with fields – e.g. Blog with title, date, image and text.

Create: Collections → + New → name and slug (the slug is the name for data("…")). In the Schema tab you create the fields:

Field type Input In the template
Text, Multi-line text, URL Text field {{ item.feld }}
Markdown Markdown editor with preview {{ item.feld|markdown }}
HTML Code editor {{ item.feld|safe }}
Number Number field {{ item.feld }}, {{ item.feld|floatformat:2 }}
Yes/No Checkbox {% if item.feld %}…{% endif %}
Date Date field (stored as YYYY-MM-DD) {{ item.feld }}
Choice List of fixed options {{ item.feld }}
Image Selection from the file manager {{ image(item.feld) }}
File Selection from the file manager {{ file(item.feld).url }}
Link (one entry) Selection of an entry from another Collection {{ item.feld.name }}
Links (multiple) Add, remove, sort entries {% for x in item.feld %}{{ x.name }}{% endfor %}
Blocks Form editor for blocks made of building blocks (see 7.6) {% blocks item.feld %}

The field name (e.g. titel) is the name in the template, the label is what appears in the form. Required enforces a value.

If you rename a field, existing entries keep their old values only until the entry is saved next. So plan field names as early as possible.

Language of the entries: In the schema you set which language the entries of a Collection are in. Only then can entries be linked with entries in other languages – see Multilingual websites.

Links between Collections: With the field types Link and Links, an entry points to entries of another (or the same) Collection. You choose the target Collection in the schema next to the type. This way a blog article has an author (1:n), and articles and tags can be assigned to each other many times over (n:m). Only the IDs are stored. In data() and item() the links are already resolved:

{% for a in data("blog") %}
  <h2>{{ a.titel }}</h2>
  <p>von {{ a.autor.name }} · {% for t in a.tags %}#{{ t.name }} {% endfor %}</p>
{% endfor %}

Backwards, i.e. “all articles by this author” or “all articles with this tag”, you filter the other Collection by the ID. For Links (multiple), the comparison means “contains”, with __ne “does not contain”:

{% for x in data("blog", "autor", autor.id) %}…{% endfor %}
{% for x in data("blog", "tags", tag.id, "sort", "-datum") %}…{% endfor %}
  • One level is resolved: an article’s tags are complete entries, but their own links are just IDs. That way, mutual links don’t form a loop. To go one level deeper, use data() and the filter.
  • Deleted entries and, for visitors, unpublished entries are dropped. A Link is then empty, and Links contain only the remaining entries. In the preview, drafts are included.
  • The Usage tab has matching examples, including for the reverse direction (“Linked from”). The Template data panel in the page editor shows the resolved entries.
  • The REST API (/collections/…/items) returns the IDs. GET /data/{slug} returns the same as data() in the template, i.e. resolved.
  • CSV: The export writes the IDs (several separated by commas). The import accepts IDs or the value of a text field of the target entry, preferring the slug. This way Webflow exports that output links as slugs fit. You separate multiple values with a comma or semicolon; anything unknown is reported as a notice.

Folders: In the Schema tab you can give a Collection a Folder, e.g. the name of a project. The list on the left groups by it, and each folder can be collapsed. Folders are only for organization and change nothing about data("…"). Snippets and CSS/JS files have the same field next to the name.

Entries are maintained in the Entries tab: + Entry, edit with a double-click or Edit, order with the arrows, Published per entry. Unpublished entries only appear in the preview.

In the Usage tab, the Collection shows ready-made template and API examples. With Publicly readable via the REST API, JavaScript clients can fetch the published entries without signing in – even from other domains (CORS is then enabled automatically).

CSV import and export: In the Entries tab, Export CSV downloads all entries (including drafts) as a CSV file – columns id, published and then the fields; UTF-8 with BOM so that Excel displays special characters correctly. Import CSV takes a CSV file (UTF-8; comma, semicolon or tab delimiters are detected) and maps its columns to the fields – automatically by field name or label, changeable per column. A preview shows the first entries the way they will be saved and lists all errors with row and column; as long as there are errors, nothing is imported. Everything or nothing is imported, new entries go at the end; Replace the existing entries deletes all existing ones first.

  • Matching instead of appending: Under Match you choose a field with unique values, usually the slug. If there is already an entry with the same value, it is updated – only the mapped columns; everything else (including the status, if no status column is mapped) stays; otherwise a new one is created. With Delete entries that are not in the file, this becomes a complete sync: entries keep their ID, only what’s missing from the file disappears. The preview shows new or changes #ID per row and the result at the top (e.g. “1 new · 2 updated · 1 deleted”). Duplicate values in the file or in the Collection are errors.

  • Editing in Excel: Export, edit the file (change rows, add new rows with an empty id, delete rows) and import it again – the id column is automatically recognized as the Entry ID and suggested for matching. An unknown ID is an error. Use a fresh export for this: if the newest entry is deleted, one created later may get its ID.

  • New Collection from CSV: Collections → CSV (next to + New). IllusionCMS suggests name, slug and for each column a field name and type (Number, Yes/No, Date, URL, HTML, Text …) – all changeable before the import; columns can be ignored.

  • Webflow exports: The administrative columns (Collection ID, Item ID, Created On …) are ignored; Draft and Archived determine the status (true = draft). For other files, you can use a column with yes/no yourself as Status: online or Status: draft; without such a column, all entries are published.

  • Values: Numbers may be written in German (1.234,50) or English (1,234.50) style – 1,5 counts as 1.5. Yes/No understands ja/nein, true/false, 1/0, x. Dates are converted from 2025-10-02, 02.10.2025, ISO timestamps and the Webflow format to YYYY-MM-DD. The import suggests values with a leading zero (postal codes) as text, not as a number.

  • Images and files: An image or file column contains either the ID of a file or the address of a file that has already been taken over via website import – this way, the image URLs of a Webflow export find the imported images. Other addresses stay empty (the preview shows them as a notice); if you want to keep the address, use the type URL.

  • The import triggers no Workflow events – with a thousand rows, there would otherwise be a thousand Workflow runs (e.g. notifications).

Live data: Some Collections fill up on their own on the live server – e.g. contact requests from a Workflow. If you check Live data in the schema, their entries are kept on the target when you upload a bundle from local; the test entries of your local instance don’t go along. The schema (fields, name) still comes from the local state. In the list, such Collections carry the Live badge.

Singleton: For things that exist exactly once – website settings, color codes, contact details, opening hours – check Singleton in the schema. The Collection then holds exactly one entry; instead of a list, you see the form right away under Content. In the template, data() returns the entry directly as an object:

{% set s = data("einstellungen") %}
<header style="--primaer: {{ s.primaerfarbe }}">{{ s.firmenname }} · {{ s.telefon }}</header>

The API (GET /api/v1/collections/einstellungen/items, /api/v1/data/einstellungen) also returns an object; it can be changed via PUT /api/v1/collections/einstellungen/item. A second entry is not possible, nor is a CSV import. Switching is only possible as long as the Collection has at most one entry.

Detail page and preview: If the entries appear on a Catch-All page (e.g. /blog/<slug>), select it in the schema under Detail page, along with the field containing the URL part (usually slug). When editing an entry, the preview of that page then appears on the right – with the not-yet-saved state, i.e. live as you type, switchable between Desktop/Tablet/Mobile; Open ↗ shows the saved entry on the website.

9.2 data() and item()

{% for post in data("blog") %}…{% endfor %}

Parameters follow as pairs of name and value:

Parameter Effect Example
sort Sort, - = descending data("blog", "sort", "-datum")
limit At most n entries data("blog", "limit", 3)
offset Skip the first n data("blog", "offset", 10, "limit", 10)
any field Filter on equality data("produkte", "kategorie", "schuhe")
feld__gt, __gte, __lt, __lte Greater / greater-or-equal / less / less-or-equal. Numbers are compared as numbers, date fields (YYYY-MM-DD) chronologically; entries without a value are dropped data("events", "datum__gte", today)
feld__ne Not equal data("blog", "kategorie__ne", "intern")

In addition, there are the fields id, _created and _updated (Unix time) – e.g. "sort", "-_created" for “newest first”. Values from variables work as well: data("produkte", "kategorie", route.query.kat).

You find a single entry with item():

{% with p=item("produkte", "slug", "sneaker-weiss") %}{{ p.name }}{% endwith %}

9.3 REST sources

REST sources connect any JSON API – shop, CRM, ticket system, headless CMS, your own services. The CMS fetches the API during rendering, extracts the list and caches the result for a while.

Create: REST sources → + New:

Field Meaning
Name / Slug The slug is the name for data("…")
Method / URL GET or POST, absolute http(s) URL
Headers e.g. Authorization: Bearer …
Body JSON for POST
List path Where the list is in the response, e.g. data.items (empty = whole response)
Cache How long the result is reused (seconds, 0 = never)
Timeout Maximum waiting time (1–60 seconds)

Placeholders make requests flexible:

  • {name} in the URL, body or headers is filled from the template: URL https://shop.example.com/api/products?cat={kat} → data("produkte", "kat", "schuhe").
  • {env.NAME} reads an environment variable of the server. This keeps credentials out of the database: header Authorization: Bearer {env.SHOP_TOKEN}, server started with SHOP_TOKEN=… ./illusioncms.

Testing: On the right, Test request shows the status, duration and the data. Under Lists in the result you’ll find all places where the response contains lists – one click takes over the path. Below that is a ready-made template example with the fields found.

Good to know:

  • If a request fails (error status, timeout, invalid JSON), the page cannot be rendered. When signed in, you see the exact cause.
  • A page with REST data stays in the page cache at most as long as the source’s cache duration.
  • Anyone who is allowed to create REST sources can make the server fetch arbitrary addresses – including in the internal network.