Skip to content

Latest commit

 

History

History
245 lines (195 loc) · 9.56 KB

File metadata and controls

245 lines (195 loc) · 9.56 KB
title Field Kit
description Composable field widgets for json fields, configured through seed options.

import { Aside } from "@astrojs/starlight/components";

EmDash's json field type stores arbitrary structured data, edited by default through a single-line text input that takes raw JSON. Field Kit is a first-party plugin that ships four composable widgets for json fields, configured entirely through seed options so site builders can use them with seed schema alone.

Field Kit widgets store **clean JSON**: the stored value is plain content data, so removing the plugin leaves valid content behind.

Installation

Install the package from npm:

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

The following configuration registers the plugin:

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": [...] }
}

Widgets

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.

object-form

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.

list

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.

grid

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.

tags

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.

Sub-fields

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.

Summary templates

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.

Data durability

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.

See also