npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

aem-dialog-generator-plugin

v2.0.6

Published

Webpack plugin that automatically generates AEM component dialog XML, design dialogs, and policies from JSON configurations

Downloads

78

Readme

AEM Dialog Generator Plugin

A Webpack plugin that automatically generates AEM component _cq_dialog.xml and _cq_design_dialog.xml files with associated policies from simple JSON configurations. Features intelligent automation including automatic style tab generation, policy-to-template mapping, and dynamic indentation for production-ready AEM components.

npm version License: MIT Test Coverage

Features

27 Field Types Supported - textfield, textarea, select, pathfield, pagefield, checkbox, multifield, RTE, fieldset, container, heading, text/alert, tags, image, autocomplete, radiogroup, contentfragmentpicker, experiencefragmentpicker, assetpicker, hidden, button, well, and more
🎨 Flexible Layouts - Tabs, simple layouts, accordion, or fieldsets for organization
🔄 Auto-generation - XML files generated on every webpack build
📝 Simple JSON - Easy-to-read configuration instead of verbose XML
🎯 Multifield Support - Both simple and composite multifields
📋 Rich Text Editor - Full RTE configuration with customizable features
🗂️ Folder Structure - Supports both _cq_dialog/.content.xml and _cq_dialog.xml formats 🎭 Show/Hide - Dynamic field visibility based on dropdown or checkbox values 📷 Image Upload - Built-in support for DAM integration and file uploads 📝 Page Selection - Native AEM page picker with content tree navigation 🧩 Content Fragments - Full support for Content Fragment and Experience Fragment pickers ✅ Validation - Regex patterns, min/max values, required fields, maxLength 💡 Contextual Help - Inline help tooltips and documentation links 🎨 Custom Styling - CSS classes, field width control, and Coral UI spacing 📝 Field Enhancements - Default values, descriptions, placeholder/emptyText 🎯 Enterprise Features - Custom IDs (granite:id), analytics tracking, render hidden, collapsible sections 🔍 Advanced Pickers - Filter support, freshness control for DAM assets 🔄 Multifield Pro - Delete confirmation, drag & drop reordering 🎯 Tracking - trackingElement para analytics granular (además de trackingFeature) 🧭 UX - autoFocus para enfocar campos al abrir el diálogo 🧱 Estilos - wrapperClass para clases del contenedor 🧪 Tipos - typeHint para guardar con el tipo correcto en JCR 🎨 Design Dialogs - Generate design dialogs (_cq_design_dialog) for component policies 📋 Policy Generation - Automatically create component policies with RTE config, styles, and more 🎨 Style System - Define style groups and variants for the AEM Style System 📐 Component Mapping - Configure asset-to-component drag & drop mappings 🔄 Dynamic Indentation - Consistent XML indentation across all generated content ⚡ Auto Style Tab - Automatic cq:styles tab generation when styleGroups are present 🔗 Template Integration - Automatic policy-to-template mapping for seamless deployment

Installation

npm install aem-dialog-generator-plugin --save-dev

Quick Start

1. Configure webpack

const AemDialogGeneratorPlugin = require('aem-dialog-generator-plugin');
const path = require('path');

module.exports = {
  plugins: [
    new AemDialogGeneratorPlugin({
      sourceDir: path.resolve(__dirname, 'src/main/webpack/components'),
      targetDir: path.resolve(__dirname, '../ui.apps/src/main/content/jcr_root/apps/mysite/components'),
      appName: 'mysite',
      generatePolicies: true,
      policiesTargetDir: path.resolve(__dirname, '../ui.content/src/main/content/jcr_root/conf/mysite/settings/wcm/policies'),
      templatePoliciesDir: path.resolve(__dirname, '../ui.content/src/main/content/jcr_root/conf/mysite/settings/wcm/templates'),
      autoMapPoliciesToTemplates: true
    })
  ]
};

2. Create a dialog.json file

Create src/main/webpack/components/button/dialog.json:

{
  "title": "Button Component",
  "tabs": [
    {
      "title": "Properties",
      "fields": [
        {
          "type": "textfield",
          "name": "./text",
          "label": "Button Text",
          "required": true
        },
        {
          "type": "pathfield",
          "name": "./link",
          "label": "Link",
          "rootPath": "/content"
        },
        {
          "type": "select",
          "name": "./style",
          "label": "Button Style",
          "options": [
            { "value": "primary", "text": "Primary" },
            { "value": "secondary", "text": "Secondary" }
          ]
        }
      ]
    }
  ]
}

3. Build

npm run dev

The plugin automatically generates:

ui.apps/src/main/content/jcr_root/apps/mysite/components/button/_cq_dialog/.content.xml

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | sourceDir | String | Required | Folder containing component dialog.json files | | targetDir | String | Required | Target folder for generated XML files | | dialogFileName | String | dialog.json | Name of the JSON configuration file | | designDialogFileName | String | designDialog.json | Name of the design dialog JSON configuration file | | appName | String | mysite | AEM application name | | useFolderStructure | Boolean | true | Use _cq_dialog/.content.xml (true) or _cq_dialog.xml (false) | | verbose | Boolean | false | Enable detailed logging | | generatePolicies | Boolean | true | Enable automatic policy generation from designDialog.json | | policiesTargetDir | String | ../ui.content/.../policies | Target folder for generated policy XML files | | templatePoliciesDir | String | ../ui.content/.../templates | Target folder for template policy mapping | | autoMapPoliciesToTemplates | Boolean | true | Automatically map policies to specified templates |

Supported Field Types

Basic Fields

textfield

{
  "type": "textfield",
  "name": "./title",
  "label": "Title",
  "description": "Enter title",
  "required": true,
  "defaultValue": "Default text"
}

New Properties Quick Guide

typeHint (Sling/JCR)

Force the JCR data type saved by Sling. Useful for numbers, booleans, or arrays.

Examples:

{ "type": "textfield", "name": "./views", "label": "Views", "typeHint": "Long" }
{ "type": "select", "name": "./tags", "label": "Tags", "multiple": true, "typeHint": "String[]", "options": [{"value":"a","text":"A"}] }

wrapperClass (field container)

Add CSS classes to the field container; merged with other Granite classes.

{ "type": "textfield", "name": "./title", "label": "Title", "className": "input-sm", "wrapperClass": "wrap-a wrap-b" }

autoFocus (better UX)

Focus the field when the dialog opens.

{ "type": "textfield", "name": "./search", "label": "Search", "autoFocus": true }

trackingElement (granular analytics)

Complement trackingFeature to identify a specific element interaction.

{ "type": "textfield", "name": "./ctaText", "label": "CTA Text", "trackingFeature": "hero", "trackingElement": "cta" }

What's New in v2.0.0 🎉

Version 2.0.0 introduces powerful automation features that significantly reduce manual configuration and improve developer productivity:

🤖 Intelligent Automation

  • Auto Style Tab: Automatically generates cq:styles tab when policies contain styleGroups
  • Template Mapping: One-click policy deployment to multiple templates
  • Dynamic Indentation: Perfect XML formatting regardless of complexity

🏗️ Enhanced Architecture

  • Hierarchical Policies: Proper AEM policy directory structure (/conf/{app}/settings/wcm/policies/{app}/components/)
  • Consistent XML: Unified XML generation using buildNode() for reliability
  • Smart Detection: Automatically detects when style system integration is needed

🚀 Developer Experience

  • Zero Configuration: Default settings work out of the box
  • Automatic Deployment: Policies are instantly mapped to templates
  • Production Ready: Generated XML follows all AEM best practices

📊 Comprehensive Testing

  • 239 Tests: Complete test coverage including all new automation features
  • Regression Protection: Ensures backward compatibility with existing configurations
  • Edge Case Handling: Robust validation and error handling

Design Dialogs & Component Policies

The plugin now supports generating Design Dialogs (_cq_design_dialog) and their associated component policies. This simplifies the complex task of configuring component policies in AEM.

Creating a Design Dialog

Create a designDialog.json file alongside your dialog.json:

Example: src/main/webpack/components/button/designDialog.json

{
  "title": "Button Design",
  "layout": "simple",
  "fields": [
    {
      "type": "checkbox",
      "name": "./enableVariants",
      "label": "Enable Button Variants",
      "description": "Allow authors to choose between different button styles"
    },
    {
      "type": "checkbox",
      "name": "./enableSizes",
      "label": "Enable Size Options"
    }
  ],
  "policy": {
    "name": "policy_button",
    "title": "Button Policy",
    "description": "Policy configuration for button component",
    "properties": {
      "allowedVariants": "[primary,secondary,outline]",
      "enableAnimation": "{Boolean}true"
    },
    "styleGroups": [
      {
        "name": "variants",
        "label": "Button Variants",
        "styles": [
          {
            "name": "primary",
            "label": "Primary",
            "classes": "cmp-button--primary"
          },
          {
            "name": "secondary",
            "label": "Secondary",
            "classes": "cmp-button--secondary"
          }
        ]
      }
    ]
  }
}

Policy Configuration

The policy object in your designDialog.json supports:

Basic Policy Properties

{
  "policy": {
    "name": "policy_mycomponent",
    "title": "My Component Policy",
    "description": "Policy description shown in template editor",
    "properties": {
      "customProperty": "value",
      "enableFeature": "{Boolean}true",
      "maxItems": "{Long}5"
    }
  }
}

Style System Configuration

Define style groups for the AEM Style System:

{
  "policy": {
    "styleGroups": [
      {
        "name": "layout",
        "label": "Layout Options",
        "styles": [
          {
            "name": "grid",
            "label": "Grid Layout",
            "classes": "cmp-container--grid",
            "icon": "viewGrid"
          },
          {
            "name": "flex",
            "label": "Flex Layout",
            "classes": "cmp-container--flex"
          }
        ]
      }
    ]
  }
}

RTE Plugin Configuration

Configure Rich Text Editor plugins in policies:

{
  "policy": {
    "rtePlugins": {
      "format": {
        "features": "bold,italic,underline"
      },
      "paraformat": {
        "features": "*",
        "formats": [
          { "description": "Heading 1", "tag": "h1" },
          { "description": "Heading 2", "tag": "h2" },
          { "description": "Paragraph", "tag": "p" }
        ]
      },
      "links": {
        "features": "modifylink,unlink"
      },
      "lists": {
        "features": "*"
      },
      "justify": {
        "features": "-"
      },
      "table": {
        "features": "-"
      }
    }
  }
}

Component Mapping

Configure asset-to-component drag & drop mappings:

{
  "policy": {
    "componentMapping": [
      {
        "assetGroup": "media",
        "assetMimetype": "image/*",
        "droptarget": "image",
        "resourceType": "mysite/components/image"
      },
      {
        "assetGroup": "content",
        "assetMimetype": "text/html",
        "droptarget": "experiencefragment",
        "resourceType": "mysite/components/experiencefragment"
      }
    ]
  }
}

Complete Design Dialog Example

Hero Component with Full Policy Configuration:

{
  "title": "Hero Design",
  "layout": "tabs",
  "tabs": [
    {
      "title": "Layout Options",
      "fields": [
        {
          "type": "select",
          "name": "./defaultLayout",
          "label": "Default Layout",
          "defaultValue": "centered",
          "options": [
            { "text": "Centered", "value": "centered" },
            { "text": "Left Aligned", "value": "left" },
            { "text": "Right Aligned", "value": "right" }
          ]
        },
        {
          "type": "checkbox",
          "name": "./allowBackgroundImage",
          "label": "Allow Background Image"
        }
      ]
    }
  ],
  "policy": {
    "name": "policy_hero",
    "title": "Hero Component Policy",
    "description": "Comprehensive policy for hero component",
    "properties": {
      "allowedLayouts": "[centered,left,right]",
      "minHeight": "{Long}400",
      "maxHeight": "{Long}800"
    },
    "rtePlugins": {
      "format": {
        "features": "bold,italic"
      },
      "paraformat": {
        "features": "*",
        "formats": [
          { "description": "Heading 1", "tag": "h1" },
          { "description": "Paragraph", "tag": "p" }
        ]
      },
      "links": {
        "features": "modifylink,unlink"
      }
    },
    "styleGroups": [
      {
        "name": "layouts",
        "label": "Hero Layouts",
        "styles": [
          {
            "name": "centered",
            "label": "Centered",
            "classes": "cmp-hero--centered"
          },
          {
            "name": "left",
            "label": "Left Aligned",
            "classes": "cmp-hero--left"
          }
        ]
      },
      {
        "name": "themes",
        "label": "Color Themes",
        "styles": [
          {
            "name": "light",
            "label": "Light Theme",
            "classes": "cmp-hero--light"
          },
          {
            "name": "dark",
            "label": "Dark Theme",
            "classes": "cmp-hero--dark"
          }
        ]
      }
    ]
  }
}

Generated Output

When you build, the plugin generates:

  1. Design Dialog XML: ui.apps/.../button/_cq_design_dialog/.content.xml
  2. Policy XML: ui.content/.../policies/button/.content.xml
  3. Template Mapping: Automatically maps policies to specified templates (when autoMapPoliciesToTemplates: true)

The policy XML is structured according to AEM standards and can be referenced in your page templates.

Automatic Features

The plugin now includes several automatic features to streamline AEM component development:

🎨 Automatic cq:styles Tab Generation

When a policy includes styleGroups, the plugin automatically generates a cq:styles tab in the design dialog:

{
  "policy": {
    "styleGroups": [
      {
        "name": "variants",
        "label": "Button Variants",
        "styles": [...]
      }
    ]
  }
}

This automatically adds the AEM Style System tab to your design dialog without manual configuration.

🔗 Automatic Policy-to-Template Mapping

Configure which templates should use a component policy by adding a templates array to your policy:

{
  "policy": {
    "name": "policy_section_container",
    "title": "Section Container Policy",
    "description": "Policy for section component",
    "templates": ["page-content", "landing-page"]
  }
}

The plugin will automatically update the template policy files to include the mapping:

<section
    cq:policy="mysite/components/section/policy_section_container"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="wcm/core/components/policies/mapping"/>

📐 Dynamic Indentation System

All generated XML uses a consistent, dynamic indentation system that ensures properly formatted output regardless of nesting level or complexity.

📁 Correct Policy Structure

Policies are automatically generated in the proper AEM directory structure:

/conf/{appName}/settings/wcm/policies/{appName}/components/{componentName}/.content.xml

This follows AEM best practices and ensures policies are properly organized.

textarea

{
  "type": "textarea",
  "name": "./description",
  "label": "Description",
  "rows": 5
}

pathfield

{
  "type": "pathfield",
  "name": "./link",
  "label": "Link",
  "rootPath": "/content"
}

select

{
  "type": "select",
  "name": "./type",
  "label": "Type",
  "options": [
    { "value": "type1", "text": "Type 1" },
    { "value": "type2", "text": "Type 2" }
  ]
}

Advanced Properties

  • Show/Hide by expression: use showIf or hideIf to emit granite:hide and control field visibility.
  • Select datasource: add datasource (child node), emptyOption, forceSelection.
  • Validation messages: override built-ins with requiredMessage, minMessage, maxMessage, patternMessage.
  • Order fields: place with orderBefore (emits sling:orderBefore).
  • Granite data: set data: { key: value }granite:data-key="value" on the field.
  • Render conditions: renderCondition supports simple, privilege, and, or with nested conditions.
  • Multifield UX: addItemLabel, maxItemsMessage, minItemsMessage, reorderableHandle.
  • QoL inputs: clearButton (textfield), autocomplete, ariaLabel, ariaDescribedBy, tooltipIcon.

Examples:

{
  "type": "textfield",
  "name": "./videoUrl",
  "label": "Video URL",
  "showIf": { "field": "./contentType", "value": "video" },
  "required": true,
  "requiredMessage": "Required for Video",
  "ariaLabel": "Video URL"
}
{
  "type": "select",
  "name": "./category",
  "label": "Category",
  "emptyOption": true,
  "forceSelection": true,
  "datasource": "/apps/mysite/datasources/categories"
}
{
  "type": "textfield",
  "name": "./adminOnly",
  "label": "Admin Only",
  "renderCondition": {
    "type": "and",
    "conditions": [
      { "type": "simple", "expression": "${currentUser == 'admin'}" },
      { "type": "privilege", "privilege": "jcr:read" }
    ]
  }
}
{
  "type": "multifield",
  "name": "./items",
  "label": "Items",
  "addItemLabel": "Add Item",
  "maxItemsMessage": "Too many",
  "minItemsMessage": "Too few",
  "reorderableHandle": "drag",
  "fields": [{ "type": "textfield", "name": "./item", "label": "Item" }]
}

checkbox

{
  "type": "checkbox",
  "name": "./enabled",
  "label": "Enabled",
  "text": "Enable feature",
  "value": "true"
}

numberfield

{
  "type": "numberfield",
  "name": "./count",
  "label": "Count",
  "min": 0,
  "max": 100,
  "step": 1
}

datepicker

{
  "type": "datepicker",
  "name": "./date",
  "label": "Date"
}

colorfield

{
  "type": "colorfield",
  "name": "./color",
  "label": "Color"
}

switch

{
  "type": "switch",
  "name": "./active",
  "label": "Active",
  "checked": true
}

hidden

{
  "type": "hidden",
  "name": "./hiddenValue",
  "value": "hidden-value"
}

fileupload

{
  "type": "fileupload",
  "name": "./fileReference",
  "label": "File"
}

hidden

{
  "type": "hidden",
  "name": "./componentId",
  "defaultValue": "auto-generated-id"
}

Stores values in JCR without displaying them in the dialog. Perfect for:

  • Auto-generated IDs or timestamps
  • Technical flags or metadata
  • Calculated values set by JavaScript
  • Values that authors shouldn't modify

Note: Hidden fields have no label or visual representation.

button

{
  "type": "button",
  "text": "Generate Content",
  "variant": "primary",
  "icon": "magic",
  "command": "generateContent"
}

Adds clickable buttons to trigger actions:

| Property | Type | Description | Default | |----------|------|-------------|---------|
| text | String | Button label | "Button" | | variant | String | Style: primary, secondary, action, warning | primary | | icon | String | Coral UI icon name | - | | command | String | Command to execute | - | | handler | String | JavaScript handler file | - | | type | String | Button type attribute | - | | disabled | Boolean | Disable the button | false |

Use Cases:

  • Trigger content generation
  • Clear form fields
  • Preview content
  • Execute custom actions
  • Integration with client libraries

Advanced Fields

fieldset - Group Related Fields

{
  "type": "fieldset",
  "label": "SEO Settings",
  "fields": [
    {
      "type": "textfield",
      "name": "./metaTitle",
      "label": "Meta Title"
    },
    {
      "type": "textarea",
      "name": "./metaDescription",
      "label": "Meta Description"
    }
  ]
}

container - Generic Container for Grouping

{
  "type": "container",
  "fields": [
    {
      "type": "textfield",
      "name": "./option1",
      "label": "Option 1"
    },
    {
      "type": "textfield",
      "name": "./option2",
      "label": "Option 2"
    }
  ]
}

Differences:

  • fieldset: Form-specific grouping with a visible label (jcr:title). Requires label property.
  • container: Generic grouping element without visual label. The name property is optional (used only for node naming).

Note: Both support showhideClass for hiding entire groups of fields together.

fixedcolumns - Multi-Column Layout

{
  "type": "fixedcolumns",
  "columns": [
    {
      "fields": [
        { "type": "textfield", "name": "./firstName", "label": "First Name" },
        { "type": "textfield", "name": "./email", "label": "Email" }
      ]
    },
    {
      "fields": [
        { "type": "textfield", "name": "./lastName", "label": "Last Name" },
        { "type": "textfield", "name": "./phone", "label": "Phone" }
      ]
    }
  ]
}

Organizes fields into side-by-side columns for better space utilization. Perfect for forms with related fields.

Properties:

| Property | Type | Description | |----------|------|-------------| | columns | Array | Array of column objects, each containing a fields array | | name | String | Optional custom node name |

Column Properties:

  • name (String): Optional custom column name (default: column1, column2, etc.)
  • fields (Array): Array of field definitions for this column

Common Use Cases:

  • Name and contact info side by side
  • Address fields in multiple columns
  • Date ranges (From/To)
  • Compact form layouts

well - Visual Grouping Container

{
  "type": "well",
  "name": "advancedSettings",
  "fields": [
    { "type": "textfield", "name": "./cssClass", "label": "CSS Class" },
    { "type": "numberfield", "name": "./zIndex", "label": "Z-Index" },
    { "type": "checkbox", "name": "./customBehavior", "label": "Enable Custom Behavior" }
  ]
}

A well is a container with a subtle gray background that visually groups related fields.

Properties:

| Property | Type | Description | |----------|------|-------------| | fields | Array | Array of field definitions to display in the well | | name | String | Optional custom node name |

When to Use:

  • Highlighting optional or advanced settings
  • Visually separating field groups without tabs
  • Drawing attention to important configuration sections
  • Creating visual hierarchy within a tab

Visual Effect: Fields appear with a light gray background, creating a subtle "inset" appearance that groups them together.

heading - Section Heading

{
  "type": "heading",
  "text": "Advanced Settings",
  "level": 3
}

Creates a visual heading element to organize dialog sections. Does not store any data.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------| | text | String | The heading text to display (required) | - | | level | Number | Heading level (1-6) | 3 |

text - Informational Text / Alert

{
  "type": "text",
  "text": "This setting will affect all child pages.",
  "variant": "warning"
}

Displays static informational text or alerts. Does not store any data. Also accepts "type": "alert" as an alias.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------| | text | String | The message to display (required) | - | | variant | String | Visual style: info, warning, error, success | info |

Common Use Cases:

  • Help text and instructions
  • Warnings about field impacts
  • Error messages or important notes
  • Success confirmation messages

tags - AEM Tag Selector

{
  "type": "tags",
  "name": "./cq:tags",
  "label": "Tags",
  "required": true,
  "rootPath": "/content/cq:tags/mysite"
}

Provides an AEM tag picker that allows users to select from the AEM tagging system.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------| | name | String | Property name where selected tags are stored (required) | - | | label | String | Field label (required) | - | | rootPath | String | Root path in tag hierarchy | /content/cq:tags | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Content categorization
  • SEO keywords
  • Content filtering and search
  • Taxonomy management

image - Image Upload and Selection

{
  "type": "image",
  "name": "./image",
  "label": "Image",
  "required": true,
  "uploadUrl": "/content/dam/mysite",
  "allowUpload": true
}

Provides an image upload field with DAM integration. Supports drag-and-drop, file browsing, and DAM asset selection.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------| | name | String | Property name (required) | - | | label | String | Field label (required) | - | | uploadUrl | String | Upload destination path in DAM | - | | allowUpload | Boolean | Enable file upload | true | | mimeTypes | Array | Allowed mime types | ['image/gif', 'image/jpeg', 'image/png', 'image/webp', 'image/tiff', 'image/svg+xml'] | | fileNameParameter | String | Property for filename | ./fileName | | fileReferenceParameter | String | Property for file reference | ./fileReference | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Hero images and banners
  • Product images
  • Author avatars
  • Background images

autocomplete - Autocomplete Field

{
  "type": "autocomplete",
  "name": "./product",
  "label": "Select Product",
  "multiple": true,
  "datasource": "/apps/mysite/datasources/products"
}

Provides an autocomplete field with optional datasource integration for dynamic suggestions.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------| | name | String | Property name (required) | - | | label | String | Field label (required) | - | | datasource | String | Path to datasource for suggestions | - | | multiple | Boolean | Allow multiple selections | false | | forceSelection | Boolean | Only allow values from suggestions | true | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Product selection
  • User search
  • Category selection
  • Dynamic value lists

radiogroup - Radio Button Group

{
  "type": "radiogroup",
  "name": "./layout",
  "label": "Layout",
  "vertical": false,
  "options": [
    { "value": "grid", "text": "Grid" },
    { "value": "list", "text": "List", "checked": true }
  ]
}

Displays a group of radio buttons. Better than select when you have 2-4 options that should be immediately visible.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------| | name | String | Property name (required) | - | | label | String | Field label (required) | - | | options | Array | Radio button options (required) | - | | vertical | Boolean | Stack radio buttons vertically | false | | defaultValue | Any | Default selected value | - | | disabled | Boolean | Disable the field | false | | required | Boolean | Make field mandatory | false |

Option Properties:

  • value (String): The value to store
  • text (String): Display text
  • checked (Boolean): Default selected option

Common Use Cases:

  • Layout selection (2-3 options)
  • Yes/No questions
  • Content type selection
  • Alignment options (left/center/right)

pagefield - AEM Page Selector

{
  "type": "pagefield",
  "name": "./targetPage",
  "label": "Link to Page",
  "required": true,
  "rootPath": "/content/mysite/en"
}

Provides an AEM-specific page selector with content tree navigation. Essential for any component that links to other pages.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------|
| name | String | Property name (required) | - | | label | String | Field label (required) | - | | rootPath | String | Root path in content tree | /content | | multiple | Boolean | Allow multiple page selection | false | | filter | String | Filter node types or properties | - | | pickerSrc | String | Custom picker dialog source | - | | pickerTitle | String | Custom picker dialog title | - | | pickerMultiselect | Boolean | Enable multiselect in picker | false | | forceSelection | Boolean | Only allow values from picker | false | | typeHint | String | JCR/Sling type hint | - | | disabled | Boolean | Disable the field | false | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Navigation links
  • Call-to-action buttons
  • Related content links
  • Breadcrumb configuration
  • Footer links

contentfragmentpicker - Content Fragment Selector

{
  "type": "contentfragmentpicker",
  "name": "./fragmentPath",
  "label": "Select Content Fragment",
  "required": true,
  "rootPath": "/content/dam/fragments",
  "fragmentModel": "/conf/mysite/settings/dam/cfm/models/article"
}

Provides a Content Fragment picker for selecting structured content. Essential for AEM headless and content-driven sites.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------|
| name | String | Property name (required) | - | | label | String | Field label (required) | - | | rootPath | String | Root path in DAM | /content/dam | | fragmentModel | String | Path to specific Content Fragment Model | - | | multiple | Boolean | Allow multiple fragment selection | false | | filter | String | Filter fragment types | - | | pickerSrc | String | Custom picker dialog source | - | | typeHint | String | JCR/Sling type hint | - | | disabled | Boolean | Disable the field | false | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Article content references
  • Product data integration
  • Headless content delivery
  • Structured data references
  • Multi-channel content

experiencefragmentpicker - Experience Fragment Selector

{
  "type": "experiencefragmentpicker",
  "name": "./xfPath",
  "label": "Select Experience Fragment",
  "required": true,
  "rootPath": "/content/experience-fragments/mysite"
}

Provides an Experience Fragment picker for reusable component compositions. Perfect for headers, footers, and repeated content blocks.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------|
| name | String | Property name (required) | - | | label | String | Field label (required) | - | | rootPath | String | Root path for XF | /content/experience-fragments | | multiple | Boolean | Allow multiple XF selection | false | | filter | String | Filter XF types | - | | pickerSrc | String | Custom picker dialog source | - | | typeHint | String | JCR/Sling type hint | - | | disabled | Boolean | Disable the field | false | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Global headers and footers
  • Reusable content blocks
  • Multi-variant content
  • Promotional banners
  • Email templates

assetpicker - Generic Asset Selector

{
  "type": "assetpicker",
  "name": "./assetPath",
  "label": "Select Asset",
  "required": true,
  "rootPath": "/content/dam/videos",
  "mimeTypes": ["video/mp4", "video/webm", "application/pdf"]
}

Provides a generic DAM asset picker with mime type filtering. More flexible than image for videos, documents, and other file types.

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------|
| name | String | Property name (required) | - | | label | String | Field label (required) | - | | rootPath | String | Root path in DAM | /content/dam | | mimeTypes | Array | Allowed mime types | - | | multiple | Boolean | Allow multiple asset selection | false | | filter | String | Filter asset types | - | | pickerSrc | String | Custom picker dialog source | - | | pickerTitle | String | Custom picker dialog title | - | | pickerMultiselect | Boolean | Enable multiselect in picker | false | | forceSelection | Boolean | Only allow values from picker | false | | typeHint | String | JCR/Sling type hint | - | | disabled | Boolean | Disable the field | false | | required | Boolean | Make field mandatory | false |

Common Use Cases:

  • Video assets
  • PDF documents
  • Downloadable files
  • Audio files
  • Mixed media selection

rte - Rich Text Editor

{
  "type": "rte",
  "name": "./text",
  "label": "Content",
  "required": true,
  "disabled": false,
  "readOnly": false,
  "height": "300px",
  "width": "100%",
  "maxlength": 5000,
  "features": ["bold", "italic", "underline", "links", "lists"]
}

Properties:

| Property | Type | Description | Default | |----------|------|-------------|---------|
| name | String | Property name (required) | - | | label | String | Field label (required) | - | | required | Boolean | Make field mandatory | false | | disabled | Boolean | Disable the editor | false | | readOnly | Boolean | Make editor read-only | false | | height | String | Editor height (CSS value) | - | | width | String | Editor width (CSS value) | - | | maxlength | Number | Maximum character count | - | | useFixedInlineToolbar | Boolean | Use fixed inline toolbar | false | | features | Array | Enabled RTE features | ['*'] |

Use "features": ["*"] for all features, or specify individual ones:

  • "bold", "italic", "underline" - Text formatting
  • "links" - Hyperlinks
  • "lists" - Ordered and unordered lists
  • "justify" - Text alignment

Dynamic Show/Hide

The plugin supports AEM's built-in cq-dialog-dropdown-showhide and cq-dialog-checkbox-showhide scripts for conditional field visibility.

Dropdown Show/Hide

Show different fields based on dropdown selection:

{
  "type": "select",
  "name": "./contentType",
  "label": "Content Type",
  "cqShowHide": true,
  "showhideTarget": ".content-fields",
  "options": [
    {
      "text": "Image",
      "value": "image"
    },
    {
      "text": "Video",
      "value": "video"
    },
    {
      "text": "Text",
      "value": "text"
    }
  ]
}

Then define containers that will be shown/hidden based on the selected value:

{
  "type": "container",
  "showhideClass": "content-fields",
  "showhidetargetvalue": "image",
  "fields": [
    {
      "type": "pathfield",
      "name": "./imagePath",
      "label": "Image Path",
      "rootPath": "/content/dam"
    },
    {
      "type": "textfield",
      "name": "./altText",
      "label": "Alt Text"
    }
  ]
},
{
  "type": "container",
  "showhideClass": "content-fields",
  "showhidetargetvalue": "video",
  "fields": [
    {
      "type": "pathfield",
      "name": "./videoUrl",
      "label": "Video URL"
    }
  ]
},
{
  "type": "container",
  "showhideClass": "content-fields",
  "showhidetargetvalue": "text",
  "fields": [
    {
      "type": "textarea",
      "name": "./textContent",
      "label": "Text Content"
    }
  ]
}

Checkbox Show/Hide

Show fields when checkbox is checked:

{
  "type": "checkbox",
  "name": "./enableCustomSettings",
  "label": "Enable Custom Settings",
  "cqShowHide": true,
  "showhideTarget": ".custom-settings"
},
{
  "type": "textfield",
  "name": "./customValue",
  "label": "Custom Value",
  "showhideClass": "custom-settings"
},
{
  "type": "numberfield",
  "name": "./customNumber",
  "label": "Custom Number",
  "showhideClass": "custom-settings"
}

Show/Hide Properties

| Property | Type | Used On | Description | |----------|------|---------|-------------| | cqShowHide | Boolean | select, checkbox | Enable show/hide functionality | | showhideTarget | String | select, checkbox | CSS selector of elements to show/hide (e.g., ".my-fields") | | showhideClass | String | fieldset, container | CSS class for elements that will be shown/hidden (e.g., "my-fields") | | showhidetargetvalue | String | fieldset, container | Value that triggers showing this container (used with showhideClass) |

Note: You can use showhideClass on fieldset or container types to hide entire groups of fields together.

Generated XML for dropdown:

<contentType
    granite:class="cq-dialog-dropdown-showhide"
    ...>
    <granite:data
        jcr:primaryType="nt:unstructured"
        cq-dialog-dropdown-showhide-target=".content-fields"/>
    <items jcr:primaryType="nt:unstructured">
        <image text="Image" value="image"/>
        <video text="Video" value="video"/>
        <text text="Text" value="text"/>
    </items>
</contentType>

<container_123456
    sling:resourceType="granite/ui/components/coral/foundation/container"
    granite:class="hide content-fields">
    <granite:data
        jcr:primaryType="nt:unstructured"
        showhidetargetvalue="image"/>
    <items jcr:primaryType="nt:unstructured">
        <!-- image fields -->
    </items>
</container_123456>

Generated XML for checkbox:

<enableCustomSettings
    granite:class="cq-dialog-checkbox-showhide"
    ...>
    <granite:data
        jcr:primaryType="nt:unstructured"
        cq-dialog-checkbox-showhide-target=".custom-settings"/>
</enableCustomSettings>

<customValue
    granite:class="hide custom-settings"
    .../>

Complete Show/Hide Example

{
  "title": "Media Component",
  "tabs": [
    {
      "title": "Content",
      "fields": [
        {
          "type": "select",
          "name": "./mediaType",
          "label": "Media Type",
          "cqShowHide": true,
          "showhideTarget": ".media-fields",
          "defaultValue": "image",
          "options": [
            {
              "text": "Image",
              "value": "image"
            },
            {
              "text": "Video",
              "value": "video"
            }
          ]
        },
        {
          "type": "container",
          "showhideClass": "media-fields",
          "showhidetargetvalue": "image",
          "fields": [
            {
              "type": "pathfield",
              "name": "./imageAsset",
              "label": "Image Asset",
              "rootPath": "/content/dam"
            },
            {
              "type": "textfield",
              "name": "./imageAlt",
              "label": "Alt Text"
            }
          ]
        },
        {
          "type": "container",
          "showhideClass": "media-fields",
          "showhidetargetvalue": "video",
          "fields": [
            {
              "type": "pathfield",
              "name": "./videoAsset",
              "label": "Video Asset",
              "rootPath": "/content/dam"
            },
            {
              "type": "checkbox",
              "name": "./videoAutoplay",
              "label": "Autoplay Video"
            }
          ]
        },
        {
          "type": "checkbox",
          "name": "./addCaption",
          "label": "Add Caption",
          "cqShowHide": true,
          "showhideTarget": ".caption-fields"
        },
        {
          "type": "container",
          "showhideClass": "caption-fields",
          "fields": [
            {
              "type": "textarea",
              "name": "./caption",
              "label": "Caption Text"
            }
          ]
        }
      ]
    }
  ]
}

Hiding Groups of Fields with Container

You can use container or fieldset with showhideClass and showhidetargetvalue to hide multiple fields as a group:

{
  "type": "select",
  "name": "./mode",
  "label": "Display Mode",
  "cqShowHide": true,
  "showhideTarget": ".mode-settings",
  "options": [
    { "text": "Simple", "value": "simple" },
    { "text": "Advanced", "value": "advanced" }
  ]
},
{
  "type": "container",
  "name": "advancedSettings",
  "showhideClass": "mode-settings",
  "showhidetargetvalue": "advanced",
  "fields": [
    {
      "type": "textfield",
      "name": "./customClass",
      "label": "Custom CSS Class"
    },
    {
      "type": "numberfield",
      "name": "./customWidth",
      "label": "Custom Width"
    },
    {
      "type": "textfield",
      "name": "./dataAttributes",
      "label": "Data Attributes"
    }
  ]
}

This will show/hide all three fields inside the container only when "Advanced" is selected. Note that container doesn't require a label - it's just a grouping element.

multifield - Repeatable Fields

Simple Multifield (single field repeated):

{
  "type": "multifield",
  "name": "./tags",
  "label": "Tags",
  "minItems": 1,
  "maxItems": 5,
  "fields": [
    {
      "type": "textfield",
      "name": "./tag",
      "label": "Tag"
    }
  ]
}

Composite Multifield (grouped fields repeated together):

{
  "type": "multifield",
  "name": "./slides",
  "label": "Slides",
  "composite": true,
  "minItems": 2,
  "maxItems": 10,
  "fields": [
    {
      "type": "textfield",
      "name": "./title",
      "label": "Title",
      "required": true
    },
    {
      "type": "textarea",
      "name": "./description",
      "label": "Description"
    },
    {
      "type": "pathfield",
      "name": "./image",
      "label": "Image",
      "rootPath": "/content/dam"
    }
  ]
}

Multifield Properties:

| Property | Type | Description | Example | |----------|------|-------------|---------| | minItems | Number | Minimum number of items required (0 or greater) | minItems: 1 | | maxItems | Number | Maximum number of items allowed (1 or greater) | maxItems: 10 | | composite | Boolean | Group multiple fields together in each item | composite: true |

Conditional Tabs

Tabs can be shown or hidden based on the value of another field:

{
  "title": "My Component",
  "tabs": [
    {
      "title": "General",
      "fields": [
        {
          "type": "checkbox",
          "name": "./enableAdvanced",
          "label": "Enable Advanced Features"
        }
      ]
    },
    {
      "title": "Advanced Settings",
      "showIf": {
        "field": "./enableAdvanced",
        "value": true
      },
      "fields": [
        {
          "type": "textfield",
          "name": "./customClass",
          "label": "Custom CSS Class"
        }
      ]
    }
  ]
}

The "Advanced Settings" tab will only appear when the "Enable Advanced Features" checkbox is checked.

showIf Properties:

  • field (String): Path to the field to check (e.g., ./enableAdvanced)
  • value (Any): Value to compare against (true, false, "video", etc.)

Use Cases:

  • Advanced/expert mode settings
  • Content-type specific tabs (show video tab only when content type is "video")
  • Optional feature configurations
  • Progressive disclosure to simplify UX

Accordion Layout

Use layout: 'accordion' for collapsible sections instead of tabs:

{
  "title": "My Component",
  "layout": "accordion",
  "tabs": [
    {
      "title": "Basic Settings",
      "active": true,
      "fields": [
        { "type": "textfield", "name": "./title", "label": "Title" },
        { "type": "textarea", "name": "./description", "label": "Description" }
      ]
    },
    {
      "title": "Advanced Options",
      "fields": [
        { "type": "textfield", "name": "./cssClass", "label": "CSS Class" },
        { "type": "numberfield", "name": "./order", "label": "Display Order" }
      ]
    }
  ]
}

Accordion Properties:

| Property | Type | Description | |----------|------|-------------| | layout | String | Set to "accordion" to use collapsible sections | | tabs | Array | Array of section objects (reuses tabs structure) |

Section Properties:

  • title (String): Section heading
  • active (Boolean): If true, this section is expanded by default (default: false)
  • fields (Array): Fields within this section
  • name (String): Optional custom node name

When to use Accordion vs Tabs:

  • Accordion: Many sections, progressive disclosure, or when authors need to see multiple sections at once
  • Tabs: Few sections (2-5), mutually exclusive content, or when a cleaner single-view interface is preferred

Field Descriptions

Add helpful guidance text below any field using the description property:

{
  "type": "textfield",
  "name": "./title",
  "label": "Title",
  "description": "Enter a short, descriptive title for this component",
  "required": true
}

The description appears below the field in a lighter font, providing context and instructions to content authors.

Placeholder Text

Add example text inside empty fields using the placeholder property:

{
  "type": "textfield",
  "name": "./username",
  "label": "Username",
  "placeholder": "Enter your username"
}

Placeholder text appears in a lighter color inside empty fields and disappears when the user starts typing. Very useful for:

  • Showing format examples ("MM/DD/YYYY")
  • Providing input hints ("Type to search...")
  • Clarifying expected values ("e.g., [email protected]")

Supported on: textfield, textarea, numberfield, pathfield, select, autocomplete

Min/Max Validation

Set numeric range constraints on number fields:

{
  "type": "numberfield",
  "name": "./age",
  "label": "Age",
  "min": 18,
  "max": 99,
  "placeholder": "Enter age between 18-99"
}

The min and max properties enforce numeric boundaries:

  • min (Number): Minimum allowed value (inclusive)
  • max (Number): Maximum allowed value (inclusive)
  • Works with numberfield type
  • Browser-native validation

Common Use Cases:

// Percentage (0-100)
{ "type": "numberfield", "name": "./opacity", "label": "Opacity %", "min": 0, "max": 100 }

// Positive numbers only
{ "type": "numberfield", "name": "./quantity", "label": "Quantity", "min": 1 }

// Rating system
{ "type": "numberfield", "name": "./rating", "label": "Rating", "min": 1, "max": 5 }

Disabled and ReadOnly Fields

Control field interactivity:

// Disabled field - grayed out, not submitted
{
  "type": "textfield",
  "name": "./calculatedValue",
  "label": "Calculated Value",
  "disabled": true,
  "defaultValue": "Auto-generated"
}

// ReadOnly field - visible but not editable, submitted with form
{
  "type": "textfield",
  "name": "./timestamp",
  "label": "Created Date",
  "readOnly": true,
  "defaultValue": "2024-01-15"
}

Properties:

  • disabled (Boolean): Disables the field completely (default: false)
  • readOnly (Boolean): Makes field read-only but still submits value (default: false)

Key Differences:

  • Disabled: Field is grayed out and value is NOT submitted
  • ReadOnly: Field looks normal but can't be edited, value IS submitted

Use Cases:

  • Disabled: Conditional fields, insufficient permissions, calculated values
  • ReadOnly: System-generated IDs, timestamps, inherited values, display-only info

Multiple Selection

Allow selecting multiple options in dropdowns:

{
  "type": "select",
  "name": "./categories",
  "label": "Categories",
  "multiple": true,
  "options": [
    { "text": "News", "value": "news" },
    { "text": "Events", "value": "events" },
    { "text": "Blog", "value": "blog" }
  ]
}

The multiple property enables multi-select:

  • Works on select and autocomplete field types
  • Users can select multiple values using Ctrl/Cmd + click
  • Values are stored as an array

Common Use Cases:

  • Tag selection
  • Category assignment
  • Permission selection
  • Feature toggles

Contextual Help

Add help icon with tooltip next to field labels:

// Simple text tooltip
{
  "type": "textfield",
  "name": "./pattern",
  "label": "RegEx Pattern",
  "contextualHelp": "Enter a valid JavaScript regular expression"
}

// With external documentation link
{
  "type": "select",
  "name": "./layout",
  "label": "Layout Type",
  "contextualHelp": {
    "text": "Choose the layout format for this component",
    "url": "https://docs.example.com/layouts"
  },
  "options": [...]
}

The contextualHelp property adds a small ⓘ icon next to the field label:

  • String value: Shows tooltip on hover
  • Object value: Shows tooltip and optional "Learn more" link
    • text (String): Tooltip content
    • url (String): External documentation URL

Benefits:

  • Keeps UI clean while providing detailed help
  • Reduces need for lengthy field descriptions
  • Links to external documentation for complex features
  • Standard AEM pattern for contextual assistance

Custom CSS Classes

Add custom CSS classes to any field using the className property:

// Single class
{
  "type": "textfield",
  "name": "./title",
  "label": "Title",
  "className": "custom-field-style"
}

// Multiple classes (string with spaces)
{
  "type": "select",
  "name": "./type",
  "label": "Type",
  "className": "highlight-field required-indicator",
  "options": [...]
}

// Multiple classes (array format)
{
  "type": "textarea",
  "name": "./description",
  "label": "Description",
  "className": ["large-textarea", "rich-editor"]
}

The className property allows you to:

  • Apply custom styling to specific fields
  • Add JavaScript selector hooks for custom behavior
  • Highlight important or required fields visually
  • Maintain consistent styling across similar fields

Note: Custom classes are merged with existing Granite UI classes (like show/hide classes) and added to the field's granite:class attribute.

Field Width Control

Control the width of individual fields using the width property:

// Fixed pixel width
{
  "type": "textfield",
  "name": "./code",
  "label": "Product Code",
  "width": "150px"
}

// Percentage width
{
  "type": "numberfield",
  "name": "./quantity",
  "label": "Qty",
  "width": "30%"
}

// Numeric value (treated as pixels)
{
  "type": "select",
  "name": "./size",
  "label": "Size",
  "width": "200",
  "options": [...]
}

The width property accepts:

  • Pixel values: "100px", "250px"
  • Percentages: "50%", "75%"
  • Numeric strings: "200" (treated as pixels)
  • CSS values: "auto", "fit-content"

Common Use Cases:

  • Short input fields for codes, IDs, or numbers
  • Compact layouts with multiple fields per row
  • Consistent sizing across related fields
  • Optimizing dialog space utilization

Coral UI Spacing (Margin)

Control vertical spacing between fields using the margin property:

// Add margin above field
{
  "type": "heading",
  "text": "Advanced Settings",
  "level": 3,
  "margin": true
}

// No margin (tight layout)
{
  "type": "textfield",
  "name": "./field1",
  "label": "Field 1",
  "margin": false
}

// Default behavior (don't specify)
{
  "type": "textfield",
  "name": "./field2",
  "label": "Field 2"
}

The margin property controls Coral UI's vertical spacing:

  • true: Adds standard margin above the field
  • false: Removes margin for tight layouts
  • undefined: Uses Coral UI default spacing

When to Use:

  • margin: true: Add visual separation before headings or new sections
  • margin: false: Create compact, dense layouts; group tightly related fields
  • Default: Most fields should use default Coral spacing

Common Patterns:

// Section with visual breathing room
{
  "type": "heading",
  "text": "Section Title",
  "margin": true
},

// Compact field group (address fields, name parts, etc.)
{
  "type": "textfield",
  "name": "./street1",
  "label": "Street Address",
  "margin": false
},
{
  "type": "textfield",
  "name": "./street2",
  "label": "Apt/Suite",
  "margin": false
}

Default Values

Set initial values for fields using the defaultValue property:

// Text field with default
{
  "type": "textfield",
  "name": "./title",
  "label": "Title",
  "defaultValue": "Welcome Message"
}

// Number field with default
{
  "type": "numberfield",
  "name": "./quantity",
  "label": "Quantity",
  "defaultValue": 1,
  "min": 1
}

// Checkbox with default checked state
{
  "type": "checkbox",
  "name": "./enabled",
  "label": "Enable Feature",
  "defaultValue": true
}

// Select with pre-selected option
{
  "type": "select",
  "name": "./theme",
  "label": "Theme",
  "defaultValue": "dark",
  "options": [
    { "value": "light", "text": "Light" },
    { "value": "dark", "text": "Dark" }
  ]
}

The defaultValue property:

  • Sets the initial value when component is first added
  • Works with all field types (textfield, numberfield, checkbox, select, etc.)
  • Values are stored in the value attribute in the XML
  • Useful for sensible defaults that authors can modify

Common Use Cases:

  • Default "Read More" text for CTAs
  • Pre-set quantity to 1 in product components
  • Enable features by default (opt-out vs opt-in)
  • Default theme or style selection

Field Descriptions

Add helpful descriptive text below field labels using the description property:

{
  "type": "textfield",
  "name": "./email",
  "label": "Email Address",
  "description": "This email will be used for notification purposes only"
}

{
  "type": "pathfield",
  "name": "./backgroundImage",
  "label": "Background Image",
  "description": "Recommended size: 1920x1080px. Supports JPG, PNG, and WebP formats.",
  "rootPath": "/content/dam"
}

{
  "type": "numberfield",
  "name": "./animationDuration",
  "label": "Animation Duration",
  "description": "Duration in milliseconds. Lower values = faster animations.",
  "min": 100,
  "max": 5000,
  "defaultValue": 1000
}

The description property:

  • Adds fieldDescription attribute in AEM
  • Appears as gray text below the field label
  • Helps authors understand field purpose and constraints
  • Supports special characters (automatically escaped)

When to Use:

  • Explaining technical fields (CSS classes, regex patterns)
  • Providing examples or format requirements
  • Clarifying business rules or constraints
  • Guiding authors on best practices

Pro Tip: Combine with contextualHelp for comprehensive guidance:

{
  "type": "textfield",
  "name": "./regex",
  "label": "Validation Pattern",
  "description": "JavaScript regex pattern for validation",
  "contextualHelp": {
    "text": "Enter a valid JavaScript regular expression",
    "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions"
  }
}

Maximum Length Validation

Limit the number of characters users can enter using the maxLength property:

// Short text field
{
  "type": "textfield",
  "name": "./title",
  "label": "Title",
  "maxLength": 50,
  "placeholder": "Maximum 50 characters"
}

// Textarea with character limit
{
  "type": "textarea",
  "name": "./description",
  "label": "Description",
  "maxLength": 500,
  "rows": 5,
  "description": "Maximum 500 characters"
}

// Product code
{
  "type": "textfield",
  "name": "./sku",
  "label": "SKU",
  "maxLength": 12,
  "placeholder": "12-char code"
}

The maxLength property:

  • Adds maxlength attribute to the field
  • Prevents users from entering more than the specified number of characters
  • Browser-native validation (no form submission required)
  • Works with textfield, textarea, and pathfield types

Common Use Cases:

// Tweet-length content
{ "maxLength": 280 }

// Meta descriptions for SEO
{ "maxLength": 160 }

// Headline/Title fields
{ "maxLength": 100 }

// Product codes or IDs
{ "maxLength": 20 }

Best Practices:

  • Always inform users about the limit (via description or placeholder)
  • Consider UX: extremely restrictive limits can frustrate authors
  • Use in combination with required validation for data quality
  • Common limits: 50 (titles), 160 (meta), 500 (short descriptions), 2000 (long content)

Empty Text

Provide placeholder text using the emptyText property (Coral UI native alternative to placeholder):

// Search field
{
  "type": "textfield",
  "name": "./search",
  "label": "Search",
  "emptyText": "Type to search..."
}

// Email with format example
{
  "type": "textfield",
  "name": "./email",
  "label": "Email",
  "emptyText": "[email protected]"
}

// Date format hint
{
  "type": "textfield",
  "name": "./eventDate",
  "label": "Event Date",
  "emptyText": "MM/DD/YYYY"
}

The emptyText property:

  • Native Coral UI property (semantic alternative to placeholder)
  • Displays hint text in empty fields
  • Disappears when user starts typing
  • If both placeholder and emptyText are specified, emptyText takes precedence

Difference from placeholder:

  • placeholder → Converted to emptyText in AEM (for backward compatibility)
  • emptyText → Direct Coral UI property (more semantic)
  • Both achieve the same visual result
  • Use emptyText for new implementations, placeholder for familiarity

Common Patterns:

// Format hints
{ "emptyText": "YYYY-MM-DD" }
{ "emptyText": "(555) 123-4567" }

// Action prompts
{ "emptyText": "Start typing..." }
{ "emptyText": "Select an option" }

// Examples
{ "emptyText": "e.g., John Doe" }
{ "emptyText": "e.g., /content/mysite/en" }

Pro Tip: For complex format requirements, combine with description or validation:

{
  "type": "textfield",
  "name": "./phone",
  "label": "Phone Number",
  "emptyText": "(555) 123-4567",
  "description": "US phone numbers only",
  "validation": {
    "pattern": "^\\(\\d{3}\\) \\d{3}-\\d{4}$",
    "message": "Please use format: (555) 123-4567"
  }
}

### Granite ID (Custom Field IDs)

Assign custom IDs to fields for JavaScript integration and specific styling using the `graniteId` property:

```json
// Custom field ID for JavaScript hooks
{
  "type": "select",
  "name": "./contentType",
  "label": "Content Type",
  "graniteId": "content-type-selector",
  "options": [...]
}

// For dynamic field manipulation
{
  "type": "textfield",
  "name": "./dynamicField",
  "label": "Dynamic Field",
  "graniteId": "js-dynamic-field"
}

// Multiple fields with coordinated IDs
{
  "type": "checkbox",
  "name": "./enableAdvanced",
  "label": "Enable Advanced",
  "graniteId": "advanced-toggle",
  "cqShowHide": true,
  "showhideTarget": ".advanced-options"
},
{
  "type": "numberfield",
  "name": "./advancedValue",
  "label": "Advanced Value",
  "graniteId": "advanced-input",
  "showhideClass": "advanced-options"
}

The graniteId property:

  • Adds granite:id attribute to the field
  • Provides stable, predictable IDs for JavaScript selectors
  • Useful for custom client libraries and interactions
  • Better than relying on generated or name-based IDs

Common Use Cases:

  • Custom JavaScript validation or formatting
  • Integration with third-party libraries
  • Dynamic field behavior (calculations, cascading dropdowns)
  • Automated testing with stable selectors
  • CSS styling for specific fields

Best Practices:

// Use descriptive, kebab-case IDs
{ "graniteId": "hero-title-input" }
{ "graniteId": "primary-cta-link" }

// Prefix by component or feature
{ "graniteId": "carousel-slide-count" }
{ "graniteId": "video-autoplay-toggle" }

// Avoid generic names
// ❌ Bad: "field1", "input", "select"
// ✅ Good: "product-sku", "author-bio", "featured-image"

Tracking Feature (Analytics Integration)

Add analytics tracking identifiers to fields using the trackingFeature property:

// Track field usage in Adobe Analytics
{
  "type": "select",
  "name": "./template",
  "label": "Template Selection",
  "trackingFeature": "template-selector",
  "options": [...]
}

// Track feature toggles
{
  "type": "checkbox",
  "name": "./enableVideo",
  "label": "Enable Video Background",
  "trackingFeature": "video-background-toggle"
}

// Track specific component interactions
{
  "type": "pathfield",
  "name": "./ctaLink",
  "label": "CTA Link",
  "trackingFeature": "hero-cta-link",
  "rootPath": "/content"
}

The trackingFeature property:

  • Adds trackingFeature attribute for analytics frameworks
  • Enables tracking of component configuration patterns
  • Helps identify which features are most used by authors
  • Integrates with Adobe Analytics or custom tracking solutions

Enterprise Use Cases:

// Component adoption tracking
{
  "trackingFeature": "layout-grid-usage"
}

// Feature flag analysis
{
  "trackingFeature": "personalization-enabled"
}

// Content strategy insights
{
  "trackingFeature": "video-content-type"
}

// A/B testing configurations
{
  "trackingFeature": "variation-b-selected"
}

Analytics Dashboard Examples:

  • Most configured component features
  • Template selection trends
  • Feature adoption rates
  • Author workflow patterns

Render Hidden

Conditionally hide fields in the UI while preserving their functionality using the renderHidden property:

// Hidden until condition met
{
  "type": "textfield",
  "name": "./apiKey",
  "label": "API Key",
  "renderHidden": true,
  "description": "Only shown to administrators"
}

// Programmatically revealed field
{
  "type": "numberfield",
  "name": "./advancedSetting",
  "label": "Advanced Setting",
  "renderHidden": true
}

// Combined with conditional logic
{
  "type": "select",
  "name": "./mode",
  "label": "Mode",
  "options": [
    { "value": "simple", "text": "Simple" },
    { "value": "advanced", "text": "Advanced" }
  ]
},
{
  "type": "textfield",
  "name": "./advancedConfig",
  "label": "Advanced Config",
  "renderHidden": true,
  "description": "Revealed when Advanced mode is selected"
}

The renderHidden property:

  • Adds renderHidden="{Boolean}true" attribute
  • Hides field in dialog UI but keeps it in DOM
  • Different from type: "hidden" (which never renders)
  • Can be shown/hidden dynamically with JavaScript
  • Field still submits its value when form is saved

**Difference from `type