Skip to content

Field Kit

EmDash’s json field type stores arbitrary structured data. Its default editor is a single-line input for raw JSON. Field Kit is a first-party native plugin that adds four widgets configured through field options in a seed file.

Install the package from npm:

Terminal window
npm i @emdash-cms/plugin-field-kit

Import fieldKitPlugin, call it, and add the result to plugins: []:

astro.config.mjs
import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { fieldKitPlugin } from "@emdash-cms/plugin-field-kit";
export default defineConfig({
integrations: [
emdash({
plugins: [fieldKitPlugin()],
}),
],
});

Attach a widget to any json field by setting widget to field-kit:<name>. The following field definition uses the list widget:

{
"slug": "ingredients",
"type": "json",
"widget": "field-kit:list",
"options": {
"fields": [{ "key": "name", "label": "Name", "type": "text", "required": true }]
}
}
Widget Use for Stored value
object-form Inline form for flat JSON objects { key: value, ... }
list Ordered array editor with add / remove / reorder [{ ... }, ...]
grid Rows × columns matrix { rowKey: { colKey: value } }
tags Free-form chip/tag input ["tag1", "tag2"]

If a widget is missing its required options (e.g. fields for object-form/list, or rows/columns for grid), the editor renders an inline “Widget misconfigured” warning instead of a broken input — useful while iterating on seed schemas.

Renders a group of typed sub-fields that store as a single JSON object. Good for fixed-shape structured data like nutrition facts or contact info. The following field definition configures a nutrition object:

{
"slug": "nutrition",
"type": "json",
"widget": "field-kit:object-form",
"options": {
"collapsed": false,
"fields": [
{ "key": "calories", "label": "Calories", "type": "number", "suffix": "kcal" },
{ "key": "protein", "label": "Protein", "type": "number", "suffix": "g" },
{ "key": "fat", "label": "Fat", "type": "number", "suffix": "g" },
{ "key": "carbs", "label": "Carbs", "type": "number", "suffix": "g" }
]
}
}

Stored value: { "calories": 250, "protein": 12.5, "fat": 8, "carbs": 30 }.

Option Type Default Description
fields SubFieldDef[] (required) Sub-field definitions — see Sub-fields.
collapsed boolean false Render the group collapsed by default.
helpText string — Help text shown below the widget.

An ordered array editor with add, remove, and reorder controls. Each row is a JSON object whose shape is defined by fields. The row header shows a summary rendered from a Mustache-style template. The following field definition configures an ingredients list:

{
"slug": "ingredients",
"type": "json",
"widget": "field-kit:list",
"options": {
"itemLabel": "Ingredient",
"min": 1,
"max": 50,
"sortable": true,
"summary": "{{name}} — {{amount}}",
"fields": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "amount", "label": "Amount", "type": "text" },
{ "key": "optional", "label": "Optional", "type": "boolean" }
]
}
}

The stored value is an array of row objects:

[
{ "name": "Flour", "amount": "500g", "optional": false },
{ "name": "Butter", "amount": "200g", "optional": false }
]
Option Type Default Description
fields SubFieldDef[] (required) Sub-field definitions for each row.
itemLabel string "Item" Singular label for a row (used in the “Add” button and fallback row titles).
min number — Minimum number of items. Below this, the remove button hides.
max number — Maximum number of items. At this count, the add button hides.
sortable boolean true Show up/down reorder buttons.
summary string — Mustache template rendered as the collapsed-row title. See Summary templates.
helpText string — Help text shown below the widget.

A two-dimensional matrix of rows × columns. Each cell can be a toggle, text input, number input, or select. Useful for matrices like seasonal availability, price tables, or feature comparisons. The following field definition configures a seasonal availability grid:

{
"slug": "availability",
"type": "json",
"widget": "field-kit:grid",
"options": {
"cell": "toggle",
"rows": [
{ "key": "berries", "label": "Berries" },
{ "key": "stoneFruit", "label": "Stone fruit" },
{ "key": "citrus", "label": "Citrus" }
],
"columns": [
{ "key": "spring", "label": "Spring" },
{ "key": "summer", "label": "Summer" },
{ "key": "autumn", "label": "Autumn" },
{ "key": "winter", "label": "Winter" }
]
}
}

The stored value is an object keyed by row, then by column:

{
"berries": { "spring": false, "summer": true, "autumn": false, "winter": false },
"stoneFruit": { "spring": false, "summer": true, "autumn": true, "winter": false },
"citrus": { "spring": false, "summer": false, "autumn": true, "winter": true }
}
Option Type Default Description
rows GridAxisDef[] (required) Row definitions: { key, label, image? }.
columns GridAxisDef[] (required) Column definitions: { key, label, image? }.
cell "toggle" | "text" | "number" | "select" "toggle" Cell input type, applied uniformly to every cell.
cellOptions string[] | Array<{ label, value }> [] Required when cell is "select".
helpText string — Help text shown below the widget.

A chip-style input for arrays of strings. Supports a fixed suggestions list, free-form custom values (toggleable), case transforms, and an optional max. The following field definition configures a keywords tag input:

{
"slug": "keywords",
"type": "json",
"widget": "field-kit:tags",
"options": {
"placeholder": "Add a keyword…",
"max": 10,
"transform": "lowercase",
"allowCustom": true,
"suggestions": ["vegan", "vegetarian", "gluten-free", "dairy-free", "nut-free"]
}
}

Stored value: ["vegan", "gluten-free"].

Press Enter or , to commit a tag. Backspace on an empty input removes the last tag. Duplicate tags are silently ignored.

Option Type Default Description
placeholder string "Add..." Input placeholder shown when no tags are present.
max number — Maximum number of tags. The input hides at the limit.
suggestions string[] [] Autocomplete suggestions surfaced via a <datalist>.
allowCustom boolean true When false, only values from suggestions can be added.
transform "none" | "lowercase" | "uppercase" | "trim" "none" Normalize tags as they’re added.
helpText string — Help text shown below the widget.

object-form and list accept an options.fields array of typed sub-field definitions. Each entry has a key (the JSON object key it writes to), a label, a type, and type-specific extras.

Sub-field type Renders as Notable extras
text Single-line input placeholder
textarea Multi-line input rows (default 3), placeholder
number Numeric input min, max, step, prefix, suffix, placeholder
boolean Toggle switch —
select Dropdown options: string[] | Array<{ label, value }>, placeholder
date Date input —
color Native color picker paired with a hex text input —
url URL input (HTML5 type="url") placeholder

Common props on every sub-field: required, helpText, defaultValue.

The list widget renders each collapsed row using a Mustache-style template in options.summary. {{key}} is replaced with the row’s value for that key (coerced to a string). Falsy values fall back to "{itemLabel} {n}". The following template combines two keys:

"summary": "{{name}} — {{amount}}"

Renders rows like Flour — 500g. The template is plain string substitution — no HTML, no nested expressions.

Field Kit widgets store plain JSON in the field’s existing column, using only that column. If you remove @emdash-cms/plugin-field-kit from your config, the data stays valid — the field reverts to the default json text input.

This applies even when you change the widget shape: unknown keys on stored objects are preserved on the next write, so you can evolve a schema without losing data captured under an older field set.