Skip to content

Media Library

EmDash includes a media library for managing images, documents, and other files. This guide covers uploading, organizing, and using media in your content.

Open the media library from the admin sidebar by clicking Media. The library displays all uploaded files with previews, filenames, and upload dates.

EmDash media library showing image grid with upload button
  1. Click Media in the admin sidebar

  2. Click Upload or drag files onto the upload area

  3. Select one or more files from your computer

  4. Wait for uploads to complete

  1. In the rich text editor, click the image button

  2. Click Upload in the media picker

  3. Select a file from your computer

  4. Add alt text and click Insert

EmDash supports common web file types:

CategoryExtensions
Images.jpg, .jpeg, .png, .gif, .webp, .avif, .svg
Documents.pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx
Video.mp4, .webm, .mov
Audio.mp3, .wav, .ogg

EmDash supports multiple storage backends. Configure storage in your Astro config:

astro.config.mjs
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.

EmDash uses signed URLs for secure uploads:

  1. Client requests an upload URL from the API

  2. Server generates a signed URL with expiration

  3. Client uploads directly to storage using the signed URL

  4. Server records the file metadata in the database

This approach keeps large files off your application server and enables direct uploads to cloud storage.

Create folders to organize your media:

  1. Click New Folder in the media library

  2. Enter a folder name

  3. Click Create

  4. Drag files into folders to organize them

Use the search box to find files by name. Search matches partial filenames.

Filter media by:

  • Type - Images, Documents, Video, Audio
  • Date - Upload date range
  • Folder - Specific folder
  1. Place your cursor where you want the image

  2. Click the image button in the toolbar

  3. Select an image from the media library or upload a new one

  4. Enter alt text

  5. Click Insert

  1. Open a content entry in the editor

  2. Find the Featured Image field in the sidebar

  3. Click Select Image

  4. Choose from the media library or upload

  5. Click Save

For fields configured as image or file types, click the field to open the media picker.

Access media URLs from your content data:

src/pages/posts/[slug].astro
---
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 ?? ""}
/>
)}

For responsive images, use Astro’s Image component with external URLs:

For responsive images, use Astro’s Image component with external URLs:

---
import { Image } from "astro:assets";
import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug);
---
{post?.data.featured_image && (
<Image
src={post.data.featured_image}
alt={post.data.featured_image_alt ?? ""}
width={800}
height={450}
/>
)}
  1. Select the file(s) you want to delete

  2. Click Delete

  3. Confirm the deletion

Access media programmatically using the admin API.

Request a signed upload URL:

Terminal window
POST /_emdash/api/media/upload
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
"filename": "hero-image.jpg",
"contentType": "image/jpeg",
"size": 245000
}

Response:

{
"url": "https://storage.example.com/signed-upload-url...",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
},
"expiresAt": "2024-01-15T12:00:00Z",
"key": "media/abc123/hero-image.jpg"
}

Upload the file using the signed URL:

Terminal window
PUT https://storage.example.com/signed-upload-url...
Content-Type: image/jpeg
<file contents>
Terminal window
GET /_emdash/api/media?prefix=images/&limit=20
Authorization: Bearer YOUR_API_TOKEN
Terminal window
DELETE /_emdash/api/media/images/hero.jpg
Authorization: Bearer YOUR_API_TOKEN

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.

Cloudflare Images provides image hosting with automatic optimization, resizing, and format conversion.

astro.config.mjs
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

You can configure multiple providers. Each appears as a tab in the media picker:

astro.config.mjs
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.

Use the EmDashMedia component to render media from any provider:

src/pages/posts/[slug].astro
---
import { EmDashMedia } from "emdash/ui";
import { getEmDashEntry } from "emdash";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug);
---
{post?.data.featured_image && (
<EmDashMedia
value={post.data.featured_image}
width={800}
height={450}
/>
)}

The component automatically:

  • Detects the provider from the stored value
  • Renders the appropriate element (<img>, <video>, etc.)
  • Applies provider-specific optimizations (e.g., Cloudflare Images transformations)

Media fields store a MediaValue object containing provider information:

interface MediaValue {
provider: string; // Provider ID (e.g., "local", "cloudflare-images")
id: string; // Provider-specific ID
url?: string; // Direct URL (for local/external)
filename?: string; // Original filename
mimeType?: string; // MIME type
width?: number; // Image/video width
height?: number; // Image/video height
alt?: string; // Alt text
meta?: Record<string, unknown>; // Provider-specific metadata
}

This allows EmDash to render media correctly regardless of where it’s hosted.