wisewigdocs

Assets

R2AssetStore is the Cloudflare reference adapter for images, videos, and downloadable files. It allocates opaque, tenant-and-site-scoped R2 keys and applies a separate MIME allowlist, byte ceiling, signature check, and delivery policy to each kind.

KindAccepted typesMaximumDelivery
ImageAVIF, JPEG, PNG, WebP10 MiBInline immutable original; bounded Image Resizing URLs are optional
VideoMP4, WebM100 MiBInline immutable object suitable for progressive browser playback
DownloadPDF, UTF-8 plain text, CSV25 MiBImmutable object with Content-Disposition: attachment and a sanitized filename

The metering transform checks the actual streamed byte count and the declared format’s identifying bytes before R2 accepts an object; disguised files fail closed. File names containing control characters are rejected, and the response fallback filename is sanitized before it reaches R2 metadata. HTML, SVG, JavaScript, arbitrary binaries, executables, ZIP archives, and Office ZIP containers are not accepted.

The upload URL returned by createUpload() is an authenticated Worker-route identifier, not a bearer-signed R2 URL. Morning Bell’s /api/media route authenticates the caller, authorizes media.manage for the hostname-derived site, allocates the key, creates an uploading tenant-workspace record, and streams the request to R2. A validation failure moves the record to quarantined; only ready records appear in the picker.

Managed hosting sends the byte stream through the trusted hostname dispatcher into the selected tenant Worker. The Worker validates MIME, signature, declared and actual size, filename, role, quota, and idempotency while writing to that tenant’s unique R2 bucket and SQLite media table. Validation flows through a FixedLengthStream, preserving Cloudflare R2’s known-length requirement without buffering images, videos, or downloads in memory. Public asset URLs contain only an opaque asset ID; the tenant Worker resolves that ID under its platform-selected site before streaming immutable R2 bytes. Another tenant cannot retrieve the object even if it learns the ID or uses a colliding site ID.

R2 objects receive immutable public cache headers plus tenant/site/kind metadata. An object key from another tenant is rejected before it can be written or exposed. imageDeliveryUrl() creates bounded same-site Cloudflare Image Resizing URLs while content continues to reference the immutable original. The editor filters the same ready-asset library for hero images, video sections, and download sections. Deletion checks draft and published hero images, video sources/posters, downloadable files, and settings logos; it deletes R2 first, then tombstones metadata through an idempotent tenant command. In-use assets are rejected.

This implementation provides allowlisting, signature validation, bounded streaming, and quarantine, not an antivirus claim. A hosted installation that expands the file allowlist must add an external malware scanner before promoting higher-risk uploads to ready.

R2 video support is intentionally simple progressive delivery. Adaptive bitrate encoding, generated posters, captions, playback analytics, and long-form video processing would require a video adapter such as Cloudflare Stream; they are not implied by the MP4/WebM component.

External video embeds

The video component also accepts a YouTube watch/share URL or copied iframe snippet. parseVideoEmbed() extracts an allowlisted YouTube video ID and accessible title; the raw HTML, pasted host, dimensions, scripts, and query parameters are discarded. Documents persist only the typed { provider: "youtube", videoId, title } record. The Astro renderer constructs its own lazy, sandboxed youtube-nocookie.com iframe without autoplay or JavaScript API parameters.

This is not a general arbitrary-embed escape hatch. HTTP URLs, lookalike domains, malformed IDs, script tags, and unsupported providers fail schema validation. Additional providers require a framework parser, schema member, renderer, and security tests rather than an editor-controlled iframe URL.