Media Library

The Media Library uses Supabase Storage to manage uploaded files. It supports images, PDFs, and videos with security validation including file type checking and magic bytes verification.

Drag & Drop Upload

Upload files via drag and drop or file picker with progress tracking.

Security Validation

File type validation, magic bytes verification, and sanitized filenames.

Folder Organization

Organize files in folders: uploads, images, documents, videos.

URL Copy

Copy public URLs with one click to paste into content.

Configuration

The bucket name is 'media' — a hardcoded constant in both lib/cms/storage.ts (STORAGE_BUCKET) and app/api/admin/cms/media/route.ts (STORAGE_BUCKET). Change both call sites if you need a different name. The bucket is auto-seeded by supabase/cms-schema.sql with public reads and admin-only writes (MIME allowlist + 10 MB cap enforced at the storage layer). The storage module in lib/cms/storage.ts handles file uploads, URL generation, and cleanup.

TypeScript Types

Media types are defined in lib/cms/types.ts and include interfaces for uploaded files, storage metadata, and media library items with their public URLs and dimensions.

Storage Functions

The storage module provides functions for uploading files (with automatic path generation), getting public URLs, listing files in a directory, and deleting files. All operations use the Supabase admin client to bypass RLS for admin-level media management.

Media API

Endpoint Method Description
/api/admin/cms/media GET List files (with pagination)
/api/admin/cms/media POST Upload file (multipart/form-data)
/api/admin/cms/media PATCH Rename file
/api/admin/cms/media?path=xxx DELETE Delete file

Security Validation

The media upload validates files in multiple ways: a MIME-type allowlist (images + documents only — SVG is intentionally excluded because the format can carry scripts and the bucket is public; use PNG/WebP, or serve trusted SVGs from /public), a hard 10 MB per-file size cap, and filename sanitization before the object is written to the Supabase Storage bucket. CMS block content is additionally validated as a JSON object (no script injection) with a 500 KB ceiling, and block keys are restricted to alphanumeric + underscores.

Supabase Storage Setup

The media bucket policies (see supabase/cms-schema.sql lines ~190-236) are not membership-scoped. Instead they implement a public-read / admin-write model:

  • SELECT: public — anyone (including unauthenticated visitors) can fetch any object from the media bucket. This is what makes uploaded URLs usable in marketing pages and blog posts.
  • INSERT / UPDATE / DELETE: restricted to authenticated users whose profiles.is_admin is true. There is no per-account scoping on the bucket itself — media is treated as a platform-wide asset library.

If you need per-account or per-workspace media isolation, you must either add additional bucket policies that join through memberships, or use a separate bucket per account.