Assets
Madori includes a filesystem asset manager for uploading, organising, and browsing files. The public asset route reads the configured assetsPath; the /assets/... rewrite and /api/public/assets/... route work for custom roots as well as the default path. The Asset Manager provides a Control Panel interface and REST API.
The system supports images, documents, videos, audio, fonts, and archives, subject to upload safety checks. Active HTML and script extensions are rejected. Images get thumbnail previews; other files display type-appropriate icons.
Configuration Reference
Storage Configuration
Asset storage is configured in madori.config.ts:
| Option | Type | Default | Description |
|---|---|---|---|
assetsPath |
string |
./public/assets |
Directory where uploaded assets are stored |
// madori.config.ts
const config: MadoriConfigInput = {
assetsPath: './public/assets',
}
Assets are served through Madori's safe file route at /assets/... (and /api/public/assets/...), including files uploaded after startup. Requests are containment-checked; metadata sidecars are hidden. Active file types and SVG downloads use attachment disposition and a sandbox CSP.
Asset Field Configuration
Use the asset field type in blueprints to let editors pick files:
| Option | Type | Default | Description |
|---|---|---|---|
max_files |
number |
1 |
Maximum number of files. 1 = single file mode; 0 = unlimited |
min_files |
number |
0 |
Minimum number of selected files |
- handle: hero_image
field:
type: asset
display: Hero Image
options:
max_files: 1
Asset Metadata Storage
Metadata is stored alongside files as .meta.yaml:
# public/assets/images/hero.jpg.meta.yaml
alt: "Homepage hero banner"
| Property | Type | Description |
|---|---|---|
alt |
string |
Alt text for accessibility |
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/assets |
List assets (optional ?directory= param) |
| POST | /api/assets/upload |
Upload a single file (file field) |
| POST | /api/assets/upload-multiple |
Upload multiple files (files field) |
| PATCH | /api/assets/{path} |
Update asset metadata (alt text, filename) |
| DELETE | /api/assets/{path} |
Delete a single file |
| POST | /api/assets/move |
Move/rename a file |
| POST | /api/assets/bulk-move |
Move multiple files to a folder |
| POST | /api/assets/bulk-delete |
Delete multiple files |
| POST | /api/assets/directories |
Create a directory |
| POST | /api/assets/directories/delete |
Delete a directory |
| POST | /api/assets/directories/rename |
Rename a directory |
Display Modes
Assets are displayed based on their MIME type:
| MIME Type Pattern | Display Mode | Visual |
|---|---|---|
image/* |
Thumbnail | Image preview |
application/pdf |
Icon | Document icon |
video/* |
Icon | Video icon |
audio/* |
Icon | Music icon |
application/zip |
Icon | Archive icon |
| Other | Icon | Generic file icon |
Usage Examples
Uploading Files via the Control Panel
- Navigate to Assets in the CP sidebar
- Drag and drop files onto the page, or click Upload and select files
- Multiple files can be uploaded simultaneously — each shows individual progress
- Uploaded files appear in the grid immediately upon completion
Creating Folders
- In the Asset Manager, click Create Folder
- Enter a folder name (e.g. "Blog Images")
- Navigate into the folder by clicking it
- Use the breadcrumb trail to go back to parent folders
Editing Asset Metadata
- Click any asset in the grid to open its detail panel
- Edit the Alt text field (important for image accessibility)
- Edit the Filename if needed
- Changes persist immediately without page reload
Using Asset Fields in Blueprints
Single image:
- handle: featured_image
field:
type: asset
display: Featured Image
required: true
options:
max_files: 1
Multiple images (gallery):
- handle: gallery
field:
type: asset
display: Photo Gallery
options:
max_files: 10
Unlimited attachments:
- handle: attachments
field:
type: asset
display: Attachments
Programmatic Upload
const formData = new FormData()
formData.append('file', fileBlob, 'photo.jpg')
const response = await fetch('/api/assets/upload', {
method: 'POST',
body: formData,
})
const { data } = await response.json()
// data.path contains the asset reference returned by the API
Updating Metadata via API
await fetch('/api/assets/images/hero.jpg', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
alt: 'Updated alt text for hero image',
}),
})
Bulk Operations
Select multiple assets in the Control Panel (Shift/Cmd-click), then:
- Move — choose a destination folder and move all selected files
- Delete — confirm once to delete all selected files
Via API:
// Bulk move
await fetch('/api/assets/bulk-move', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paths: ['/assets/photo1.jpg', '/assets/photo2.jpg'],
destination: '/assets/archive/',
}),
})
// Bulk delete
await fetch('/api/assets/bulk-delete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
paths: ['/assets/old-file.pdf', '/assets/unused.png'],
}),
})
Common Patterns
Folder Organisation Strategy
Organise assets by purpose or content type:
public/assets/
├── blog/ # Blog post images
├── pages/ # Page-specific assets
├── branding/ # Logos, favicons, brand assets
├── downloads/ # Downloadable documents
└── uploads/ # User-uploaded content
Or organise by date for high-volume sites:
public/assets/
├── 2026/
│ ├── 01/
│ ├── 02/
│ └── 03/
Image Optimisation
Upload images at the dimensions you need. For responsive images, consider uploading multiple sizes:
public/assets/hero/
├── hero-1920.jpg # Desktop
├── hero-1024.jpg # Tablet
└── hero-640.jpg # Mobile
Use Next.js Image component with the asset paths:
import Image from 'next/image'
<Image src="/assets/hero/hero-1920.jpg" alt="Hero banner" width={1920} height={1080} />
Asset Picker in Forms
When an editor uses an asset field, the picker modal allows:
- Browsing existing folders
- Searching by filename
- Uploading a new file directly into the picker
- Selecting and confirming the choice
The selected asset path is stored in the entry data.
Alt Text Best Practices
Always add alt text to images for accessibility:
- Describe what the image shows, not what it is ("Team meeting in conference room" not "photo.jpg")
- Keep it concise — aim for under 125 characters
- Use empty alt text for purely decorative images
- Include relevant details that aren't available in surrounding text
Backing Up Assets
Since assets live on the filesystem, back them up alongside your content:
# Rsync to backup location
rsync -avz public/assets/ /backups/assets/
# Or include in Git (for smaller sites)
git add public/assets/
git commit -m "Add new blog images"
For large sites with many assets, consider excluding them from Git and using a separate backup strategy.
Referencing Assets in Templates
Assets are referenced by their public /assets/ URL (the configured root may be outside public/):
// File at public/assets/logo.svg → accessible at /assets/logo.svg
<img src="/assets/logo.svg" alt="Site logo" />
// File at public/assets/blog/post-image.jpg → accessible at /assets/blog/post-image.jpg
<img src="/assets/blog/post-image.jpg" alt="Blog illustration" />