Theme
Assets + Image Transforms
Upload images, videos, PDFs, anything else; serve them through a signed CDN URL with on-the-fly transforms (resize, crop, format conversion).
Upload
http
POST /v1/spaces/{slug}/assets
Content-Type: multipart/form-dataForm field file. Auth: auth:sanctum + space.scope (editor+).
Response:
json
{
"data": {
"uuid": "abc-...",
"filename": "hero.jpg",
"mime": "image/jpeg",
"bytes": 4096712,
"width": 4800,
"height": 3200,
"url": "https://peacock-cms.webhoch.com/v1/cdn/demo/assets/abc-...jpg"
}
}Object storage backend: S3-compatible (Hetzner / R2 / MinIO). Configurable via FILESYSTEM_DISK=s3 in the API env.
List / search
http
GET /v1/spaces/{slug}/assets?type=image&search=hero&page=1Returns paginated assets. Filterable by type (image|video|other) and free-text search against filename + tags.
Image transforms (public CDN)
http
GET /v1/cdn/{space}/assets/{uuid}.{format}?w=600&h=300&fit=crop&q=85| Param | Values | Default |
|---|---|---|
format (path) | jpg, png, webp, avif | original |
w | 1–4096 px | original width |
h | 1–4096 px | original height |
fit | contain, crop, pad, stretch | contain |
q | 1–100 | 85 |
gravity (when fit=crop) | center, north, north-east, east, … auto | center |
blur | 0–100 (optional) | 0 |
Cached at edge for 1 year (max-age=31536000, immutable) — the URL fully encodes the transform so each variant is its own cache entry.
Powered by Glide on top of the storage backend.
Signed URLs (anti-resize-attack)
By default, Peacock signs every transform URL with HMAC to prevent an attacker from filling the cache with ?w=4097&h=4096&q=100 combinations.
text
https://peacock-cms.webhoch.com/v1/cdn/demo/assets/abc.jpg
?w=600&h=300&fit=crop&s=<8-char-hmac>The Astro/Next/Nuxt SDKs append s= automatically when you use their peacock.imageUrl() helper. Hand-rolled clients can compute it:
ts
import { createHmac } from 'node:crypto';
const sig = createHmac('sha256', SIGNING_KEY)
.update('/v1/cdn/demo/assets/abc.jpg?w=600&h=300&fit=crop')
.digest('hex').slice(0, 8);PEACOCK_ASSET_SIGNING_KEY env var holds the secret on the API host; obtain it from the operator. Set PEACOCK_ASSET_REQUIRE_SIGNING=false for dev / open-CMS deployments.
Asset CRUD (authenticated)
http
GET /v1/spaces/{slug}/assets/{uuid} # metadata + signed URLs
PATCH /v1/spaces/{slug}/assets/{uuid} # rename, add tags
DELETE /v1/spaces/{slug}/assets/{uuid} # soft-delete (recoverable)Storage layout
text
s3://<bucket>/<space-slug>/<uuid-first-2-chars>/<uuid>/original.<ext>
s3://<bucket>/<space-slug>/<uuid-first-2-chars>/<uuid>/transforms/<hash>.<fmt>Two-char fan-out prevents single-prefix hot-spots when a Space has many thousands of assets.
See also
/api/cdn— public read of Stories that reference assets/api/cdn-caching— same caching contract applies to assets- Security audit 2026-05-21 — open-resize attack defense