Skip to content

Assets

The admin includes a media library at /admin/assets with:

  • Grid view with thumbnails
  • Folder organization (create, rename, delete, drag-to-move)
  • Upload via button or drag-and-drop
  • Alt text editing
  • Focal point selector (click image to set crop center)

Image fields render an upload button and a browse dialog that connects to the media library. The stored value is the file URL (/uploads/filename.ext).

Files are stored in public/uploads/ by default. Asset metadata (filename, mime type, size, alt, focal point) is stored in the cms_assets table.

MethodEndpointDescription
POST/api/cms/assets/uploadUpload file (multipart/form-data)
GET/api/cms/assetsList assets
GET/api/cms/assets/:idGet single asset
PATCH/api/cms/assets/:idUpdate metadata
DELETE/api/cms/assets/:idDelete asset and file
ParamTypeDescription
limitnumberMax results (default 50)
offsetnumberSkip N results
folderstringFilter by folder ID (empty string for root)
FieldTypeDescription
altstringAlt text
folderstring | nullMove to folder (null for root)
focalXnumber | nullFocal point X (0–100)
focalYnumber | nullFocal point Y (0–100)

Set a focal point on any image in the asset detail view. Image fields in the admin display thumbnails cropped to the focal point via object-position.

Uploaded images are automatically optimized when rendered on public pages. The CMS includes an on-demand image transformation endpoint powered by Sharp.

Images in rich text and block content are automatically served as optimized WebP with responsive srcset. No configuration needed — it works out of the box for all local uploads.

The transformation endpoint is at /api/cms/img/[...path]:

/api/cms/img/uploads/photo.jpg?w=800 → 800px wide WebP
/api/cms/img/uploads/photo.jpg?w=1024&f=avif → 1024px wide AVIF
/api/cms/img/uploads/photo.jpg?q=90 → quality 90
ParamTypeDefaultDescription
wnumberWidth (snapped to nearest allowed size)
fstringwebpFormat: webp, avif, jpeg, png
qnumber80Quality (1–100)

Transformed images are cached to .cms-cache/img/ and served with immutable cache headers.

Use cmsImage and cmsSrcset in your own templates:

import { cmsImage, cmsSrcset } from "./cms/core/image";
// Single optimized URL
cmsImage("/uploads/photo.jpg", 800);
// → /api/cms/img/uploads/photo.jpg?w=800
// Responsive srcset
cmsSrcset("/uploads/photo.jpg", [480, 768, 1024]);
// → /api/cms/img/uploads/photo.jpg?w=480 480w, ...

Requested widths are snapped to the nearest allowed size to maximize cache efficiency: 320, 480, 640, 768, 960, 1024, 1280, 1536, 1920.