Collections & Fields

On this page

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.

Creating Collections

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

Collection Features

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"]
}

Field Types

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

Text Fields

string

Short text input. Maps to TEXT column.

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

text

Multi-line textarea. Maps to TEXT column.

{ slug: "excerpt", type: "text", label: "Excerpt" }

slug

URL-safe slug field. Maps to TEXT column.

{ slug: "handle", type: "slug", label: "URL Handle" }

Rich Content

portableText

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.

json

Arbitrary JSON data. Stored as JSON.

{ slug: "metadata", type: "json", label: "Custom Metadata" }

Numbers

number

Decimal numbers. Maps to REAL column.

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

integer

Whole numbers. Maps to INTEGER column.

{ slug: "quantity", type: "integer", label: "Stock Quantity" }

Booleans & Dates

boolean

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

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

datetime

Date and time picker. Stored as ISO 8601 string.

{ slug: "eventDate", type: "datetime", label: "Event Date" }

Selection

select

Single option from a list. Maps to TEXT column.

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

multiSelect

Multiple options from a list. Stored as JSON array.

{
  slug: "features",
  type: "multiSelect",
  label: "Product Features",
  validation: {
    options: ["wireless", "waterproof", "eco-friendly"]
  }
}

Media & References

image

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

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

file

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

{ slug: "attachment", type: "file", label: "PDF Attachment" }

reference

Reference to another collection’s entry. Stores entry ID as TEXT.

{
  slug: "author",
  type: "reference",
  label: "Author",
  options: {
    collection: "authors"
  }
}

Field Properties

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

Validation Rules

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,}$"
  }
}

Widget Options

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
  }
}

Querying Collections

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);
}

Type Generation

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[];
}

Database Mapping

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

Next Steps