package.json
{
"name": "ue-component-model",
"version": "0.0.0-semantically-released",
"private": true
}
references/architecture.md
# Architecture: How the Three JSON Files Connect
## The Three Files
An AEM Edge Delivery Services (aem.live) project using the Universal Editor (UE) requires three JSON configuration files at the project root:
1. **component-definition.json** — Registers components in the UE palette (what authors can add)
2. **component-models.json** — Defines the property panel fields (what authors can configure)
3. **component-filters.json** — Controls component hierarchy (where components can be placed)
## How They Connect
```
component-definition.json component-models.json component-filters.json
┌──────────────────────┐ ┌─────────────────────┐ ┌──────────────────────┐
│ { │ │ { │ │ { │
│ "id": "hero", │ │ "id": "hero", │ │ "id": "section", │
│ "template": { │ │ "fields": [...] │ │ "components": [ │
│ "model": "hero"──┼─────────►│ } │ │ "hero", │
│ "filter": "cards"┼──────┐ └─────────────────────┘ │ "cards", ... │
│ } │ │ │ ] │
│ } │ │ │ } │
└──────────────────────┘ │ ┌─────────────────────┐ │ { │
└──►│ "id": "cards", │ │ "id": "cards", │
│ "components":["card"]│◄─────┤ } │
└─────────────────────┘ └──────────────────────┘
```
### Link 1: `template.model` → `component-models.json` id
The `model` property in a component definition's template references a model `id` in component-models.json. When an author selects a component, the UE renders the matching model's `fields` as the property panel.
### Link 2: `template.filter` → `component-filters.json` id
Container components (like Cards) use a `filter` to restrict which child components can be added inside them. The filter `id` in component-filters.json lists the allowed `components` array.
### Link 3: Section filter lists block IDs
The `section` filter in component-filters.json lists all block IDs that can be added to a page section. New blocks MUST be added here to appear in the author's "add component" menu.
## Full Pipeline: AEM → Markdown → HTML → Block JS
```
1. Author creates content in Universal Editor
├── component-definition.json tells UE what components exist
├── component-models.json tells UE what fields to show
└── component-filters.json tells UE where components can go
│
2. AEM stores content as JCR nodes
├── resourceType determines the rendering component
├── template properties become JCR properties
└── Block.java renders HTML table structure
│
3. helix-html2md converts AEM HTML → Markdown
├── Block div with classes → Grid table with header row
├── Block name from first class (title-cased)
└── Variants from additional classes → parenthetical "(variant)"
│
4. helix-html-pipeline converts Markdown → HTML
├── Grid table → block div with CSS classes
├── Block name → lowercase kebab-case class
└── Each row → nested div, each cell → inner div
│
5. Block JS decorates the HTML
├── blocks/<name>/<name>.js receives the block div
├── decorate(block) reads content from the div structure
└── Transforms into interactive UI
```
## Block Types
### Simple Block (most common)
A block with a single model, no children. Example: Hero, Embed.
- **definition**: 1 entry with `model` reference
- **model**: 1 entry with fields
- **filter**: Just add block ID to `section` components list
### Container Block
A block that contains repeatable child items. Example: Cards.
- **definition**: 2 entries (container + item). Container has `filter`, item has `model`
- **model**: 1 entry for the item
- **filter**: 1 new filter defining allowed children, plus add container to `section` list
### Key-Value Block
A block rendered as key-value pairs (2-column table). Example: Product Details, Commerce blocks.
- **definition**: 1 entry with `"key-value": true` in template
- **model**: 1 entry with fields
- **filter**: Add to `section` list
## Resource Types
Almost all blocks use one of these:
- `core/franklin/components/block/v1/block` — Standard block (most blocks)
- `core/franklin/components/block/v1/block/item` — Child item within a container block
- `core/franklin/components/section/v1/section` — Section component
- `core/franklin/components/text/v1/text` — Default text
- `core/franklin/components/title/v1/title` — Title
- `core/franklin/components/image/v1/image` — Image
- `core/franklin/components/button/v1/button` — Button
- `core/franklin/components/columns/v1/columns` — Columns (special)
Custom blocks should use `core/franklin/components/block/v1/block`. Do NOT create custom resource types.
## Field Name Conventions & Semantic Collapsing
The pipeline uses naming suffixes to collapse related fields into single semantic elements:
| Suffix | Behavior | Example |
|--------|----------|---------|
| `Alt` | Image alt text | `imageAlt` collapses into `image` as `<img alt="...">` |
| `Text` | Link display text | `linkText` collapses into `link` as `<a>text</a>` |
| `Title` | Link/heading title | `linkTitle` collapses into `link` as `<a title="...">` |
| `Type` | Heading level or link style | `titleType` → `<h2>`, `linkType` → class on `<a>` |
| `MimeType` | File type hint | `fileMimeType` → type attribute |
**Element Grouping**: Fields sharing a prefix with `_` separator are grouped into a single table cell:
- `cta_link`, `cta_text`, `cta_type` → grouped in one cell
**Block Options (`classes`)**: The `classes` field becomes CSS classes on the block wrapper:
- `"classes": ["light", "left"]` → `<div class="teaser light left">`
- Boolean sub-options: `classes_fullwidth: true` → adds `fullwidth` class
## Type Inference
The pipeline auto-detects content types:
- MIME type starting with `image/` → `<picture><img src="...">`
- Values matching URL patterns (`https://`, `#`) → `<a href="...">`
- Values starting with HTML tags → rendered as rich text
- Multi-value properties → comma-separated or `<ul><li>` lists
## RTE Filter Configuration
Filters can include an `rte` property to configure the richtext editor toolbar for components using that filter. This controls which formatting options are available to authors when editing richtext fields.
### Toolbar Categories
The `toolbar` property organizes actions into category groups:
| Category | Available actions | Notes |
|----------|------------------|-------|
| `format` | `bold`, `italic`, `underline`, `strike` / `strikethrough` | `strike` and `strikethrough` are aliases. Optional `tag` property overrides HTML element. |
| `blocks` | `h1`-`h6`, `paragraph`, `code_block` | Block-level formatting |
| `list` | `bullet_list` / `bullist`, `ordered_list` / `numlist` | Aliases: `bullet_list`=`bullist`, `ordered_list`=`numlist`. Optional `wrapInParagraphs`. |
| `insert` | `link`, `table`, `image`, `unlink` | `link` supports `disableForImages`, `hideTarget`. `image` supports `wrapInPicture`. |
| `alignment` | `left`, `right`, `center`, `justify`, `alignleft`, `alignright`, `aligncenter`, `alignjustify` | Short and prefixed forms are both valid |
| `indentation` | `indent`, `outdent` | Block indentation |
| `sr_script` | `superscript`, `subscript` | Superscript/subscript formatting |
| `direction` | `ltr`, `rtl` | Text direction |
| `editor` | `removeformat`, `paste_text`, `fullscreen` | Editor utility actions |
| `advanced` | (custom items) | For custom toolbar extensions |
| `dropdowns` | (custom items) | Custom dropdown menus |
| `sections` | (custom items) | Custom toolbar sections |
| `extensions` | (custom items) | Custom toolbar extensions |
Each action supports these common options:
- `hideInline` (boolean) — hide from inline toolbar
- `label` (string) — custom button label
- `shortcut` (string) — keyboard shortcut
### Top-Level RTE Properties
| Property | Required | Description |
|----------|----------|-------------|
| `toolbar` | Yes | Toolbar category configuration (object, string, or boolean) |
| `plugins` | Yes | Space-separated list of enabled plugins |
| `icons` | Yes | Icon set name |
| `icons_url` | Yes | URL to icon set JS |
| `skin_url` | Yes | URL to editor skin |
| `inline` | No | Whether editor is inline |
| `menubar` | No | Show/hide menu bar |
| `statusbar` | No | Show/hide status bar |
| `toolbar_sticky` | No | Sticky toolbar on scroll |
| `toolbar_sticky_offset` | No | Offset for sticky toolbar |
| `toolbar_location` | No | Toolbar position |
| `content_css` | No | Custom CSS for editor content |
| `content_style` | No | Inline CSS for editor content |
| `min_width` | No | Minimum editor width |
| `unsupportedHtml` | No | Allow unsupported HTML |
| `selector` | No | CSS selector for the editor |
| `labels` | No | Custom labels (key-value object) |
| `advlist_bullet_styles` | No | Bullet list style options |
| `advlist_number_styles` | No | Numbered list style options |
### Example: Filter with RTE Configuration
```json
{
"id": "section",
"components": ["text", "image", "hero"],
"rte": {
"toolbar": {
"format": ["bold", "italic", "underline"],
"blocks": ["h2", "h3", "h4", "paragraph"],
"list": ["bullet_list", "ordered_list"],
"insert": ["link", "image"]
},
"plugins": "link lists image",
"icons": "thin",
"icons_url": "/icons/thin/icons.js",
"skin_url": "/skins/oxide"
}
}
```
> **Schema reference:** [filter-definition-rte.schema.json](https://universal-editor-service.adobe.io/schemas/filter-definition-rte.schema.json)
references/examples.md
# Real Examples from the Codebase
## Table of Contents
1. [Simple Block: Hero](#simple-block-hero)
2. [Simple Block: Embed](#simple-block-embed)
3. [Container Block: Cards](#container-block-cards)
4. [Block with Variants: Teaser](#block-with-variants-teaser)
5. [Key-Value Block: Product Details](#key-value-block-product-details)
6. [Block with Content Fragment: Article](#block-with-content-fragment-article)
7. [Section Configuration](#section-configuration)
8. [Block with Textarea: Metadata](#block-with-textarea-metadata)
9. [Block with Checkbox Group: Feature Toggles](#block-with-checkbox-group-feature-toggles)
10. [Filter with RTE Configuration](#filter-with-rte-configuration)
---
## Simple Block: Hero
A basic block with an image, alt text, and rich text content.
### _hero.json (distributed config)
```json
{
"definitions": [
{
"title": "Hero",
"id": "hero",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Hero",
"model": "hero"
}
}
}
}
}
],
"models": [
{
"id": "hero",
"fields": [
{
"component": "reference",
"valueType": "string",
"name": "image",
"label": "Image",
"multi": false
},
{
"component": "text",
"valueType": "string",
"name": "imageAlt",
"label": "Alt",
"value": ""
},
{
"component": "richtext",
"name": "text",
"value": "",
"label": "Text",
"valueType": "string"
}
]
}
],
"filters": []
}
```
**Key patterns**:
- `image` + `imageAlt` pair: The pipeline collapses these into `<picture><img alt="...">`
- `richtext` for flexible content with formatting
---
## Simple Block: Embed
A block that embeds external content (YouTube, Vimeo, Twitter).
### component-definition.json entry
```json
{
"title": "Embed",
"id": "embed",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Embed",
"model": "embed"
}
}
}
}
}
```
### component-models.json entry
```json
{
"id": "embed",
"fields": [
{
"component": "aem-content",
"valueType": "string",
"name": "url",
"label": "URL"
},
{
"component": "reference",
"valueType": "string",
"name": "image",
"label": "Placeholder Image",
"multi": false
},
{
"component": "text",
"valueType": "string",
"name": "imageAlt",
"label": "Placeholder Image Alt Text",
"value": ""
}
]
}
```
### component-filters.json entry
The embed block appears in the `section` filter:
```json
{
"id": "section",
"components": ["text", "image", "button", "title", "hero", "...", "embed"]
}
```
**Key patterns**:
- `aem-content` for URL field (allows AEM content paths and external URLs)
- Optional `image` + `imageAlt` for placeholder thumbnail
---
## Container Block: Cards
A block with repeatable child items.
### _cards.json
```json
{
"definitions": [
{
"title": "Cards",
"id": "cards",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Cards",
"filter": "cards"
}
}
}
}
},
{
"title": "Card",
"id": "card",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block/item",
"template": {
"name": "Card",
"model": "card"
}
}
}
}
}
],
"models": [
{
"id": "card",
"fields": [
{
"component": "reference",
"valueType": "string",
"name": "image",
"label": "Image",
"multi": false
},
{
"component": "richtext",
"name": "text",
"value": "",
"label": "Text",
"valueType": "string"
}
]
}
],
"filters": [
{
"id": "cards",
"components": ["card"]
}
]
}
```
**Key patterns**:
- Container (Cards) has `filter` in template, NO `model`
- Item (Card) has `model` in template, uses `block/item` resourceType
- Filter restricts container to only accept card items
- Both container and item need separate definition entries
---
## Block with Variants: Teaser
A complex block with style variants via multiselect, multiple fields with defaults.
### Key component-definition.json entry
```json
{
"title": "Teaser",
"id": "teaser",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Teaser",
"model": "teaser",
"title": "Teaser Title",
"titleType": "h3",
"longDescr": "",
"classes": ["light", "left"]
}
}
}
}
}
```
### Key model fields (abbreviated)
```json
{
"id": "teaser",
"fields": [
{
"component": "reference",
"name": "fileReference",
"label": "Image",
"multi": false
},
{
"component": "multiselect",
"name": "classes",
"label": "Style",
"required": true,
"maxSize": 3,
"options": [
{
"name": "Theme",
"children": [
{ "name": "Light", "value": "light" },
{ "name": "Dark", "value": "dark" }
]
},
{
"name": "Alignment",
"children": [
{ "name": "Left", "value": "left" },
{ "name": "Right", "value": "right" }
]
}
]
},
{
"component": "select",
"name": "titleType",
"value": "h3",
"label": "Heading Type",
"options": [
{ "name": "H1", "value": "h1" },
{ "name": "H2", "value": "h2" },
{ "name": "H3", "value": "h3" }
]
}
]
}
```
**Key patterns**:
- `classes` field with grouped multiselect → CSS classes on the block div
- Template pre-populates default values (`classes: ["light", "left"]`)
- `titleType` suffix → heading level in rendered HTML
---
## Key-Value Block: Product Details
Renders as a 2-column key-value table rather than a grid.
### component-definition.json entry
```json
{
"title": "Product Details",
"id": "product-details",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/block/v1/block",
"template": {
"name": "Product Details",
"model": "product-details",
"key-value": true
}
}
}
}
}
```
**Key pattern**: `"key-value": true` in the template changes how the block renders in the pipeline (2-column format: property name | value).
---
## Block with Content Fragment: Article
Uses a Content Fragment reference.
### component-models.json entry
```json
{
"id": "article",
"fields": [
{
"component": "aem-content-fragment",
"name": "articlepath",
"value": "",
"label": "Article Content Fragment path",
"valueType": "string"
},
{
"component": "text",
"name": "variation",
"value": "",
"label": "Variation",
"valueType": "string"
}
]
}
```
**Key pattern**: `aem-content-fragment` component for selecting Content Fragments from the DAM.
---
## Section Configuration
Sections use the same model/filter system but with a different resource type.
### component-definition.json
```json
{
"title": "Section",
"id": "section",
"plugins": {
"xwalk": {
"page": {
"resourceType": "core/franklin/components/section/v1/section",
"template": {
"model": "section"
}
}
}
}
}
```
### component-models.json
```json
{
"id": "section",
"fields": [
{
"component": "text",
"name": "name",
"label": "Section Name",
"description": "The label shown for this section in the Content Tree"
},
{
"component": "multiselect",
"name": "style",
"label": "Style",
"options": [
{ "name": "Highlight", "value": "highlight" },
{ "name": "Background Color", "value": "background" }
]
}
]
}
```
### component-filters.json
```json
{
"id": "section",
"components": [
"text", "image", "button", "title", "hero", "article", "cards",
"columns", "teaser", "fragment", "offer", "storelocator", "blog-list",
"product-recommendations", "product-teaser", "product-list-page",
"product-details", "video", "form", "embed-adaptive-form",
"reward", "reward-sticky", "quiz", "embed"
]
}
```
The `section` filter is the main gatekeeper: any block that should be available to authors **must** be listed here.
---
## Block with Textarea: Metadata
A block using `textarea` for multi-line description input.
### component-models.json entry
```json
{
"id": "metadata",
"fields": [
{
"component": "text",
"name": "title",
"label": "Page Title",
"valueType": "string"
},
{
"component": "textarea",
"name": "description",
"label": "Page Description",
"valueType": "string"
},
{
"component": "aem-tag",
"name": "tags",
"label": "Tags",
"valueType": "string"
}
]
}
```
**Key patterns**:
- `textarea` for multi-line plain text (no formatting needed)
- `aem-tag` for content categorization
---
## Block with Checkbox Group: Feature Toggles
A block using `checkbox-group` for multiple independent boolean options.
### component-models.json entry
```json
{
"id": "banner",
"fields": [
{
"component": "richtext",
"name": "text",
"label": "Banner Text",
"valueType": "string"
},
{
"component": "checkbox-group",
"name": "features",
"label": "Display Options",
"valueType": "string[]",
"options": [
{ "name": "Show Close Button", "value": "show-close" },
{ "name": "Show Icon", "value": "show-icon" },
{ "name": "Sticky Position", "value": "sticky" }
]
}
]
}
```
**Key patterns**:
- `checkbox-group` with `valueType: "string[]"` for multiple independent toggles
- Each option is independently selectable (unlike `radio-group` which is mutually exclusive)
---
## Filter with RTE Configuration
A filter that customizes the richtext editor toolbar available to authors.
### component-filters.json entry
```json
{
"id": "section",
"components": ["text", "image", "button", "title", "hero", "cards", "embed"],
"rte": {
"toolbar": {
"format": ["bold", "italic", "underline"],
"blocks": ["h2", "h3", "h4", "paragraph"],
"list": ["bullet_list", "ordered_list"],
"insert": ["link"]
},
"plugins": "link lists",
"icons": "thin",
"icons_url": "/icons/thin/icons.js",
"skin_url": "/skins/oxide"
}
}
```
**Key patterns**:
- `rte` property on a filter entry configures the richtext editor toolbar
- `toolbar` groups actions into categories (format, blocks, list, insert)
- `plugins` is a space-separated list of enabled editor plugins
- `icons`, `icons_url`, `skin_url` are required for the RTE to render
references/field-types.md
# Component Model Field Types Reference
> **Canonical source:** These field types are defined by the Universal Editor JSON schemas:
> - [model-definition-fields.schema.json](https://universal-editor-service.adobe.io/schemas/model-definition-fields.schema.json)
> - [model-definition.schema.json](https://universal-editor-service.adobe.io/schemas/model-definition.schema.json)
## Field Structure
Every field in a component model follows this base structure:
```json
{
"component": "<field-type>",
"name": "<property-name>",
"label": "<Display Label>",
"valueType": "<data-type>",
"value": "<default-value>",
"description": "<helper-text>"
}
```
**Required properties for all fields:** `component`, `label`, `name`.
Some component types require additional properties — see the per-component sections and the [Required Properties](#required-properties-by-component) table.
---
## Component Types
### Text Input Fields
#### `text`
Single-line text input. Used for short strings like titles, labels, alt text.
- **Enforced valueType:** `"string"`
- **Validation:** `minLength`, `maxLength`, `regExp`, `customErrorMsg`
```json
{
"component": "text",
"name": "title",
"label": "Title",
"valueType": "string"
}
```
#### `textarea`
Multi-line text input without rich formatting. Use for descriptions, notes, or longer plain text content. Distinct from `richtext` which includes formatting controls.
- **Enforced valueType:** `"string"`
```json
{
"component": "textarea",
"name": "description",
"label": "Description",
"valueType": "string"
}
```
#### `richtext`
Rich text editor with formatting controls (bold, italic, lists, links). Toolbar can be customized via RTE filter configuration (see architecture.md).
- **Enforced valueType:** `"string"`
```json
{
"component": "richtext",
"name": "text",
"value": "",
"label": "Text",
"valueType": "string"
}
```
### Media & Content Fields
#### `reference`
AEM asset picker. Opens DAM browser for selecting images, videos, documents. Unlike `aem-content`, this can only reference assets.
- **Enforced valueType:** `"string"`
```json
{
"component": "reference",
"name": "image",
"label": "Image",
"valueType": "string",
"multi": false
}
```
- Set `multi: true` for multiple asset selection
- Typically paired with a `text` field for alt text (e.g., `imageAlt`)
#### `aem-content`
AEM content picker. Can select any AEM resource — pages, URLs, content paths. Unlike `reference` which is limited to assets.
- **Flexible valueType** (any from enum, defaults to `"string"`)
- **Validation:** `rootPath` — limits content picker to a specific directory
```json
{
"component": "aem-content",
"name": "link",
"label": "Link",
"valueType": "string"
}
```
#### `aem-content-fragment`
Content Fragment picker. Selects AEM Content Fragments — structured content reusable across channels.
- **Flexible valueType** (any from enum, defaults to `"string"`)
```json
{
"component": "aem-content-fragment",
"name": "articlepath",
"value": "",
"label": "Article Content Fragment path",
"valueType": "string"
}
```
#### `aem-experience-fragment`
Experience Fragment picker. Selects AEM Experience Fragments — groups of one or more components including content and layout that can be referenced and reused across pages.
- **Flexible valueType** (any from enum, defaults to `"string"`)
```json
{
"component": "aem-experience-fragment",
"name": "fragment",
"label": "Experience Fragment",
"valueType": "string"
}
```
#### `aem-tag`
AEM tag picker for content categorization and organization.
- **Enforced valueType:** `"string"`
```json
{
"component": "aem-tag",
"name": "tags",
"label": "Tags",
"valueType": "string"
}
```
### Selection Fields
#### `select`
Single-choice dropdown. Requires `options` array.
- **Enforced valueType:** `"string"`
- **Required properties:** `options`
```json
{
"component": "select",
"name": "titleType",
"label": "Title Type",
"valueType": "string",
"value": "h2",
"options": [
{ "name": "H1", "value": "h1" },
{ "name": "H2", "value": "h2" },
{ "name": "H3", "value": "h3" }
]
}
```
#### `multiselect`
Multiple-choice selector. Supports grouped options via `children`. Requires `options` array.
- **Enforced valueType:** `"string"` (not `"string[]"` — this is enforced by the schema)
- **Required properties:** `options`
```json
{
"component": "multiselect",
"name": "style",
"label": "Style",
"valueType": "string",
"options": [
{ "name": "Highlight", "value": "highlight" },
{ "name": "Dark", "value": "dark" }
]
}
```
**Grouped options** (with `children`):
```json
{
"component": "multiselect",
"name": "classes",
"label": "Style",
"valueType": "string",
"maxSize": 3,
"options": [
{
"name": "Theme",
"children": [
{ "name": "Light", "value": "light" },
{ "name": "Dark", "value": "dark" }
]
},
{
"name": "Alignment",
"children": [
{ "name": "Left", "value": "left" },
{ "name": "Right", "value": "right" }
]
}
]
}
```
#### `checkbox-group`
Multiple true/false checkbox items. Users can select multiple options simultaneously. Requires `options` array.
- **Enforced valueType:** `"string[]"`
- **Required properties:** `options`
```json
{
"component": "checkbox-group",
"name": "features",
"label": "Features",
"valueType": "string[]",
"options": [
{ "name": "Show Title", "value": "show-title" },
{ "name": "Show Image", "value": "show-image" },
{ "name": "Show CTA", "value": "show-cta" }
]
}
```
#### `radio-group`
Radio button group for mutually exclusive choices. Requires `options` array.
- **Enforced valueType:** `"string"`
- **Required properties:** `options`
```json
{
"component": "radio-group",
"name": "orientation",
"label": "Display Options",
"valueType": "string",
"value": "horizontal",
"options": [
{ "name": "Horizontally", "value": "horizontal" },
{ "name": "Vertically", "value": "vertical" }
]
}
```
### Data Fields
#### `boolean`
Toggle for true/false values.
- **Enforced valueType:** `"boolean"`
- **Validation:** `customErrorMsg`
```json
{
"component": "boolean",
"name": "hideHeading",
"label": "Hide Heading",
"description": "Hide the heading of the block",
"valueType": "boolean",
"value": false
}
```
#### `number`
Numeric input with optional min/max constraints.
- **Enforced valueType:** `"number"`
- **Validation:** `numberMin`, `numberMax`, `customErrorMsg`
```json
{
"component": "number",
"name": "maxItems",
"label": "Max Items",
"valueType": "number",
"description": "Maximum number of items to display"
}
```
#### `date-time`
Date/time picker.
- **Enforced valueType:** `"date"`
```json
{
"component": "date-time",
"name": "startDate",
"label": "Start Date",
"valueType": "date"
}
```
### Structural Fields
#### `container`
Groups nested fields together. Used for composite fields or repeated field groups.
- **Flexible valueType** (any from enum)
```json
{
"component": "container",
"name": "ctas",
"label": "Call to Actions",
"collapsible": false,
"multi": true,
"fields": [
{ "component": "richtext", "name": "text", "label": "Text", "valueType": "string" },
{ "component": "aem-content", "name": "link", "label": "Link" }
]
}
```
- `collapsible`: Whether the group can be collapsed in the UI
- `multi: true`: Makes the container repeatable (add/remove instances). Container nesting is not permitted for multi-fields.
- `fields`: Array of nested field definitions
#### `tab`
Creates a tab separator in the properties panel. Not a data field — purely UI organization. Everything after a `tab` is placed on that tab until a new `tab` is encountered.
- **Flexible valueType** (any from enum)
```json
{
"component": "tab",
"label": "Validation",
"name": "validation"
}
```
---
## valueType Constraints
The `valueType` property controls data validation. Most components **enforce** a specific value (marked as `const` in the schema). Only a few allow any value from the enum.
| Component | Enforced valueType | Flexibility |
|---|---|---|
| `text` | `"string"` | Enforced |
| `textarea` | `"string"` | Enforced |
| `richtext` | `"string"` | Enforced |
| `reference` | `"string"` | Enforced |
| `select` | `"string"` | Enforced |
| `multiselect` | `"string"` | Enforced |
| `radio-group` | `"string"` | Enforced |
| `checkbox-group` | `"string[]"` | Enforced |
| `boolean` | `"boolean"` | Enforced |
| `number` | `"number"` | Enforced |
| `date-time` | `"date"` | Enforced |
| `aem-tag` | `"string"` | Enforced |
| `aem-content` | Any | Flexible |
| `aem-content-fragment` | Any | Flexible |
| `aem-experience-fragment` | Any | Flexible |
| `container` | Any | Flexible |
| `tab` | Any | Flexible |
**Valid valueType enum values:** `"string"`, `"string[]"`, `"number"`, `"date"`, `"boolean"`
---
## Required Properties by Component
All fields require `component`, `label`, and `name`. Some require additional properties:
| Component | Additional required properties |
|---|---|
| `select` | `options` |
| `multiselect` | `options` |
| `radio-group` | `options` |
| `checkbox-group` | `options` |
| All others | — |
---
## Field Properties
| Property | Type | Description |
|----------|------|-------------|
| `component` | string | **Required.** Field type |
| `name` | string | **Required.** Property name for data persistence. Underscore (`_`) not allowed with aem/xwalk plugins. Can be a nested path (e.g., `teaser/image/fileReference`). |
| `label` | string | **Required.** Display label in the property panel |
| `description` | string | Helper text shown below the field |
| `valueType` | string | Data type for the value (see constraints table above) |
| `value` | any | Default value. The UE will persist this if no value is set. |
| `multi` | boolean | Allow multiple values. Container nesting not permitted for multi-fields. |
| `required` | boolean | Whether the field must have a value before saving |
| `readOnly` | boolean | Field cannot be edited by authors |
| `hidden` | boolean | Field is hidden from the properties panel |
| `options` | array | Choices for select/multiselect/radio-group/checkbox-group. **Required** for those types. |
| `condition` | object | JSON Logic rule for showing/hiding the field dynamically |
| `validation` | object | Validation rules — component-specific (see below) |
| `maxSize` | number | Max selections for multiselect |
| `collapsible` | boolean | For containers: whether they can collapse |
| `fields` | array | For containers: nested field definitions |
| `raw` | any | Additional metadata passed to the component |
---
## Validation
Validation rules are component-specific. Each component type supports different constraints:
### TextValidation (for `text` only)
| Property | Type | Description |
|----------|------|-------------|
| `minLength` | number | Minimum characters allowed |
| `maxLength` | number | Maximum characters allowed |
| `regExp` | string | Regular expression the input must match |
| `customErrorMsg` | string | Custom error message on validation failure |
```json
{
"component": "text",
"name": "email",
"label": "Email",
"valueType": "string",
"validation": {
"regExp": "^[^@]+@[^@]+\\.[^@]+$",
"customErrorMsg": "Please enter a valid email address"
}
}
```
### NumberValidation (for `number` only)
| Property | Type | Description |
|----------|------|-------------|
| `numberMin` | number | Minimum value allowed |
| `numberMax` | number | Maximum value allowed |
| `customErrorMsg` | string | Custom error message on validation failure |
### BooleanValidation (for `boolean` only)
| Property | Type | Description |
|----------|------|-------------|
| `customErrorMsg` | string | Custom error message for invalid boolean values |
### AEMContentValidation (for `aem-content` only)
| Property | Type | Description |
|----------|------|-------------|
| `rootPath` | string | Limits content picker to this directory and subdirectories |
```json
{
"component": "aem-content",
"name": "link",
"label": "Link",
"valueType": "string",
"validation": {
"rootPath": "/content/mysite"
}
}
```
---
## Conditional Fields
Use JSON Logic syntax to show/hide fields based on other field values:
```json
{
"component": "text",
"name": "customUrl",
"label": "Custom URL",
"valueType": "string",
"condition": {
"==": [
{ "var": "linkType" },
"custom"
]
}
}
```
Operators: `==`, `===`, `!=`, `!==`, and standard JSON Logic operators.
---
## Options Format
### Flat options
```json
"options": [
{ "name": "Display Name", "value": "stored-value" }
]
```
Each option requires `name` (display text) and `value` (persisted value).
### Grouped options (multiselect only)
```json
"options": [
{
"name": "Group Label",
"children": [
{ "name": "Option A", "value": "a" },
{ "name": "Option B", "value": "b" }
]
}
]
```
### With empty/default option
```json
"options": [
{ "name": "default", "value": "" },
{ "name": "primary", "value": "primary" },
{ "name": "secondary", "value": "secondary" }
]
```