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).

Asset metadata (filename, mime type, size, alt, focal point) is stored in the cms_assets table. File storage depends on the deployment target:

  • Local/Node.js: Files stored in public/uploads/, served by Astro’s static file handling
  • Cloudflare: Files stored in R2 via the CMS_ASSETS bucket binding, served by a dynamic route

The CMS uses a storage abstraction (src/cms/core/storage.ts) with putFile, getFile, deleteFile functions. The setup script configures the correct implementation for your deployment target.

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.