Media Library
EmDash includes a media library for managing images, documents, and other files. This guide covers uploading, finding, and using media in your content.
Accessing the Media Library
Section titled “Accessing the Media Library”Open the media library from the admin sidebar by clicking Media. The Main library shows folders and files that are not assigned to a folder. Open a folder to see its files.
Used in
Section titled “Used in”Open a file in EmDash’s media library to see the content entries that reference it. While EmDash scans existing content, the list includes the references found so far and may be incomplete.
Turn on media usage tracking
Section titled “Turn on media usage tracking”If media usage tracking is off, an administrator can turn it on:
- Finish any content edits. If another application writes directly to the content database, pause it and wait for any writes in progress to finish.
- Open Settings → Media usage tracking, select Enable tracking, then confirm.
- When the page shows Indexing existing content, editing and other database writes can resume.
- Keep the page open until it shows Ready. If you leave, return to continue from saved progress.
Once media usage tracking is on, it cannot be turned off.
Uploading Files
Section titled “Uploading Files”From the Media Library
Section titled “From the Media Library”-
Open Media in the admin sidebar.
-
Select Upload Files, then Browse files to choose one or more files. You can also drag files anywhere onto the media library.
-
Uploads start automatically. The dialog shows each file’s status and lets you cancel or retry individual files.
From the Content Editor
Section titled “From the Content Editor”-
In the rich text editor, click the image button
-
Click Upload in the media picker
-
Select a file from your computer
-
Add alt text and click Insert
Supported File Types
Section titled “Supported File Types”EmDash accepts these file types by default:
| Category | Extensions |
|---|---|
| Images | .jpg, .jpeg, .png, .gif, .webp, .avif |
| Documents | .pdf |
| Video | .mp4, .webm, .mov |
| Audio | .mp3, .wav, .ogg |
Image and file fields can allow other MIME types, including image/svg+xml for SVG files.
Storage Backends
Section titled “Storage Backends”EmDash supports multiple storage backends. Configure storage in your Astro config:
import { defineConfig } from "astro/config";import emdash, { local } from "emdash/astro";
export default defineConfig({ integrations: [ emdash({ storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file", }), }), ],});Files are stored in the ./uploads directory. Suitable for development and single-server deployments.
import { defineConfig } from "astro/config";import emdash from "emdash/astro";import { r2 } from "@emdash-cms/cloudflare";
export default defineConfig({ integrations: [ emdash({ storage: r2({ binding: "MEDIA_BUCKET", publicUrl: "https://media.example.com", }), }), ],});Requires an R2 bucket configured in wrangler.jsonc:
{ "r2_buckets": [ { "binding": "MEDIA_BUCKET", "bucket_name": "my-media-bucket", }, ],}import { defineConfig } from "astro/config";import emdash, { s3 } from "emdash/astro";
export default defineConfig({ integrations: [ emdash({ storage: s3({ endpoint: "https://s3.amazonaws.com", bucket: "my-media-bucket", accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, region: "us-east-1", publicUrl: "https://media.example.com", }), }), ],});Works with Cloudflare R2 (via S3 API), MinIO, and other S3-compatible services.
How Uploads Work
Section titled “How Uploads Work”The admin uses the upload target flow:
-
The client requests an upload target, and EmDash creates a pending media item.
-
The client uploads the file to the returned target.
-
The client confirms the upload.
-
EmDash validates the stored file and marks the media item as ready.
S3-compatible storage returns a signed URL so the file can bypass the application runtime. Local storage and native R2 return a same-origin streaming endpoint instead.
Finding Media
Section titled “Finding Media”Search
Section titled “Search”Use the search box to find files by name. Search matches partial filenames.
Filter by type
Section titled “Filter by type”Use the type filter to show images, documents, video, or audio files.
Organizing media in folders
Section titled “Organizing media in folders”Editors can select Add new folder from the Main library. Open a folder by selecting its name. Without a search term, folder pages show only the media assigned to that folder. Filename searches cover the whole library, including other folders and the Main library.
To move a local file into a visible folder, drag its grid card or list row onto the folder. You can also open Media Details, choose a Location, and select Save. Use Location to return a file to the Main library or to move it without dragging.
Authors can move local files they uploaded. Editors can move any local file. Files from external providers cannot be assigned to folders.
Uploads enter the Main library. Move them into a folder after upload using either method above.
Deleting a folder returns its media to the Main library. The media files, URLs, and content references remain unchanged.
Using Media in Content
Section titled “Using Media in Content”In the Rich Text Editor
Section titled “In the Rich Text Editor”-
Place your cursor where you want the image
-
Click the image button in the toolbar
-
Select an image from the media library or upload a new one
-
Enter alt text
-
Click Insert
As a Featured Image
Section titled “As a Featured Image”-
Open a content entry in the editor
-
Find the Featured Image field in the sidebar
-
Click Select Image
-
Choose from the media library or upload
-
Click Save
In Custom Fields
Section titled “In Custom Fields”For fields configured as image or file types, click the field to open the media picker.
Setting a focal point
Section titled “Setting a focal point”A focal point keeps the important part of a local image visible when a card, gallery, or other layout crops it to fill a fixed shape.
- Open Media, then select an image from the local library.
- Select Focal point.
- Click or drag the marker onto the important part of the image. You can also use the Arrow keys.
- Check the square, landscape, and portrait previews, then select Save.
Select Reset to remove a custom focal point. The saved point is copied when you select the image for a content field or gallery. Content that already uses the image keeps its stored point until you select the image again.
Displaying Media in Templates
Section titled “Displaying Media in Templates”Access media URLs from your content data:
---import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug);---
{post?.data.featured_image && ( <img src={post.data.featured_image} alt={post.data.featured_image_alt ?? ""} />)}Responsive Images
Section titled “Responsive Images”For EmDash media fields, use the Image component from emdash/ui:
---import { Image } from "emdash/ui";import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug);---
{post?.data.featured_image && ( <Image image={post.data.featured_image} width={800} height={450} priority />)}priority is for the primary above-the-fold image. It sets loading="eager" and fetchpriority="high": loading controls whether loading is deferred, and fetchpriority gives the browser a priority hint for the request.
When the field value carries a dark counterpart, Image renders both and shows the one matching the visitor’s color scheme. Dark Mode covers enabling the slot on a field and the <html> class convention the component relies on.
EmDash installs an image endpoint that produces the resized variants on request. On Cloudflare Workers that endpoint uses the IMAGES binding. Image Transformation covers where the binding comes from and what happens when it is absent.
Deleting Media
Section titled “Deleting Media”-
Select the file(s) you want to delete
-
Click Delete
-
Confirm the deletion
Media API
Section titled “Media API”Use the REST API to upload, list, update, delete, and organize local media. The media endpoint reference documents the direct multipart upload and upload target flows, request parameters, response shapes, permissions, and folder operations.
Media Providers
Section titled “Media Providers”In addition to local storage, EmDash supports external media providers for specialized image and video hosting. Media providers appear as tabs in the media picker, letting editors choose from multiple sources.
Available Providers
Section titled “Available Providers”Cloudflare Images provides image hosting with automatic optimization, resizing, and format conversion.
import { defineConfig } from "astro/config";import emdash from "emdash/astro";import { cloudflareImages } from "@emdash-cms/cloudflare";
export default defineConfig({ integrations: [ emdash({ // ... database, storage config mediaProviders: [ cloudflareImages({ accountId: import.meta.env.CF_ACCOUNT_ID, apiToken: import.meta.env.CF_IMAGES_TOKEN, // Optional: custom delivery domain deliveryDomain: "images.example.com", }), ], }), ],});Features:
- Browse and upload images directly from the admin
- Automatic image optimization and format conversion
- URL-based transformations (resize, crop, format)
- Flexible variants for responsive images
Cloudflare Stream provides video hosting with HLS/DASH adaptive streaming.
import { defineConfig } from "astro/config";import emdash from "emdash/astro";import { cloudflareStream } from "@emdash-cms/cloudflare";
export default defineConfig({ integrations: [ emdash({ // ... database, storage config mediaProviders: [ cloudflareStream({ accountId: import.meta.env.CF_ACCOUNT_ID, apiToken: import.meta.env.CF_STREAM_TOKEN, // Optional: player settings controls: true, autoplay: false, loop: false, }), ], }), ],});Features:
- Browse, search, and upload videos from the admin
- HLS and DASH adaptive streaming
- Automatic thumbnail generation
- Direct upload for large files
Using Multiple Providers
Section titled “Using Multiple Providers”You can configure multiple providers. Each appears as a tab in the media picker:
import { defineConfig } from "astro/config";import emdash from "emdash/astro";import { cloudflareImages, cloudflareStream } from "@emdash-cms/cloudflare";
export default defineConfig({ integrations: [ emdash({ database: d1({ binding: "DB" }), storage: r2({ binding: "MEDIA" }), mediaProviders: [ cloudflareImages({ accountId: import.meta.env.CF_ACCOUNT_ID, apiToken: import.meta.env.CF_IMAGES_TOKEN, }), cloudflareStream({ accountId: import.meta.env.CF_ACCOUNT_ID, apiToken: import.meta.env.CF_STREAM_TOKEN, }), ], }), ],});The local media library (“Library” tab) is always available alongside any configured providers.
Rendering Provider Media
Section titled “Rendering Provider Media”Use the Image component to render media:
---import { Image } from "emdash/ui";import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug);---
{post?.data.featured_image && ( <Image image={post.data.featured_image} width={800} height={450} />)}The component automatically:
- Detects the provider from the stored value
- Renders an optimized
<img>element - Applies provider-specific optimizations (e.g., Cloudflare Images transformations)
File Values and Current Metadata
Section titled “File Values and Current Metadata”A file field stores a reference and a metadata snapshot. Cached fields such as url, filename,
mimeType, and size are optional because persisted values may omit them:
interface FileValue { id: string; url?: string; // Legacy cached URL src?: string; // Direct URL from an external provider filename?: string; // Cached original filename mimeType?: string; // Cached MIME type size?: number; // Cached size, when available provider?: string; // Defaults to "local" meta?: Record<string, unknown>;}getEmDashEntry() and getEmDashCollection() return this stored value without an extra media
query. For current metadata, use the configured provider’s get() method explicitly. Use
getEmbed() for the provider-specific render URL:
---const file = post.data.attachment;const provider = file ? Astro.locals.emdash?.getMediaProvider(file.provider ?? "local") : undefined;const current = file ? await provider?.get?.(file.id) : null;const embed = file && provider ? await provider.getEmbed(file) : null;---Authenticated HTTP clients can make the same explicit lookup through
GET /_emdash/api/media/:id for local media or
GET /_emdash/api/media/providers/:providerId/:itemId for another provider.
For a local file URL, use the stored meta.storageKey with the public URL helper. This respects a
configured R2 or S3 public domain without querying the media table:
---const storageKey = typeof file?.meta?.storageKey === "string" ? file.meta.storageKey : undefined;const url = storageKey ? Astro.locals.emdash?.getPublicMediaUrl?.(storageKey) : file?.src ?? file?.url;---Provider lookups may perform network or database work. Avoid one lookup per file on logged-out collection pages; use the stored snapshot and rendering components unless the request needs fresh metadata.
MediaValue Type
Section titled “MediaValue Type”Media fields store a MediaValue object containing provider information:
interface MediaValue { provider?: string; // Provider ID, defaults to "local" id: string; // Provider-specific ID src?: string; // Direct URL (for local media or plain-string values) previewUrl?: string; // Preview URL for admin display (external providers) filename?: string; // Original filename mimeType?: string; // MIME type width?: number; // Image/video width height?: number; // Image/video height focalX?: number; // Horizontal focal position from 0 to 1 focalY?: number; // Vertical focal position from 0 to 1 alt?: string; // Alt text meta?: Record<string, unknown>; // Provider-specific metadata}This allows EmDash to render media correctly regardless of where it’s hosted.
Next Steps
Section titled “Next Steps”- Working with Content - Use media in your content
- Create a Blog - Add images to blog posts
- Querying Content - Display media in templates