Pages are stored as Markdown files in content/pages/. Each file has a YAML frontmatter
block at
the top followed by the page body in Markdown.
| File path | Public URL |
|---|---|
content/pages/index.md |
/ |
content/pages/about.md |
/about |
content/pages/blog/index.md |
/blog |
content/pages/blog/hello-world.md |
/blog/hello-world |
---
title: My Page
description: A short description for SEO meta tags.
status: published # published | draft (draft pages return 404)
layout: default # layout name from config/presets.json
tags: [one, two] # optional taxonomy
updatedAt: 2026-03-01 # auto-set on save
---
# Page heading
Your Markdown content goes here.
Pages with status: draft are never served on the public site — they return a 404.
Toggle status
in the Page Editor to publish.
Layouts are defined in config/presets.json and apply CSS class wrappers around page
content.
Select a layout per-page in the editor, or change the default in Site Settings.
Media files are stored in content/media/ and served publicly at /media/{filename}.
Reference uploaded files by their public URL in Markdown:

<img src="/media/my-image.jpg" alt="Alt text">
The maximum file size is configured in config/server.json under uploads.maxFileSize
(bytes). Default is 10 MB.
The navigation tree is stored in config/navigation.json and injected into every public
page as
window.__CMS_NAV__.
Nest items under a parent using the drag-and-drop tree in the Navigation editor. Child items are
stored under
the items key — the public navbar renders them as a dropdown.
Set the URL to a full https:// address to link outside the site. Leave blank for
section
headings.
Set the icon field to any registered icon name. Icons appear in the navbar link
alongside the
label.
DConfig lets you wire up declarative behaviour on any public page — no JavaScript required. Define event handlers and class toggles in JSON; the page runtime applies them automatically on load.
DConfig can be set in two ways, and both are merged at runtime (inline shortcode wins on selector conflict):
window.__CMS_DCONFIG__.
[dconfig]…[/dconfig] blocks
directly in
the page body Markdown.
{
"#selector": {
"events": {
"eventName": {
"target": "#target-selector",
"action": "value"
}
}
}
}
Multiple selectors and multiple event types can appear in a single config block.
| Event | Description |
|---|---|
click |
Fires when the selector element is clicked. |
| Action key | Value type | Description |
|---|---|---|
toggleClass |
string |
Adds the class if absent, removes it if present, on the target element.
Typical use:
show/hide content with a hidden class.
|
Additional actions (addClass, removeClass, setAttribute, scroll-to) are planned for future releases.
idAll shortcodes support an id attribute, which is written directly onto the generated
element.
Use this instead of raw HTML when you want DConfig to target a card, column, or grid:
[card id="my-panel" title="Hidden Details"]
Content goes here.
[/card]
[dconfig]
{
"#my-btn": {
"events": {
"click": { "target": "#my-panel", "toggleClass": "hidden" }
}
}
}
[/dconfig]
Supported on: [card], [col], [row], [grid].
[dconfig] shortcode[dconfig]
{
"#my-btn": {
"events": {
"click": { "target": "#my-panel", "toggleClass": "hidden" }
}
}
}
[/dconfig]
<button id="my-btn" class="btn btn-primary">Toggle</button>
<div id="my-panel" class="card hidden">
<div class="card-body">Hidden content.</div>
</div>
Any number of selectors can appear in one block. Selectors are matched with
document.querySelector — use IDs, classes, or attribute selectors.
[dconfig]
{
"#show-btn": {
"events": {
"click": { "target": "#panel", "toggleClass": "hidden" }
}
},
"#hide-btn": {
"events": {
"click": { "target": "#panel", "toggleClass": "hidden" }
}
}
}
[/dconfig]
The same JSON (without the shortcode tags) can be pasted directly into the DConfig card in
the page
editor. This is useful when the config should apply to the whole page without appearing in the
body content.
Inline [dconfig] shortcodes are applied last and will win on any shared selector key.
See live demos on the Interactive Resources page.
Shortcodes extend Markdown with layout components. They are processed server-side before Markdown rendering, so you can freely mix shortcodes and Markdown in the same page body.
Syntax: [shortcode attribute="value"] (self-closing: [shortcode
/]) and [/shortcode] for paired tags.
| Shortcode | Description |
|---|---|
[grid cols="N" gap="N"]...[/grid] |
CSS Grid container — N columns (1–12), optional gap (1–6) |
[row gap="N"]...[/row] |
Flexbox row — columns share width equally |
[col span="N"]...[/col] |
Column inside a grid or row; span sets column span |
| Shortcode | Description |
|---|---|
[card title="..."]...[/card] |
Styled card. Supports 6 variants, ~44 gradients and 33 layouts — see the Cards subsection below. |
[tabs]...[/tabs] |
Tab group; use [tab title="..."]...[/tab] inside |
[accordion]...[/accordion] |
Accordion group; use [item title="..."]...[/item] inside |
[carousel]...[/carousel] |
Carousel; use [slide]...[/slide] inside |
[badge variant="success"]...[/badge] |
Inline badge; variants: primary, secondary, success, danger, warning, info |
[icon name="star" /] |
Inline Domma icon; optional size and color |
[spacer size="16" /] |
Vertical whitespace block (px height) |
[timeline]...[/timeline] |
Progression timeline; use [event title="..." date="..."
status="..."]...[/event] inside
|
[countdown to="2026-12-31" /] |
Animated countdown to a date |
The [card] shortcode is the most configurable component in the system. It has three
layers you can mix freely: variant (the overall visual style), gradient
(colour palette, used with variant="gradient" or any layout that accepts a gradient),
and layout (the internal structure — header/body/footer, image position, sub-tags,
etc.). Every layout accepts every variant, gradient and universal attribute listed below.
[card variant="gradient" gradient="ocean" layout="icon-top" icon="star" title="Fast"]
Body content in **Markdown** is supported.
[/card]
| Variant | Description |
|---|---|
clean |
Flat, minimal chrome — no border, subtle background |
gradient |
Coloured gradient background (see Gradients below). Pair with gradient="name"
|
glass |
Frosted glass / backdrop-blur effect, translucent background |
accent |
Primary-colour accent border on the left edge |
dark |
Dark background with inverted text — good on light pages |
glow |
Subtle halo / outer glow effect |
primary |
Legacy alias — adds card-primary class for backwards compatibility |
Shipped in public/css/ — available on every install. Default when
gradient is omitted: indigo.
arctic, aurora, dusk, fire, forest,
gold, indigo, lagoon, lime,
midnight, ocean, rose, slate,
sunset
Defined in content/custom.css on this site only — fresh installs will not have
these unless the CSS is copied over.
Single-tone: purple, blue, green,
night
Theme · light pairs: ocean-light, forest-light,
sunset-light, royal-light, lemon-light,
silver-light, charcoal-light, christmas-light,
unicorn-light, dreamy-light, grayve-light,
mint-light, wedding-light
Theme · dark pairs: ocean-dark, forest-dark,
sunset-dark, royal-dark, lemon-dark,
silver-dark, charcoal-dark, christmas-dark,
unicorn-dark, dreamy-dark, grayve-dark,
mint-dark, wedding-dark
Gradient names are not validated — a typo falls through to the
default background with no warning. Use /test-card-gradients to preview every
palette.
These work on any layout and any variant.
| Attribute | Values | Purpose |
|---|---|---|
title |
string | Card heading (most layouts) |
subtitle |
string | Secondary heading (many layouts re-use it as date / role / plan) |
icon |
icon name | Domma icon (see Icons view) |
footer |
string | Footer text (also used by pricing as the CTA label) |
image |
URL | Background image for image / media layouts |
collapsible |
true |
Body toggles open/closed via click on the header |
hover |
boolean flag | Raises the card on mouse-over |
borderless |
boolean flag | Removes the card border |
shadow |
none · md · lg |
Drop-shadow depth |
rounded |
none · sm · lg ·
full |
Corner radius |
padding |
compact · spacious |
Internal spacing |
text-align |
center · right |
Body text alignment (default left) |
font |
serif · mono |
Override card typeface |
font-size |
sm · lg · xl |
Override card body text size |
class |
string | Extra CSS classes appended to the root element |
id |
string | DOM id (useful for anchor links / dconfig targets) |
Select a layout with layout="name". If omitted, the card uses a simple body-only
layout. Layouts marked (needs sub-tags) will render empty without their child tags.
| Layout | Attributes | Purpose |
|---|---|---|
basic |
footer |
Body-only card, optional footer |
header-body |
title |
Header with title + body |
header-body-footer |
title, footer |
Classic three-zone card |
no-header-footer |
footer |
Body + footer only |
| Layout | Attributes | Purpose |
|---|---|---|
icon-top |
icon, title, subtitle |
Large centred icon above title — good for feature grids |
icon-inline |
icon, title, subtitle |
Icon sits to the left of the title in the header row |
| Layout | Attributes | Purpose |
|---|---|---|
image-top |
image, title |
Image banner above header + body |
image-overlay |
image, title |
Title overlaid on the image with a tint |
thumb-left |
image, title |
Square thumbnail on the left, body on the right |
thumb-right |
image, title |
Mirror of thumb-left |
wide-left-image |
image, title, footer |
Horizontal card, wider image strip on the left |
full-bg |
image, title |
Full-bleed image backdrop with body text over it |
split-half |
image, gradient, title |
Half gradient (or image) on the left, content on the right |
| Layout | Attributes | Purpose |
|---|---|---|
video-media |
image (poster), duration, title |
Video thumbnail with a play button and duration badge |
location-map |
address, title |
Placeholder map panel + address block (static — no live map) |
| Layout | Attributes | Purpose |
|---|---|---|
avatar-profile |
icon, title, subtitle,
tags (comma-separated)
|
Circular avatar + name + role + pill tags — team / author cards |
quote-testimonial |
title (author), subtitle (role) |
Quotation mark + body quote + author attribution row |
rating-review |
rating (0–5), title, subtitle,
verified (flag)
|
Star rating + review body + reviewer attribution |
| Layout | Attributes | Purpose |
|---|---|---|
stat-metric |
title (label), value, delta,
progress (0–100)
|
Big-number KPI tile with delta arrow and optional progress bar |
progress-goal (needs sub-tags) |
title, subtitle, progress; child
[milestone done]...[/milestone]
|
Progress bar + milestone checklist |
activity-feed (needs sub-tags) |
title; child
[activity user="..." action="..." time="..." /]
|
User activity list with initials avatars and timestamps |
| Layout | Attributes | Purpose |
|---|---|---|
callout |
callout-type (info/warn/success/error),
icon, title
|
Coloured inline notice / tip box |
step-numbered |
step (number), title, gradient |
Numbered step for tutorials / walkthroughs |
corner-badge |
badge (label), icon, title |
Card with a ribbon-style badge in the top-right corner |
badge-band |
badge, icon, gradient, title |
Full-width coloured band across the top with a label and icon |
tag-cloud |
title, tags (comma-separated) |
Coloured pill list — topic tags, skills, categories |
timeline-entry |
title, subtitle (date), badge (tag) |
Single dot-on-line timeline entry — stack several for a history list |
| Layout | Attributes & sub-tags | Purpose |
|---|---|---|
pricing (needs sub-tags) |
title, price, period, gradient,
footer (CTA label); child [feature]...[/feature]
|
Pricing tier card with gradient header, feature list and CTA link |
feature-comparison (needs sub-tags) |
title, subtitle (plan), gradient; child
[feature]text[/feature] or [feature excluded]text[/feature]
|
Plan comparison rows with tick / cross marks |
before-after (needs sub-tags) |
title; children
[before]item · item[/before] and
[after]item · item[/after] (items split on · or
newline)
|
Two-column before/after comparison list |
| Layout | Attributes | Purpose |
|---|---|---|
glass-gradient-border |
title |
Frosted glass card with a gradient border ring. Note: this layout
bypasses cardVariantClasses, so universal variant / gradient / shadow
attributes do not apply.
|
code-snippet |
lang (label); body is rendered as escaped code |
Code block with language label header |
file-document |
filename, filesize, filetype, title
|
File attachment row with type icon and download link (link href is placeholder) |
These are child tags accepted by specific layouts — they are not shortcodes in their own right and only render inside a matching parent card.
| Sub-tag | Parent layout | Attributes |
|---|---|---|
[feature]text[/feature] |
pricing, feature-comparison |
excluded (flag, comparison only) — renders as a cross instead of a tick
|
[before]a · b · c[/before] |
before-after |
Items split on · or newline |
[after]a · b · c[/after] |
before-after |
Items split on · or newline |
[activity user="..." action="..." time="..." /] |
activity-feed |
Self-closing; one row per tag |
[milestone done]text[/milestone] |
progress-goal |
done (flag) — filled dot if present, hollow otherwise |
| Shortcode | Description |
|---|---|
[hero variant="dark" size="sm"]...[/hero] |
Full-width hero section; supports twinkle, blobs,
image, overlay |
[table striped="true"]...[/table] |
Wraps a GFM Markdown table with Domma table CSS classes |
[form name="slug" /] |
Embeds a Form Builder form by slug |
[collection slug="..." display="table" /] |
Renders collection entries inline (table, cards, list, accordion, or block) |
| Attribute | Values | Purpose |
|---|---|---|
slug |
collection slug | Required. Which collection to render. |
display |
table · cards · list ·
accordion · block |
How to render each entry. Default table. |
block |
block template name | Required when display="block". Names a template in
content/blocks/<name>.html whose
{{field}} placeholders get substituted per entry.
|
cols |
2 · 3 · 4 · 5
· 6 |
Grid column count when display="block". |
where |
field=value orf1=v1,f2=v2 |
Row filter (simple equality only, AND'd across comma-separated predicates). Example:
where="tab=developers" or
where="tier=free,featured=true". No OR, no comparison operators —
use a saved View for anything more complex.
|
sort |
field name or createdAt |
Sort field. Default createdAt. |
order |
asc · desc |
Sort direction. Default desc. |
limit |
integer | Maximum number of entries to render. 0 or omitted = all. |
fields |
comma-separated field names | Column filter — only these fields are shown (table/cards/list displays only). |
title-field |
field name | Which field to use as the entry title (cards/accordion displays). |
empty |
string | Message shown when the filter matches no entries. |
cta |
action slug | Attach a CMS Action button to each rendered entry. See CTA attributes below. |
Two optional settings control what happens after a submission is stored:
| Field | Where | Description |
|---|---|---|
successMessage |
Settings tab | Text shown inline after submission (replaces the form). Default: "Thank you for your submission." |
successRedirect |
Settings tab | URL to redirect the visitor on success. Takes priority over successMessage if
set.
|
actionSlug |
Actions tab → CMS Action | Slug of a CMS Action to execute after the entry is stored. Requires Pro (MongoDB). Non-fatal on failure. |
For the full walkthrough, see the Tutorials → Form Follow-Up: Notifications & Actions tutorial.
| Shortcode | Description |
|---|---|
[slideover title="..." trigger="..."]...[/slideover] |
Slide-in panel opened by a trigger button |
[dconfig]{...}[/dconfig] |
Declarative click handlers and class toggles — no JavaScript needed |
| Shortcode | Description |
|---|---|
[view slug="..." display="table" /] |
Executes a saved View and renders results. Requires MongoDB. |
[cta action="..." entry="..."]Label[/cta] |
Action-trigger button. Requires the visitor to be logged in. |
Full attribute reference and live demos: Shortcode Reference · Components · Effects
Site Settings are stored in config/site.json and editable from the Settings page in
the admin
panel.
| Field | Description |
|---|---|
siteName |
Site title used in the public navbar and browser tab. |
tagline |
Subtitle shown in the public site footer. |
adminTheme |
Admin panel colour theme (e.g. charcoal-dark, ocean-light). |
publicTheme |
Public site theme applied to <html data-theme>. |
defaultLayout |
Fallback layout for pages that don't specify one. |
Low-level settings (port, CORS, upload size) live in config/server.json and require a
server
restart to take effect. These are not editable from the admin panel.
Plugins extend the CMS with new server routes, admin views, and public-site injection snippets.
Each plugin
is a directory under plugins/.
Use the Plugins page in the admin to toggle plugins on or off. State is saved to
config/plugins.json. A server restart is required for changes to take
effect.
Plugins that expose a settings page will appear as a link in the sidebar under the Plugins section.
Settings
are merged with the plugin's defaults from config.js and saved to
config/plugins.json.
Only files inside a plugin's admin/ and public/ subdirectories are served
statically. Server files (plugin.js, config.js, data/) are
blocked and
return 404.
See the Tutorials section for a guide to writing your own plugin.
Users are stored as individual JSON files in content/users/. Passwords are hashed with
bcrypt
and never returned by the API.
| Role | Access |
|---|---|
| Admin | Full access — users, plugins, settings, all content. |
| Manager | Content + structure (navigation, layouts). No users or plugins. |
| Editor | Pages and media only. |
| Subscriber | Read-only access to the API. |
The admin panel uses JWT Bearer tokens. Tokens are stored in the browser and automatically
refreshed.
Sessions expire according to config/auth.json — default is 24 hours.
Views and Actions are pro-only features that require a MongoDB connection. Both roles — Admin and Manager — can create, edit, and run Views and Actions.
| Role | Views | Actions |
|---|---|---|
| Admin | Full access | Full access |
| Manager | Full access | Full access |
| Editor | No access | No access |
| Subscriber | No access | No access |
Views are admin-designed display configurations that query one or more Collections via a stored MongoDB aggregation pipeline, then render the results in the admin panel. Views require a MongoDB connection configured under Collections → Options.
| Stage | Purpose |
|---|---|
$match |
Filter documents by condition |
$lookup |
Left join from another collection |
$sort |
Sort documents |
$project |
Include or exclude fields |
$unwind |
Deconstruct an array field |
$addFields |
Compute and add new fields |
$group |
Group and aggregate |
$count |
Count documents |
$skip |
Skip documents (added automatically for pagination) |
$limit |
Limit documents (added automatically for pagination) |
Forbidden stages ($out, $merge, $function, $accumulator,
$graphLookup) are rejected at save and execution time.
On the Display tab, column definitions are a JSON array. Each object has a key
(dot-path into the result document) and a label.
[
{ "key": "data.name", "label": "Name" },
{ "key": "data.email", "label": "Email" },
{ "key": "orders", "label": "Orders" }
]
If no columns are defined, the preview auto-generates columns from the first result document.
Tick Public on the Access tab to expose the view via
GET /api/views/:slug/public without requiring a login.
Leave it unticked to require JWT authentication with one of the allowed roles.
Actions are admin-designed sequential workflow operations triggered against individual collection entries. When an action is assigned to a collection, a trigger button appears per row in the entry list. Actions require a MongoDB connection (pro mode).
| Step | What it does | Config fields |
|---|---|---|
updateField |
Set a field on the entry to a new value | field, value |
deleteEntry |
Permanently delete the entry | None |
moveToCollection |
Create the entry in a target collection then delete it from the source | targetCollection |
webhook |
HTTP request to an external URL | url, method, body (JSON) |
email |
Send an email via the configured SMTP transport | to, subject, template |
Step config fields support {{variable}} interpolation:
| Variable | Resolves to |
|---|---|
{{entry.data.fieldName}} |
A field value from the current entry |
{{entry.id}} |
The entry's ID |
{{now}} |
Current timestamp (ISO 8601) |
{{user.name}} |
Name of the user who triggered the action |
{{user.email}} |
Email of the triggering user |
{{env.CMS_PUBLIC_*}} |
Environment variables prefixed CMS_PUBLIC_ only |
Example — approve an application and notify by email:
Step 1: updateField field=status value=approved
Step 2: updateField field=approvedAt value={{now}}
Step 3: email to={{entry.data.email}}
subject=Your application has been approved
template=Congratulations {{entry.data.name}}, your application is approved.
Actions are not transactional. If a step fails, the action stops and returns the
number of steps completed so far (stepsCompleted). Steps that already ran are
not rolled back. Design step order with this in mind — put irreversible steps
(delete, email) last.
If an action contains a deleteEntry step, subsequent steps will fail because the entry
no longer exists. Place deleteEntry as the last step.
The [cta] shortcode places an action-trigger button in any public page. Clicking it
calls
POST /api/actions/:slug/public with the entry ID, using the logged-in user's JWT.
If the user is not logged in, a warning toast is shown instead.
Wrapping form:
[cta action="slug" entry="entry-id" icon="check" confirm="Are you sure?"]Button label[/cta]
Self-closing form:
[cta action="slug" entry="entry-id" label="Button label" /]
| Attribute | Required | Default | Description |
|---|---|---|---|
action |
Yes | — | Action slug |
entry |
Yes | — | Entry UUID to act on |
label |
No | "Run" |
Button text (self-closing only) |
style |
No | "primary" |
primary · secondary · ghost · danger |
icon |
No | — | Domma icon name |
size |
No | — | sm · md · lg |
confirm |
No | — | Confirmation prompt before executing |
Add per-entry buttons to any [collection] shortcode:
[collection slug="applications" display="cards" title-field="name"
cta="approve-application"
cta-label="Approve"
cta-icon="check"
cta-style="primary"
cta-confirm="Approve this application?" /]
| Attribute | Default | Description |
|---|---|---|
cta |
— | Action slug — enables per-entry buttons |
cta-label |
"Run" |
Button label |
cta-icon |
— | Domma icon name |
cta-style |
"primary" |
Button variant |
cta-confirm |
— | Confirmation prompt |
All three display modes support CTA buttons: cards (card footer), list (inline), table (dedicated column).