Skip to main content
Version: next

Section field types

Reference of the field types available in a section's schema, the editor control each one renders, and how to configure it.

Each editable field in a section's schema has a type that selects its editor control in the properties sidebar. This page lists every available type and how to configure it. See Create custom sections for how a schema fits into a section.

Every key in a section's schema mirrors a key in its defaultProps; your component then receives the edited values as props.

Two attributes apply to every type: attributes.comment is shown to the contributor as a hint under the control (and announced with it), and attributes.required marks the field as required.

Conditional fields​

Any field can carry a condition that gates its visibility on a sibling field of the same schema. The field renders only when the sibling's effective value — its current value, or its default_value when unset — matches equals, strictly, or by membership when equals is an array. Omit condition for the fields that are always visible.

mode: {
type: "select",
label: "Categories to show",
default_value: "all",
options: [
{ label: "Every category", value: "all" },
{ label: "Picked categories", value: "selected" },
],
},
level: {
type: "select",
label: "Category level",
default_value: "all",
condition: { field: "mode", equals: "all" },
options: [
{ label: "Every level", value: "all" },
{ label: "Level 1", value: "1" },
],
},

Hiding a field clears nothing: the value a content manager entered stays in the section's props, and the published page hands all of them to your resolver. So a field shown by a condition the section no longer displays still needs a defined meaning — the built-in CategoryList reads its mode first and ignores the props of the branch the mode did not choose, which is what lets a contributor switch back and forth without losing their pick. In the editor's preview the resolver only sees the props that travel, which a section has to declare: see create custom sections.

checkbox​

A boolean on/off toggle.

{ type: "checkbox", label: "Show divider", default_value: false }

color​

Round swatches from the themed palette. The selected colour is named next to the label, and a value outside the palette is kept as a custom swatch.

{ type: "color", label: "Text color", default_value: "#111827" }

A field whose default_value is "" (or with attributes: { allowEmpty: true }) also offers a Default swatch that stores "", so the contributor can return to the theme colour after picking one.

Set attributes: { transparent: true } to add a Transparent swatch (persisted value transparent). It is also the only way to clear a selected colour back to none — a radio group cannot be unset.

{
type: "color",
label: "Background",
default_value: "",
attributes: { transparent: true },
}

datasource​

A picker backed by a data source — a typed catalog entity (a category, a product…) resolved server-side by the active backend. The contributor searches the source in a combobox (the term is sent to the backend, results are capped) and picks an entity; the field then shows it as a card with its key and Replace / Remove actions, and stores the entity's key (its id, or a product SKU). A key can also be typed by hand behind the "Enter a key manually" fold, which opens by itself when the source lists nothing. The entity itself is delivered to your component as dynamic data through a matching GraphQL field (see Dynamic sections).

{ type: "datasource", dataSource: "product", label: "Product", default_value: null }

dataSource names the source (for example category or product). The available sources depend on the backend; a source the backend does not implement yields an empty picker.

Contextual options​

By default, a source lists its options from the whole catalogue. A field can declare optionsContext — an array of section keys — to make its options contextual: the raw current values of those keys travel to the source along with the section's type, and the source computes its options on that state instead. The built-in case is the Product List on the Gezy flavor, whose facet pickers list what the current search still allows, like the search page's filters do: a brand already picked stays proposable (a search accepts several), a value the search excludes never shows.

{
type: "datasource",
dataSource: "brand",
label: "Brands",
default_value: null,
optionsContext: ["searchQuery", "onlyPromotions", "brands"],
}

Only the keys the field declares travel — none of the section's other values does — and they go as authored: the editor collects them without knowing their semantics, and the source reads them server-side. The built-in Gezy pickers read them as the section's current search; a source you register yourself receives the same context as listOptions's third parameter and can narrow its options however it sees fit. In a repeater, every row's picker shares the section's context — a row never recomputes it from its own values.

Options are computed with the values current when the properties sidebar opens: the picker captures its context once, so editing the term or the facets afterwards does not recompute the list — closing and reopening the sidebar does. A field without optionsContext, or a source that ignores the context, behaves as before: the whole catalogue.

Sources per flavor​

SourceGezyMagento 2Magento 1
category✅✅✅
product✅✅ (search: the configured engine, or Magento's native search fallback)✅ (list: requires an active search engine)
user✅✅✅
brand✅✅—
searchCategory✅✅—
caracValue✅✅—

A source a flavor does not implement degrades quietly: the field resolves to null and the picker lists no option, without an error.

The last three sources are picker-only: they resolve no entity — resolve answers null — because their picks are not data to display but filters the section's own search reads (the Product List's "Search" source). brand and searchCategory list the id buckets of the search's brand and category facets, caracValue one option per characteristic bucket, labelled "Characteristic — Value" and storing the [code, value] pair the search expects. When the field declares optionsContext, their buckets are recomputed on the section's current search: an already-set brand or category stays pickable (the search accepts several, in an OR), while an already-set characteristic keeps its filter but its buckets leave the picker (one value per characteristic). searchCategory serves the search's filter categories — the same ids as the category source's navigation tree, so picks made through either source stay valid. All three cap their list (20 options) and narrow it by the picker's own search term.

On Magento, the category picker lists the first three levels of the navigation tree, indented; a deeper category is not selectable, and the search term filters the loaded levels rather than querying the catalog again. The Category List section stops at the same three levels: its level field offers 1 to 3 on every backend, whatever the navigation actually serves, and a deeper level is only reachable through "Every level" — on a backend serving more than three levels, the section cannot list the fourth alone. The product picker is the other way round: it lists nothing until the contributor types a term, which is sent to the search backend.

The picker also accepts a key typed by hand (a SKU for product, an id for category), next to the list. Contributors who know their catalogue go straight to the key, and it is the way to contribute when the source returns no option at all. The key is applied with Enter, with the Apply button, or by leaving the field — never while typing, so a partial key is never stored. It is not verified against the backend: the section preview is the feedback, and a key that resolves to nothing renders the section's empty state.

On Magento 1 the product picker lists options only when a search engine (ElasticSearch, Algolia, Attraqt) is active: there is no native full-text product search to fall back on, so without an engine the list stays empty and contributing goes through the manual key. A product already picked still renders. Every other source, and the Category Products Grid section, work without an engine.

The picker is empty as well while the engine is down, on both Magento flavors: the search layer answers an error fallback instead of failing. When that happens the server logs a warning — The search engine answered its error fallback — so an empty picker can be told apart from a genuine "no result".

If the manual key is not enough for your project, register your own product source: the registry is a map where the last registration wins.

services.DI.get("cms").dataSources.register("product", myProductDataSource);

Register it from afterServerServicesInit, or from onServerServicesInit of an extension declared after cms() in the extensions array of front-commerce.config.ts. Hooks run in declaration order, so an extension declared before cms() would see its source overwritten by the CMS one.

date​

A native date picker. The value is an ISO YYYY-MM-DD string.

{ type: "date", label: "Publication date", default_value: "" }

image​

An image picker storing a source URL and alt text. It opens the built-in media library, where the content manager browses folders and picks or uploads an asset. The inspector shows the media tile under the field label, then the alternative text indented behind a rail: a compound field displays its value first and the settings that qualify it below.

{ type: "image", label: "Illustration", default_value: { src: "", alt: "" } }

The same input as url, for internal or external links.

{ type: "link", label: "Button link", default_value: "/contact", attributes: { placeholder: "https://…" } }

margin​

A spacing editor for the four margins (unit + lock to edit all sides at once).

{
type: "margin",
label: "Margin",
default_value: { top: 0, right: 0, bottom: 0, left: 0, unit: "px", locked: true },
}

media​

A media picker storing the selected file's URL as a plain string (no alt text). It opens the same media library as image.

{ type: "media", label: "Attachment", default_value: "", attributes: { placeholder: "https://…" } }

number​

A number. With both min and max it renders as a slider; without bounds it renders as a numeric input, since a slider needs both ends to mean anything. attributes.unit is shown after the value ("80 %").

{ type: "number", label: "Columns", default_value: 3, attributes: { min: 1, max: 6, step: 1 } }

The min, max and step attributes also accept a field reference { field: "<key>" } pointing at another field of the section's root schema. At render time the editor replaces the reference with the referenced field's effective value (its current value, or its default_value when unset) — passed through as is, without clamping: the referenced field's own bounds are the envelope. This lets one field's bounds follow another field's live value, e.g. a tile's column span bounded by the section's column count:

{
columns: { type: "number", label: "Columns", default_value: 4, attributes: { min: 1, max: 8, step: 1 } },
tiles: {
type: "repeater",
label: "Tiles",
itemLabel: "Tile",
default_value: [],
itemSchema: {
colSpan: {
type: "number",
label: "Column span",
default_value: 1,
// Bounded by the section's live `columns` value — even from an
// item field, the reference always targets the root schema.
attributes: { min: 1, max: { field: "columns" }, step: 1 },
},
},
},
}

A reference that cannot be resolved (unknown key, non-numeric value) omits the bound and logs a warning in development; the slider then falls back to its own default bound.

padding​

The same spacing editor as margin, for paddings.

{
type: "padding",
label: "Padding",
default_value: { top: 16, right: 16, bottom: 16, left: 16, unit: "px", locked: true },
}

range​

Always a slider, for a bounded range. attributes.unit is shown after the value. Its min, max and step attributes accept the same field references { field: "<key>" } as number.

{ type: "range", label: "Opacity", default_value: 100, attributes: { min: 0, max: 100, step: 1, unit: "%" } }

repeater​

A repeatable list of sub-items — use it for FAQs, testimonials, link lists, tabs, and any variable-length collection. Each item is an object edited through its own nested form, described by itemSchema (a schema made of the field types on this page). Your section receives the value as an array of those item objects.

{
type: "repeater",
label: "Links",
itemLabel: "Link",
default_value: [{ label: "", url: "", description: "" }],
itemSchema: {
label: { type: "text", label: "Label", default_value: "" },
url: { type: "link", label: "URL", default_value: "" },
description: { type: "text", label: "Description", default_value: "" },
},
}

itemLabel (optional) names each entry in the editor (for example "Link 1"). Content managers can add, remove, and reorder items.

An itemSchema may also hold a datasource field, so each row picks one catalog entity — that is how a "list of products" section lets the content manager choose N products in their own order:

{
type: "repeater",
label: "Products",
itemLabel: "Product",
default_value: [],
itemSchema: {
sku: { type: "datasource", dataSource: "product", label: "Product", default_value: null },
},
}

The editor preview resolves those keys like a top-level datasource field, so the section shows the same content in the editor as on the published page. Only the datasource keys of a row are sent for resolution: the editorial fields next to them never trigger a new request.

rich_text​

A rich-text editor. The value is a TipTap document (JSONContent), not a plain string.

{ type: "rich_text", label: "Body", default_value: "" }

Alongside text formatting, the toolbar lets content managers add links, insert images from the media library, and build tables. Rendered headings get a slugified id, so they can serve as in-page anchor targets.

The </> button opens the HTML source of the text for quick edits or pasting formatted content. The HTML is a view, not the stored format: when the source is applied, the editor parses it back into its document and drops the tags and attributes it does not support (<div>, class, style, <script>, <iframe>…), then says so and shows the HTML it kept. Escape leaves the source view without applying.

Render it in your section with the TiptapContent theme component — the same one the built-in sections use:

import TiptapContent from "theme/modules/CmsPage/sections/tiptapRenderer";

// `body` is the `rich_text` prop your section receives
<TiptapContent content={body} />;

It renders the document with the theme's own atoms (headings, paragraphs, links). Two ways to customise it:

  • per usage — pass its componentsMap prop to override how individual nodes or marks render (entries you omit fall back to DEFAULT_TIPTAP_COMPONENTS);
  • globally — override it like any theme component by shadowing theme/modules/CmsPage/sections/tiptapRenderer in your own theme, which also changes how the built-in sections render rich text.

select​

A single choice among the options you provide.

{
type: "select",
label: "Alignment",
default_value: "center",
options: [
{ label: "Left", value: "left" },
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
],
}

Up to four options with short labels (12 characters or fewer) render as a segmented control; larger sets render as a native <select>, which the inspector's 280 px can hold. Force either control with attributes: { display: "picker" } or attributes: { display: "dropdown" }.

{
type: "select",
label: "Icon",
default_value: "checkmark",
attributes: { display: "dropdown" },
options: [
/* …10+ options… */
],
}

text​

A single-line text input.

{ type: "text", label: "Heading", default_value: "Welcome", attributes: { placeholder: "Enter a heading" } }

textarea​

A multi-line text input.

{ type: "textarea", label: "Summary", default_value: "", attributes: { placeholder: "Short summary…", rows: 4 } }

title​

A structured heading editor (text, level, size, weight, alignment). The text input carries the field label; alignment, colour and the typography fold sit indented behind a rail, as settings of that text. The optional overline sub-object renders a small editorial label above the title ("pill" badge by default, or plain "text"; empty means no overline).

{
type: "title",
label: "Title",
default_value: { text: "Section title", level: "h2", size: "text-4xl", weight: "font-bold", alignment: "center", overline: { text: "New arrivals", style: "pill" } },
}

Set attributes: { overline: false } on a title field whose heading the storefront renders without an overline (e.g. a decorative secondary title): the overline input is then hidden, so editors cannot author a label that would not be rendered.

url​

A URL input.

{ type: "url", label: "Video URL", default_value: "", attributes: { placeholder: "https://…" } }