A collection is a content type (posts, pages, products). Its field definitions set the shape of each entry’s data.
Creating collections
Create collections through the admin panel under Content Types. Each collection has the following properties:
| Property | Description |
|---|---|
slug | URL-safe identifier (e.g., posts, products) |
label | Display name (e.g., “Blog Posts”) |
labelSingular | Singular form (e.g., “Post”) |
description | Optional description for editors |
icon | Lucide icon name for the admin sidebar |
supports | Features like drafts, revisions, preview, scheduling, search, seo |
Collection features
When creating a collection, enable the features you need:
| Feature | Description |
|---|---|
drafts | Enable draft/published workflow |
revisions | Track content history with version snapshots |
preview | Generate signed preview URLs for draft content |
scheduling | Schedule 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"]
}
Field types
EmDash supports 16 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 and 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 and 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:
| Property | Type | Description |
|---|---|---|
slug | string | Column name in the database |
label | string | Display label in admin UI |
type | FieldType | One of the 16 field types |
required | boolean | Whether the field must have a value |
unique | boolean | Whether values must be unique across entries |
defaultValue | unknown | Default value for new entries |
validation | object | Type-specific validation rules |
widget | string | Custom widget identifier |
options | object | Widget-specific configuration |
sortOrder | number | Display order in the editor |
Validation rules
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,}$"
}
}
Widget options
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
}
}
Querying collections
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);
}
Type generation
Run npx emdash types to generate TypeScript types from your schema. The generated file contains one interface per collection:
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 as follows:
| Field Type | SQLite Type | Notes |
|---|---|---|
string | TEXT | |
text | TEXT | |
slug | TEXT | |
url | TEXT | |
number | REAL | 64-bit floating point |
integer | INTEGER | 64-bit signed integer |
boolean | INTEGER | 0 or 1 |
datetime | TEXT | ISO 8601 format |
select | TEXT | |
multiSelect | JSON | Array of strings |
portableText | JSON | Block array |
image | TEXT | Media ID |
file | TEXT | Media ID |
reference | TEXT | Entry ID |
json | JSON | Arbitrary JSON |
repeater | JSON | Array of sub-fields |
Next steps
Content Model
Understand the content model.
Taxonomies
Organize content with categories and tags.
Media Library
Manage images and files.