Fields
A field is one control in the editor: a text box, an image picker, a number, a color choice. If something is not a field, a merchant cannot change it from saved data.
The component definition declares the field. The manifest shows it to the editor. The page document stores its value. The value is checked again when the page is read, so a saved document cannot bypass the rules.
Most themes begin with text, image, number and select. Add the other
kinds when the component needs them.
Scalar and text fields
| Kind | Stored value | Notes |
|---|---|---|
text, textarea | string or per-language map | One of default or contentKey, never both |
number | finite number | Optional min, max; with step, values lie on a grid starting at min or 0 |
boolean | boolean | |
select | one of options | default must be an option; optionLabels names each option |
url | safe link target | Site path, #fragment, ?query, http(s)://, mailto: or tel:; script and protocol-relative tricks are rejected |
color | #rgb or #rrggbb | A color name or url(…) is rejected |
display changes how an editor shows a field, never what is stored:
Code
Store references
These fields store a handle or key, not a copy of the data.
| Kind | Stored value | Read with |
|---|---|---|
menu | menu handle (main-menu) | storefront.menus.get(handle) |
collection | collection handle (best-sellers) | storefront.products.list({ collection }) |
product | product handle | storefront.products.get(handle) |
category | category slug | storefront.categories.list() |
image | media key, never a URL | pictureOf(image, config.media) |
productList | ordered product handles | storefront.products.get(handle) per item |
collectionList | ordered collection handles | storefront.collections.get(handle) per item |
productList and collectionList cap at 50; their handles must be distinct.
A deleted product or collection reads as absent, not as an error.
Structured fields
| Kind | Stored value | Notes |
|---|---|---|
richtext | { type: "doc", version: 1, content } or one per language | Fixed shape; formats narrows it; render with richTextToHtml |
list | [{ id, props }] | Item fields of any kind except list and colorSchemes; itemLabel names the title field |
style | { spacing?, layout?, size? }, per breakpoint | Steps and choices, never CSS; see Style and color |
colorScheme | a scheme id | The store must have that scheme |
colorSchemes | [{ id, name, colors }] | A theme setting, at most one; at most 24 schemes |
A rich-text document contains paragraphs, headings 2–4, bullet and ordered lists, line breaks, bold, italic, underline, strike and safe links. Nothing else is representable, so a stored value cannot carry markup or scripts.
Code
href turns each stored link into the href to render; passing the request
store's localizer keeps an internal link in the shopper's language.
richTextToPlainText gives the words for a meta description or an
aria-label. A field with inline: true is one line: a single paragraph with
marks and links, rendered without a block element.
A list item's id is unique within its list, so reordering never changes which
item is which. Item fields cannot use contentKey: every item would read the
same key. A list default carries its own ids.
Rules for every field
keyis the exact prop name the component receives. Keys are unique within a component (and across all settings groups).blockscan never be a field key: the renderer gives every component its blocks under that prop.- A list item field cannot be keyed
id(the item's own identity), cannot be alistorcolorSchemes, and cannot usecontentKey. - Every default is checked at assembly with the same rules as a saved value.
An out-of-range number or an option that is not in
optionsis aninvalid-fieldfromvalidateThemeDefinition, never a silent bad default. - Labels and descriptions are a string, or a map that covers every language
the theme declares in
locales.
Shared field options
| Option | Applies to | What it does |
|---|---|---|
key | every field | The exact prop name the component receives |
label | every field | Editor-facing name; a string or per-language map |
description | every field | Editor help text |
required | every field | The value must exist after content/default fallback |
visibleIf | every field | Editor-only condition on neighbouring fields |
default | most fields | Creation-time value; images have none |
contentKey | text, textarea | Inherited copy from the theme's content pack |
optionLabels | select | Editor words for each option |
min, max, step | number, lists | Bounds and grid / list length |
display | select, number | dropdown, radio, segmented, input or slider (a slider needs min, max and step) |
formats | richtext | Allowed marks and blocks |
inline | richtext | One-line mode |
itemLabel | list | Item field an editor shows as its title |
visibleIf is a small fixed JSON condition, not an expression:
Code
Conditions support { field, equals }, { field, in }, { all }, { any }
and { not }, nested at most four levels. The renderer ignores the condition,
and a hidden field keeps its value.
How a value resolves
For a field on an instance, the order is:
- the stored instance value — for a translatable value, the entry for the
rendered language, then each language in
fallbackLocales, content[contentKey]fortextandtextareafields,- the field's
default(a translated default in the same language order), - omitted — the component's own prop default applies.
Translatable kinds are text, textarea, url and richtext: their stored
value may be one string (or document) for every language, or a map from
language tags to values. An image's alt is translatable too. Every other kind
stores one value for every language.
A missing translation is not an error. A malformed map is invalid-prop; an
unknown key is unknown-prop; a required field with no value is
missing-required-prop.
A writer can check one value before saving:
Code
The writer check is stricter than the reader: an image reference or list item with an undeclared key is refused here, while the reader ignores it.
Typed props
Derive the component's props from its definition. Do not write them twice.
Code
InferProps:
- makes a field with a
defaultalways present, - makes every other field optional,
- narrows a
selectto its option literals, - turns
imageintoResolvedImage(withaltalready in the rendered language), - turns
listinto{ id }plus the item fields, - merges whatever
loadreturns over the fields.
The renderer does the same merge at runtime: { ...resolved.props, ...loaded }.
If a definition's component reads a prop from load, keep that name out of
fields.
Images and lists in practice
An image field has no default because the theme cannot know a store's media. The component renders its own fallback when no image was chosen.
Code
An image value also carries optional width, height and focal (a point
from 0 to 1 on each axis). See Images for the store's
media delivery, resizing and the Astro image service.
A list receives [{ id, ...fields }]. The reader drops a broken or repeated
item and keeps the rest, up to max.