MADORIMADORI

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

  1. Navigate to Assets in the CP sidebar
  2. Drag and drop files onto the page, or click Upload and select files
  3. Multiple files can be uploaded simultaneously — each shows individual progress
  4. Uploaded files appear in the grid immediately upon completion

Creating Folders

  1. In the Asset Manager, click Create Folder
  2. Enter a folder name (e.g. "Blog Images")
  3. Navigate into the folder by clicking it
  4. Use the breadcrumb trail to go back to parent folders

Editing Asset Metadata

  1. Click any asset in the grid to open its detail panel
  2. Edit the Alt text field (important for image accessibility)
  3. Edit the Filename if needed
  4. 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" />