Content Models
Content models are the heart of DM Editors. They define the shape of every entry — fields, validation, defaults, and how content is exposed through the API.
Two flavors: collection vs single
- Collection types — many entries per model (Blog Posts, Products, Team Members).
- Single types — exactly one entry per locale (Homepage, About, Site Footer). Edited via a single-entry shortcut.
Routes
| Route | Method | Purpose |
|---|---|---|
/admin/content-models | GET | List models |
/admin/content-models/create | GET | New model form |
/admin/content-models | POST | Create model |
/admin/content-models/{content_model}/edit | GET | Edit model + field builder |
/admin/content-models/{content_model} | PUT | Update model |
/admin/content-models/{content_model} | DELETE | Soft delete (cascades to entries) |
/admin/content-models/reorder | POST | Drag-and-drop reordering in sidebar |
/admin/content-models/{content_model}/toggle-active | PATCH | Activate / deactivate |
/admin/content-models/{content_model}/computed-fields | POST | Save computed-field definitions |
/admin/content-models/{content_model}/test-computed-field | POST | Test a computed expression against a sample entry |
Model attributes
| Attribute | Notes |
|---|---|
name | Display name (e.g. "Blog Posts") |
slug | URL-friendly identifier (e.g. blog-posts) — used in routes and the API |
name_singular / name_plural | Used in UI labels |
icon | Sidebar icon name |
type | collection | single |
is_single | Convenience boolean mirroring type |
has_slug | Enable per-entry slug field |
has_timestamps | Show created/updated on the entry list |
has_publish_date | Enable published_at + scheduling |
has_seo | Add the SEO sub-form (title, description, OG image) |
title_field | Which field's value is treated as the entry title (default title) |
is_localized | Allow per-locale entries |
show_in_sidebar | Whether the model appears in the admin nav |
sort_order | Position in the sidebar |
settings | JSON — UI-level preferences |
computed_fields | JSON — array of computed field definitions |
api_config | JSON — public visibility, field allowlists, default sorts |
Field builder
Each model has a list of content_fields built via drag-and-drop in the model editor. Each field has:
slug— key in the entry'sdataJSON.label— display name in the entry form.type— see below.is_required,is_unique,default.validation— JSON rule overrides.settings— type-specific (e.g. min/max length, options array).sort_order.
Available field types
| Type | Description |
|---|---|
text | Single-line text input |
textarea | Multi-line text |
richtext | TipTap rich-text editor |
markdown | Markdown editor with preview |
number | Numeric input (int or float) |
boolean | Toggle |
date / datetime | Date pickers |
select / multiselect | Predefined option list |
media | Single media picker (file UUID) |
media_multi | Gallery — array of media UUIDs |
relation | Reference another model's entries |
repeater | Repeating field group |
component | Embed a defined component |
dynamic_zone | Editor-chosen mix of components (Phase 2.5) |
json | Raw JSON editor |
color | Color picker |
url / email / phone | Validated text variants |
Computed fields
Computed fields are server-evaluated values stored on each entry's computed_data JSON column. Definitions live on the model's computed_fields column and are re-evaluated on entry save. The test-computed-field endpoint lets you preview an expression against a sample entry before committing.
// Example computed_fields definition
[
{
"slug": "reading_time",
"label": "Reading time (min)",
"expr": "ceil(word_count(data.body) / 200)"
},
{
"slug": "full_name",
"label": "Full name",
"expr": "data.first_name ~ ' ' ~ data.last_name"
}
]
API exposure
A model's api_config column controls what is exposed at /api/v1/{slug} — visibility, allowed fields, default sort, default per-page. See API: Dynamic Content for endpoint details.
cascadeOnDelete). Restore by undeleting at the DB level; consider exporting first.