Skip to content

Collections & Fields

A collection is a content type (posts, pages, products). Its field definitions set the shape of each entry’s data.

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

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, search, seo

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

The following collection enables all four features:

{
slug: "posts",
label: "Blog Posts",
labelSingular: "Post",
supports: ["drafts", "revisions", "preview", "scheduling"]
}

EmDash supports 16 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 16 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. Its full shape is:

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
}

The following field requires a unique, pattern-matched email address:

{
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. Its full shape is:

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
}

The following reference field links to multiple products:

{
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. The following example shows the common query options:

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. The generated file contains one interface per collection:

.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 as follows:

Field TypeSQLite TypeNotes
stringTEXT
textTEXT
slugTEXT
urlTEXT
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
repeaterJSONArray of sub-fields