Upload & Storage
Media files are uploaded through the admin panel and served statically at the project root. The storage layer is intentionally simple — files live on disk, with no external dependencies required.
Upload Methods
The admin panel provides two upload methods: drag-and-drop onto the upload zone, or a standard file picker dialog. Both methods support selecting multiple files at once. Progress is shown per-file during upload.
Directory Structure
Files are stored in a media/ directory at the project root. Sub-directories are supported and can be created from within the admin panel:
project-root/
├── media/
│ ├── images/ # Uploaded images
│ ├── documents/ # PDFs and other documents
│ ├── videos/ # Video files
│ └── thumbnails/ # Auto-generated thumbnails (system-managed)
Accepted File Types
| Category | Extensions | Notes |
|---|---|---|
| Images | jpg, jpeg, png, gif, webp, avif, svg |
Thumbnails generated automatically for raster formats |
| Documents | pdf |
Served inline or as attachment |
| Videos | mp4, webm, ogg |
No server-side transcoding; files served as-is |
Configuration
Upload limits and accepted types are set in config/cms.json:
{
"media": {
"maxFileSizeMB": 20,
"allowedTypes": ["image/*", "application/pdf", "video/mp4", "video/webm"],
"directory": "media",
"generateThumbnails": true,
"thumbnailWidth": 320
}
}
Static Serving
Uploaded files are served directly by the web server at /media/filename. No CMS processing is involved on read — files are delivered as static assets for maximum performance.
# Example uploaded file URLs
/media/hero-banner.jpg
/media/images/team-photo.webp
/media/documents/brochure.pdf
Upload API
Programmatic uploads use a multipart form-data POST endpoint:
POST /api/media/upload
Content-Type: multipart/form-data
# Fields
file (required) — the file binary
directory (optional) — target sub-directory, e.g. "images"
overwrite (optional) — boolean, default false
# Response
{
"success": true,
"file": {
"name": "hero-banner.jpg",
"url": "/media/hero-banner.jpg",
"thumbnail": "/media/thumbnails/hero-banner_thumb.jpg",
"size": 204800,
"type": "image/jpeg",
"uploadedAt": "2026-03-13T10:00:00Z"
}
}
Media Library
The Media Library provides a full browser for all uploaded files within the admin panel. It is accessible both as a standalone page and as an inline picker when inserting media into the page editor.
Grid Layout
Files are displayed in a responsive grid. Image files show a thumbnail preview; documents and videos show a file-type icon. The grid adapts to available screen space:
| Breakpoint | Columns |
|---|---|
| Desktop (≥ 1024px) | 4 columns |
| Tablet (≥ 640px) | 2 columns |
| Mobile (< 640px) | 1 column |
Search & Filter
The toolbar above the grid provides:
- Search — live filename search (debounced, 300 ms)
- Type filter — All / Images / Documents / Videos dropdown
- Sort — by date uploaded (newest first), name (A–Z), or file size
- Directory — dropdown to switch between sub-directories
File Actions
Each tile in the grid supports the following interactions:
| Action | How | Result |
|---|---|---|
| Preview | Click thumbnail | Opens lightbox or document viewer |
| Copy URL | Click icon | Copies site-root-relative URL to clipboard |
| Edit image | Click icon | Opens built-in image editor (see section 3) |
| Delete | Click icon | Confirm dialog, then removes file and thumbnail |
| Insert | Click Insert button | Inserts Markdown image syntax at cursor (editor context only) |
Insert Mode
When the Media Library is opened from within the page editor, each tile gains an Insert button. Clicking it closes the picker and inserts the appropriate Markdown syntax at the current cursor position:
# Image insertion

# Document insertion (link)
[brochure.pdf](/media/documents/brochure.pdf)
Image Editor
The built-in image editor is powered by Sharp on the server side (Node.js). All operations are applied server-side for consistent, high-quality output regardless of the client device. The browser-side UI sends an operations payload to the API; Sharp processes and returns the result.
The editor consists of a left-hand toolbox, a central preview pane, and a save bar at the bottom. The preview updates in real-time as options are adjusted (debounced at 500 ms to avoid excessive API calls).
Crop
A drag-to-select crop region overlaid on the preview image. Aspect ratio options:
| Mode | Description |
|---|---|
| Free | Drag handles freely in any direction |
| 1:1 | Square crop, constrained proportionally |
| 4:3 | Standard photo / presentation ratio |
| 16:9 | Widescreen / hero image ratio |
| Custom | Enter exact pixel dimensions |
Resize
Specify target dimensions in pixels. An aspect-ratio lock toggle keeps the other dimension proportional when one is changed.
# Sharp operation payload (excerpt)
{
"resize": {
"width": 1200,
"height": 630,
"fit": "cover", // cover | contain | fill | inside | outside
"maintainAspect": true
}
}
Rotate
Quick-select presets (90°, 180°, 270°) or a numeric input for any custom angle (0–359). Background fill colour is configurable for non-rectangular rotations.
Flip
Two independent toggle buttons: Flip Horizontal (mirror left-right) and Flip Vertical (mirror top-bottom). Both can be active simultaneously.
Filters
Each filter is a slider control with a live numeric readout:
| Filter | Range | Default | Sharp Method |
|---|---|---|---|
| Brightness | 0.1 – 3.0 | 1.0 | .modulate({ brightness }) |
| Contrast | -100 – 100 | 0 | .linear() |
| Saturation | 0.0 – 3.0 | 1.0 | .modulate({ saturation }) |
| Blur | 0 – 50 | 0 | .blur(sigma) |
| Sharpen | 0 – 10 | 0 | .sharpen({ sigma }) |
| Grayscale | Toggle (on/off) | Off | .grayscale() |
Watermarks
Text watermarks are composited onto the image by Sharp using an SVG overlay. Options:
- Text — the watermark string
- Font size — in points (12–120)
- Font colour — hex colour picker
- Position — nine-point grid (top-left, top-centre, top-right, centre-left, centre, centre-right, bottom-left, bottom-centre, bottom-right)
- Opacity — 0–100%
# Watermark payload example
{
"watermark": {
"text": "© MyBrand 2026",
"fontSize": 36,
"colour": "#ffffff",
"position": "bottom-right",
"opacity": 60
}
}
Borders
Adds a flat-colour border around the image using Sharp's .extend() method. Configurable options:
- Width — uniform border in pixels (1–100), or per-side (top, right, bottom, left)
- Colour — hex colour picker with alpha channel
Format Conversion
Convert between the following output formats using the format selector dropdown:
| Format | Quality Slider | Notes |
|---|---|---|
| JPEG | Yes (1–100) | Best for photographs; no transparency |
| PNG | No (lossless) | Best for graphics with transparency |
| WebP | Yes (1–100) | Modern format; smaller than JPEG at equal quality |
| AVIF | Yes (1–100) | Best compression; wide browser support as of 2024 |
Save Options
The save bar at the bottom of the editor presents two options:
- Save as new file — writes a new file to disk with a
-editedsuffix (or a custom name entered in the filename field). The original is untouched. - Overwrite original — replaces the original file in-place. A confirmation dialog is shown before proceeding.
Editor API Endpoint
POST /api/media/edit
Content-Type: application/json
{
"source": "/media/images/hero.jpg",
"operations": {
"crop": { "left": 0, "top": 0, "width": 1200, "height": 630 },
"resize": { "width": 800, "maintainAspect": true },
"rotate": 90,
"flip": { "horizontal": false, "vertical": true },
"filters": { "brightness": 1.1, "saturation": 1.2, "grayscale": false },
"watermark": { "text": "© 2026", "position": "bottom-right", "opacity": 50 },
"border": { "width": 4, "colour": "#000000" },
"format": { "type": "webp", "quality": 85 }
},
"saveAs": "hero-edited.webp" // omit to overwrite original
}
# Response
{
"success": true,
"file": {
"url": "/media/images/hero-edited.webp",
"size": 98304,
"width": 800,
"height": 420
}
}
Embedding in Pages
Media files can be embedded in page content using standard Markdown syntax, extended caption syntax, or the Domma CMS shortcode system for finer control over sizing and display.
Standard Markdown
The simplest embed — renders as a plain <img> tag:

With Caption
The optional title attribute is rendered as a visible caption below the image:

This produces a <figure> / <figcaption> pair in the rendered HTML.
Shortcode Syntax
For greater control, use the CMS shortcode syntax. Shortcodes are processed server-side at render time:
[image src="/media/hero.jpg" alt="Hero banner" width="800"]
[image src="/media/photo.jpg" alt="Team" width="600" height="400" class="rounded shadow-md"]
[image src="/media/logo.svg" alt="Logo" align="right" float="true"]
Supported Shortcode Attributes
| Attribute | Type | Description |
|---|---|---|
src | string (required) | Site-root-relative URL to the media file |
alt | string | Alt text for accessibility |
width | integer (px) | Render width; height inferred if omitted |
height | integer (px) | Render height; width inferred if omitted |
class | string | Additional CSS classes on the <img> |
align | left | centre | right | Horizontal alignment |
float | true | false | Wraps text around image when true |
caption | string | Visible caption (uses <figcaption>) |
lazy | true | false | Adds loading="lazy" (default: true) |
Responsive Images & srcset
When a raster image is uploaded, Domma CMS automatically generates multiple size variants and writes a srcset attribute into the rendered HTML. No configuration is required:
<!-- Rendered output for standard Markdown or shortcode -->
<img
src="/media/hero.jpg"
srcset="
/media/hero-320w.jpg 320w,
/media/hero-640w.jpg 640w,
/media/hero-1200w.jpg 1200w
"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 1200px"
alt="Hero banner"
loading="lazy"
width="1200"
height="630"
>
Size variants are generated at upload time (320w, 640w, 1200w) and stored in media/thumbnails/. If a variant does not yet exist when the page is rendered, it is generated on demand and cached.
Inserting Media from the Page Editor
The page editor toolbar includes a Media button. Clicking it opens the Media Library in picker mode. When a file is selected, the editor inserts the appropriate syntax at the current cursor position:
- For images: inserts Markdown image syntax —
 - For documents: inserts a Markdown link —
[filename.pdf](/media/documents/filename.pdf) - For videos: inserts a shortcode —
[video src="/media/videos/clip.mp4"]
URL Portability
All media URLs are site-root-relative (beginning with /media/). This ensures that content remains valid when pages are moved between directories or when the CMS is deployed to a different domain. Absolute URLs are never written into page content.
Document Links
# Inline link to a PDF
[Download our brochure](/media/documents/brochure.pdf)
# Force browser download (using shortcode)
[file src="/media/documents/report.pdf" label="Download Report" download="true"]
Video Embeds
# Basic video embed
[video src="/media/videos/intro.mp4"]
# With poster image and controls
[video src="/media/videos/intro.mp4" poster="/media/images/intro-thumb.jpg" controls="true" width="800"]
The [video] shortcode renders a <video> element with preload="metadata" by default. The controls attribute is true unless explicitly set to false.