Skip to content

Collections & Fields

Collections are the foundation of EmDash’s content model. Each collection represents a content type (posts, pages, products) and contains field definitions that determine the shape of your data.

Create collections through the admin panel under Content Types. Each collection has:

EmDash content types showing Pages, Posts, and custom collections with their features
PropertyDescription
slugURL-safe identifier (e.g., posts, products)
labelDisplay name (e.g., “Blog Posts”)
labelSingularSingular form (e.g., “Post”)
descriptionOptional description for editors
iconLucide icon name for the admin sidebar
supportsFeatures like drafts, revisions, preview, scheduling

When creating a collection, enable the features you need:

FeatureDescription
draftsEnable draft/published workflow
revisionsTrack content history with version snapshots
previewGenerate signed preview URLs for draft content
schedulingSchedule content to publish at a future date
// Example collection with all features enabled
{
slug: "posts",
label: "Blog Posts",
labelSingular: "Post",
supports: ["drafts", "revisions", "preview", "scheduling"]
}

EmDash supports 15 field types that map to SQLite column types:

Short text input. Maps to TEXT column.

{ slug: "title", type: "string", label: "Title" }

Rich text editor (TipTap/ProseMirror). Stored as JSON.

{ slug: "content", type: "portableText", label: "Content" }

Portable Text is a block-based format that preserves structure without embedding HTML.

Decimal numbers. Maps to REAL column.

{ slug: "price", type: "number", label: "Price" }

True/false toggle. Maps to INTEGER (0/1).

{ slug: "featured", type: "boolean", label: "Featured Post" }

Single option from a list. Maps to TEXT column.

{
slug: "status",
type: "select",
label: "Product Status",
validation: {
options: ["active", "discontinued", "coming_soon"]
}
}

Image picker from media library. Stores media ID as TEXT.

{ slug: "featuredImage", type: "image", label: "Featured Image" }

Every field supports these properties:

PropertyTypeDescription
slugstringColumn name in the database
labelstringDisplay label in admin UI
typeFieldTypeOne of the 15 field types
requiredbooleanWhether the field must have a value
uniquebooleanWhether values must be unique across entries
defaultValueunknownDefault value for new entries
validationobjectType-specific validation rules
widgetstringCustom widget identifier
optionsobjectWidget-specific configuration
sortOrdernumberDisplay order in the editor

The validation object varies by field type:

interface FieldValidation {
required?: boolean; // All types
min?: number; // number, integer
max?: number; // number, integer
minLength?: number; // string, text
maxLength?: number; // string, text
pattern?: string; // string (regex)
options?: string[]; // select, multiSelect
}

Example with validation:

{
slug: "email",
type: "string",
label: "Email Address",
required: true,
unique: true,
validation: {
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
}
}

The options object configures field-specific UI behavior:

interface FieldWidgetOptions {
rows?: number; // text (textarea rows)
showPreview?: boolean; // image, file
collection?: string; // reference (target collection)
allowMultiple?: boolean; // reference (multiple refs)
[key: string]: unknown; // Custom widget options
}

Example reference field:

{
slug: "relatedProducts",
type: "reference",
label: "Related Products",
options: {
collection: "products",
allowMultiple: true
}
}

Use the provided query functions to fetch content. These follow Astro’s live collections pattern, returning structured results:

import { getEmDashCollection, getEmDashEntry } from "emdash";
// Get all entries - returns { entries, error }
const { entries: posts } = await getEmDashCollection("posts");
// Filter by status
const { entries: drafts } = await getEmDashCollection("posts", {
status: "draft",
});
// Limit results
const { entries: recent } = await getEmDashCollection("posts", {
limit: 5,
});
// Filter by taxonomy
const { entries: newsPosts } = await getEmDashCollection("posts", {
where: { category: "news" },
});
// Get a single entry by slug - returns { entry, error, isPreview }
const { entry: post } = await getEmDashEntry("posts", "my-post-slug");
// Handle errors
const { entries, error } = await getEmDashCollection("posts");
if (error) {
console.error("Failed to load posts:", error);
}

Run npx emdash types to generate TypeScript types from your schema:

// .emdash/types.ts (generated)
export interface Post {
title: string;
content: PortableTextBlock[];
excerpt?: string;
featuredImage?: string;
author: string; // reference ID
}
export interface Product {
title: string;
price: number;
description: PortableTextBlock[];
}

Field types map to SQLite column types:

Field TypeSQLite TypeNotes
stringTEXT
textTEXT
slugTEXT
numberREAL64-bit floating point
integerINTEGER64-bit signed integer
booleanINTEGER0 or 1
datetimeTEXTISO 8601 format
selectTEXT
multiSelectJSONArray of strings
portableTextJSONBlock array
imageTEXTMedia ID
fileTEXTMedia ID
referenceTEXTEntry ID
jsonJSONArbitrary JSON