# Field Types Reference

> Complete reference for all EmDash field types.

Source: https://emdashcmseverything.com/official-docs/reference/field-types/

EmDash supports 17 field types for defining content schemas. Each type maps to a SQLite column type and provides appropriate admin UI.

## Overview

The following table lists every field type and its SQLite column:

| Type           | SQLite Column | Description                            |
| -------------- | ------------- | -------------------------------------- |
| `string`       | TEXT          | Short text input                       |
| `text`         | TEXT          | Multi-line text                        |
| `url`          | TEXT          | URL value                              |
| `number`       | REAL          | Decimal number                         |
| `integer`      | INTEGER       | Whole number                           |
| `boolean`      | INTEGER       | True/false                             |
| `datetime`     | TEXT          | Date and time                          |
| `select`       | TEXT          | Single choice from options             |
| `multiSelect`  | JSON          | Multiple choices                       |
| `portableText` | JSON          | Rich text content                      |
| `image`        | TEXT          | Image reference                        |
| `file`         | TEXT          | File reference                         |
| `reference`    | none          | Links to entries in another collection |
| `json`         | JSON          | Arbitrary JSON data                    |
| `slug`         | TEXT          | URL-safe identifier                    |
| `repeater`     | JSON          | Repeating group of fields              |
| `blocks`       | JSON          | Typed page composition                 |

## Text types

### `string`

Short, single-line text. Use for titles, names, and short values.

```ts
{
  slug: "title",
  label: "Title",
  type: "string",
  required: true,
  validation: {
    minLength: 1,
    maxLength: 200,
  },
}
```

**Validation options:**

- `minLength` — Minimum character count
- `maxLength` — Maximum character count
- `pattern` — Regular expression the value must match

The editor caps input at `maxLength` and shows a live character count against the length rules.

**Widget options:**

- None specific

### `text`

Multi-line plain text. Use for descriptions, excerpts, and longer plain text.

```ts
{
  slug: "excerpt",
  label: "Excerpt",
  type: "text",
  options: {
    rows: 3,
  },
}
```

**Validation options:**

- `minLength` — Minimum character count
- `maxLength` — Maximum character count
- `pattern` — Regular expression the value must match

The editor caps input at `maxLength` and shows a live character count against the length rules.

**Widget options:**

- `rows` — Number of rows in textarea (default: 3)

### `url`

A link target. A value must be one of:

- an absolute `http:` or `https:` URL, such as `https://example.com/page`
- a `mailto:` or `tel:` link
- a site-relative path, such as `/about`, or a fragment, such as `#contact`

The content API and the admin editor reject anything else, including protocol-relative URLs (`//example.com`), backslash forms that browsers resolve the same way (`/\\example.com`), control characters, and schemes that can run script, such as `javascript:` and `data:`.

```ts
{
  slug: "website",
  label: "Website",
  type: "url",
  required: true,
}
```

URL fields are stored as text. Seeds and plugin content updates preserve scheme-less values such as `www.example.com`, but reject unsafe schemes and values that a browser resolves to another site. Site imports apply the same checks to URL fields in entries, revisions, repeaters, and blocks.

Entries saved by earlier EmDash releases can hold an unsafe `url` value. Those values stay stored and queries return them unchanged. A write that sends the value again is rejected until the value is corrected. This includes an admin save of that entry and duplicating it. To render such values safely, pass `url` values through [`sanitizeHref()`](https://emdashcmseverything.com/official-docs/reference/api/#sanitizehref-and-issafehref) before putting them in an `href`.

### `slug`

Text intended to hold a slug-like value. This custom field type does not generate or sanitize its value.

```ts
{
  slug: "legacy_slug",
  label: "Legacy Slug",
  type: "slug",
  required: true,
  unique: true,
}
```

Every content entry already has a reserved system `slug`, which EmDash manages separately for public URLs. Use a custom `slug` field only when the content model needs another stored slug-like value.

## Number types

### `number`

Decimal number. Use for prices, ratings, and measurements.

```ts
{
  slug: "price",
  label: "Price",
  type: "number",
  required: true,
  validation: {
    min: 0,
    max: 999999.99,
  },
}
```

**Validation options:**

- `min` — Minimum value
- `max` — Maximum value

The editor sets `min` and `max` on the number input and shows the allowed range beneath it.

Stored as SQLite REAL (64-bit floating point).

### `integer`

Whole number. Use for quantities, counts, and order values.

```ts
{
  slug: "quantity",
  label: "Quantity",
  type: "integer",
  defaultValue: 1,
  validation: {
    min: 0,
    max: 1000,
  },
}
```

**Validation options:**

- `min` — Minimum value
- `max` — Maximum value

The editor sets `min` and `max` on the number input and shows the allowed range beneath it.

Stored as SQLite INTEGER.

### `boolean`

True or false. Use for toggles and flags.

```ts
{
  slug: "featured",
  label: "Featured",
  type: "boolean",
  defaultValue: false,
}
```

Stored as SQLite INTEGER (0 or 1).

## Date and time

### `datetime`

An instant in time. API, MCP, and CLI writes must include `Z` or an explicit UTC offset. The admin interprets its date-and-time picker in the timezone configured under **Settings → General**.

```ts
{
  slug: "publishedAt",
  label: "Published At",
  type: "datetime",
}
```

**Storage format:** `2025-01-24T12:00:00.000Z`

EmDash converts accepted input to UTC with three-digit milliseconds before storing it. For example, `2025-01-24T21:00:00+09:00` is stored as `2025-01-24T12:00:00.000Z`. Use a `string` field instead when the value is a calendar date or local wall-clock time rather than an instant.

## Selection types

### `select`

Single selection from predefined options.

```ts
{
  slug: "status",
  label: "Status",
  type: "select",
  required: true,
  defaultValue: "draft",
  validation: {
    options: ["draft", "published", "archived"],
  },
}
```

**Validation options:**

- `options` — Optional array of allowed values. Provide it to present predefined choices and reject other strings; without it, validation accepts any string.

Stored as TEXT containing the selected value.

### `multiSelect`

Multiple selections from predefined options.

```ts
{
  slug: "tags",
  label: "Tags",
  type: "multiSelect",
  validation: {
    options: ["news", "tutorial", "review", "opinion"],
  },
}
```

**Validation options:**

- `options` — Optional array of allowed values. Provide it to present predefined choices and reject other strings; without it, validation accepts any string array.

Stored as JSON array: `["news", "tutorial"]`

## Rich content

### `portableText`

Rich text content using Portable Text format. Supports headings, lists, links, images, and custom blocks.

```ts
{
  slug: "content",
  label: "Content",
  type: "portableText",
  required: true,
}
```

The value is stored as a JSON array of Portable Text blocks, for example:

```json
[
	{
		"_type": "block",
		"style": "normal",
		"children": [{ "_type": "span", "text": "Hello world" }]
	}
]
```

Plugins can add custom block types (embeds, widgets, etc.) to the editor. These appear in the slash command menu. Rendering the saved block on the public site requires an Astro component from a native plugin or companion package. See [Portable Text rendering components](https://emdashcmseverything.com/official-docs/plugins/creating-native-plugins/portable-text-components/).

> **Note**
>
> Portable Text is a specification for structured rich text. See
> [portabletext.org](https://portabletext.org) for details.

## Media types

### `image`

Reference to an uploaded image. Includes metadata like dimensions and alt text.

```ts
{
  slug: "featuredImage",
  label: "Featured Image",
  type: "image",
  validation: {
    allowedMimeTypes: ["image/jpeg", "image/png"],
  },
  options: {
    darkVariant: true,
  },
}
```

**Widget options:**

- `darkVariant` — Offer editors a second slot for an image shown in dark color schemes (default: false). See [Dark Mode](https://emdashcmseverything.com/official-docs/guides/dark-mode/).

**Validation options:**

- `allowedMimeTypes` — Non-empty list of exact MIME types accepted for the selected media

The value is stored as an object with the media reference and its metadata:

```json
{
	"id": "01HXK5MZSN...",
	"src": "/_emdash/api/media/file/01HXK5MZSN...",
	"alt": "Description",
	"width": 1920,
	"height": 1080,
	"provider": "local",
	"meta": {
		"storageKey": "01HXK5MZSN....jpg"
	}
}
```

With `darkVariant` enabled, the value can carry the dark counterpart under `darkVariant`, in the same shape:

```json
{
	"id": "01HXK5MZSN...",
	"alt": "Architecture diagram",
	"width": 1920,
	"height": 1080,
	"darkVariant": {
		"id": "01HXK5N2QT...",
		"width": 1920,
		"height": 1080
	}
}
```

### `file`

Reference to an uploaded file such as a document or PDF.

```ts
{
  slug: "document",
  label: "Document",
  type: "file",
  validation: {
    allowedMimeTypes: ["application/pdf"],
  },
}
```

**Validation options:**

- `allowedMimeTypes` — Non-empty list of exact MIME types accepted for the selected media

The value is stored as a provider reference with cached metadata:

```json
{
	"id": "01HXK5MZSN...",
	"provider": "local",
	"filename": "report.pdf",
	"mimeType": "application/pdf",
	"meta": {
		"storageKey": "01HXK5MZSN....pdf"
	}
}
```

`url` and `size`, like the other cached metadata fields, are optional. Content queries return the
persisted value as-is and do not hydrate it from the media library. See
[Render a file in a template](https://emdashcmseverything.com/official-docs/guides/media-library/#render-a-file-in-a-template) for how to
get the file URL.

## Relational types

### `reference`

Links an entry to entries in another collection, and renders as an entry picker in the admin. Its
links belong to a relation: a schema object that joins two collections, names each side, and limits
how many entries each side may link. [Relations](https://emdashcmseverything.com/official-docs/guides/relations/) covers the editor and admin
workflow.

The following field links a post to one entry in the `authors` collection:

```ts
{
  slug: "author",
  label: "Author",
  type: "reference",
  required: true,
  validation: {
    targetCollection: "authors",
    multiple: false,
  },
}
```

**Validation:**

- `targetCollection` — Slug of the collection this field links to. Creating the field creates a
  relation for it.
- `multiple` — Allow more than one linked entry (default: false). Read only alongside
  `targetCollection`, because the limit then belongs to the new relation.
- `relation` — Slug of an existing relation to bind to instead of creating one.
- `relationSide` — Which end of `relation` this collection sits on, `"parent"` or `"child"`. Set it
  only for a relation whose two ends are the same collection, where both ends match.

Give either `targetCollection` or `relation`. A field created from `targetCollection` becomes the
parent end of a relation named `{collection}_{field}`, whose child side takes the field's label and
holds one entry unless `multiple` is set. A field created from `relation` views that relation from
the end its collection sits on, and the collection at the other end is the field's target. One
relation accepts one field per end, so a second field over the same end is rejected. Both forms
store `relation`, `relationSide`, and `targetCollection` on the created field, and the target
collection is fixed from then on: to change it, delete the field and add a new one. Renaming the
field renames the side of the relation it views.

A reference field adds no column to the collection table. Its links live in
`_emdash_content_references`, keyed by each entry's translation group, so a selection is shared
across an entry's translations rather than set per locale. Content reads return the linked entries
under `references`, keyed by field slug, instead of in `data`. In a template, ask for the field by
name — see [Read reference fields](https://emdashcmseverything.com/official-docs/guides/querying-content/#read-reference-fields).

A field bound to a relation cannot set `indexed`, because it stores no column to index, and site
search does not cover it.

#### Fields with no relation

A reference field that names neither a relation nor a target collection keeps a `TEXT` column and
holds one entry ID in it:

```json
"01HXK5MZSN..."
```

A field whose `options.allowMultiple` is set holds a JSON array of entry IDs in the same column:

```json
["01HXK5MZSN...", "01HXK6NATS..."]
```

The column is read and written like any other text column, the collection schema validates the
value as a string, and the field can set `indexed` and act as a content-list filter. It renders as a
text box rather than a picker.

To turn it into a picker, open the field under **Content Types** and choose a referenced collection.
EmDash creates the relation, copies the entry IDs in the column in as links, and clears the field's
`searchable` and `indexed` flags, so content-list filters and site search stop covering it. The
column is left in place and stops being written. [Relations](https://emdashcmseverything.com/official-docs/guides/relations/#bind-a-field-that-has-no-relation)
covers the same step from the editor's side.

A site updated from an earlier release can hold fields in this state. See
[Reference fields bind to relations](https://emdashcmseverything.com/official-docs/deployment/updating/#changed-reference-fields-bind-to-relations)
for what the update binds and what it leaves for you to bind.

## Flexible types

### `json`

Arbitrary JSON data. Use for complex nested structures, third-party integrations, or data without a fixed schema.

```ts
{
  slug: "metadata",
  label: "Metadata",
  type: "json",
}
```

Stored as-is in SQLite JSON column.

> **Caution**
>
> JSON fields have no validation. Use sparingly for truly dynamic data.

### `repeater`

A repeating list of structured rows. Define at least one sub-field in `validation.subFields`; editors can then add, remove, reorder, and edit rows without entering raw JSON.

The following field stores a list of product specifications:

```ts
{
  slug: "specifications",
  label: "Specifications",
  type: "repeater",
  validation: {
    minItems: 1,
    maxItems: 12,
    subFields: [
      { slug: "label", label: "Label", type: "string", required: true },
      { slug: "value", label: "Value", type: "text", required: true },
      { slug: "source", label: "Source", type: "url" },
    ],
  },
}
```

Repeater values are stored as an array of objects:

```json
[
	{
		"label": "Weight",
		"value": "1.2 kg",
		"source": "https://example.com/specifications"
	}
]
```

The allowed sub-field types are `string`, `text`, `url`, `number`, `integer`, `boolean`, `datetime`, `select`, and `image`. Repeaters cannot contain another repeater or a complex field such as `portableText`, `reference`, or `file`.

Repeater validation accepts these properties:

- `subFields` — One or more sub-field definitions. Each definition requires `slug`, `label`, and `type`; it can also set `required`. A `select` sub-field supplies its choices through `options`.
- `minItems` — Minimum number of rows. Must be zero or greater.
- `maxItems` — Maximum number of rows. Must be one or greater and cannot be less than `minItems`.

### `blocks`

An ordered list of typed content blocks. Each block type has its own fields and retained numbered versions. Define block types through the schema API, MCP, or a seed file before adding them to a collection field.

The following field allows editors to compose a page from hero and call-to-action blocks:

```ts
{
  slug: "layout",
  label: "Layout",
  type: "blocks",
  validation: {
    allowedTypes: ["hero", "call_to_action"],
    maxItems: 20,
  },
}
```

Every stored block carries its type, version, and stable key alongside the fields declared by that version:

```json
[
	{
		"_type": "hero",
		"_version": 1,
		"_key": "01K5AB3F7M9QZ2X8W4V6T1R0YH",
		"heading": "Bread made slowly, by hand."
	}
]
```

When writing content to a field with exactly one entry in `allowedTypes`, you can omit `_type` for new blocks or existing blocks of that type. EmDash stores the allowed type. Provide an explicit `_type` when several types are allowed, when editing a stored block of a retired type, or in seed files. Adding a second type to `allowedTypes` makes writes that omit `_type` fail, so send `_type` from scripts and integrations that must keep working through schema changes.

Blocks validation accepts these properties:

- `allowedTypes` — Ordered block type slugs available for new blocks.
- `retiredTypes` — Server-managed block types retained for stored content but unavailable for new blocks.
- `minItems` — Minimum number of blocks. Raising it above zero on a populated collection requires a content migration.
- `maxItems` — Maximum number of blocks, up to 100.

A blocks field is optional and defaults to an empty array. It cannot be required, unique, searchable, indexed, or rendered by a custom field widget. Block definitions can use scalar, text, selection, Portable Text, image, file, and repeater fields. They cannot contain references, JSON, slugs, or nested blocks.

Render the stored array with `<Blocks value components fallback>` from `emdash/ui`. See [Build pages with blocks](https://emdashcmseverything.com/official-docs/guides/blocks/) for seed, component-map, missing-renderer, activation, and migration examples.

## Field properties

All fields support these common properties:

| Property       | Type        | Description                         |
| -------------- | ----------- | ----------------------------------- |
| `slug`         | `string`    | Unique identifier (required)        |
| `label`        | `string`    | Display name (required)             |
| `type`         | `FieldType` | Field type (required)               |
| `required`     | `boolean`   | Require a value (default: false)    |
| `unique`       | `boolean`   | Enforce uniqueness (default: false) |
| `searchable`   | `boolean`   | Include the field in full-text search (default: false) |
| `indexed`      | `boolean`   | Enable indexed field sorting/filtering |
| `translatable` | `boolean`   | Store a value per locale (default: true) |
| `defaultValue` | `unknown`   | Default value for new entries       |
| `validation`   | `object`    | Type-specific validation rules      |
| `widget`       | `string`    | Custom widget override              |
| `options`      | `object`    | Widget configuration                |
| `sortOrder`    | `number`    | Display order in admin              |

`indexed` is available for scalar fields: `string`, `url`, `number`, `integer`, `boolean`,
`datetime`, `select`, `reference`, and `slug`. An indexed field can be passed as the `orderBy`
field or used in `fieldFilters` in content list queries. Avoid indexing fields that are not used
for sorting or filtering because every index adds storage and write overhead.

`searchable` adds the field's text to the collection's full-text search index. Set `translatable: false` for identifiers, prices, flags, and other values that must stay the same across every translation of an entry; when one locale changes a non-translatable field, EmDash synchronizes the value to its translated entries.

The `blocks` type uses only `slug`, `label`, `type`, `translatable`, `validation`, and `sortOrder` from this common set. Its array is never column-required and always defaults to `[]`.

## Reserved field slugs

These slugs are reserved and cannot be used:

- `id`
- `slug`
- `status`
- `author_id`
- `primary_byline_id`
- `created_at`
- `updated_at`
- `published_at`
- `scheduled_at`
- `deleted_at`
- `version`
- `live_revision_id`
- `draft_revision_id`
- `terms`
- `bylines`
- `byline`

## TypeScript types

Import the field type definitions for programmatic use:

```ts
import type { FieldType, Field, CreateFieldInput } from "emdash";

const fieldTypes: FieldType[] = [
	"string",
	"text",
	"url",
	"number",
	"integer",
	"boolean",
	"datetime",
	"select",
	"multiSelect",
	"portableText",
	"image",
	"file",
	"reference",
	"json",
	"slug",
	"repeater",
	"blocks",
];
```
