Skip to content

Configurable Widgets

To let community admins customize your widget when they add it in the No-Code Builder — without editing code — define a configuration form in extensions_registry.json. The fields you declare become a form in the No-Code Builder, and your widget reads the submitted values at runtime through sdk.getProps().

How It Works

  1. You add a configuration object (with a properties array) and defaultConfig to your widget definition in extensions_registry.json.
  2. When someone adds or edits the widget on a page, the No-Code Builder shows a form built from those properties.
  3. When the widget runs, the SDK makes the current configuration values available via sdk.getProps().
  4. If configuration changes while the widget is active, the SDK emits a propsChanged event so your widget can react.

Add a Configuration Form

Add a configuration object with a properties array. Each property needs at least name, label, and type — plus any optional fields that shape its behavior, such as rules, options (static choices for select), dynamicOptions (API-driven choices for select and autocomplete), multiple, and sortable. Add a defaultConfig object that maps each property name to a default value. Set settings.configurable to true so the configuration form is shown.

Minimal Example

A configuration form is defined inside a widget entry in extensions_registry.json. In the complete file below, the lines in focus are the configuration-specific fields; the greyed-out lines are the standard widget definition that surrounds them:

jsonc
{
  "widgets": [
    {
      "title": "Card Grid",
      "type": "my_company_card_grid",
      "category": "custom",
      "source": {
        "path": "widgets/card-grid",
        "entry": "index.html"
      },
      "configuration": { 
        "properties": [
          {
            "name": "cards_per_row",
            "type": "number",
            "label": "Cards Per Row",
            "defaultValue": 3,
            "rules": { "required": true, "minimum": 1, "maximum": 6 }
          }
        ]
      },
      "defaultConfig": {
        "cards_per_row": 3
      },
      "settings": {
        "configurable": true
      }
    }
  ]
}

For the full field schema — every ConfigField property, field type, and validation rule — see the Widget Definition Reference.

Provide Defaults

The defaultConfig object maps each property name to a default value. Keys must match the name of items in configuration.properties. These values are used when the widget is first added or when a value is not set by the editor.

Example:

jsonc
{
  "widgets": [
    {
      "title": "Card Grid",
      "type": "my_company_card_grid",
      "category": "custom",
      "source": {
        "path": "widgets/card-grid",
        "entry": "index.html"
      },
      "configuration": { 
        "properties": [
          {
            "name": "cards_per_row",
            "type": "number",
            "label": "Cards Per Row",
            "defaultValue": 3,
            "rules": { "required": true, "minimum": 1, "maximum": 6 }
          },
          {
            "name": "card_style",
            "type": "select",
            "label": "Card Style",
            "defaultValue": "shadow",
            "options": [
              { "value": "flat", "label": "Flat" },
              { "value": "shadow", "label": "Shadow" },
              { "value": "bordered", "label": "Bordered" }
            ]
          }
        ]
      },
      "defaultConfig": {
        "cards_per_row": 3,
        "card_style": "shadow"
      }
    }
  ]
}

Tip: defaultValue (on each property) and defaultConfig (on the widget) serve similar roles. When both are set for the same property, one value is used as the starting configuration — check your platform's behavior if you use both.

Set settings.configurable to true so the configuration form is shown in the No-Code Builder.

Group Fields into Sections

By default, every field appears in a single Configuration group. For a larger form, group related fields into collapsible sections: give each field a section, then — optionally — list your sections in a sections array on the configuration object to set their labels, order, and which one opens first.

jsonc
{
  "widgets": [
    {
      "title": "Card Grid",
      "type": "my_company_card_grid",
      "category": "custom",
      "source": {
        "path": "widgets/card-grid",
        "entry": "index.html"
      },
      "configuration": { 
        "sections": [
          { "name": "layout", "label": "Layout", "expanded": true },
          { "name": "appearance", "label": "Appearance", "description": "Colors and spacing" }
        ],
        "properties": [
          {
            "name": "cards_per_row",
            "type": "number",
            "label": "Cards Per Row",
            "section": "layout"
          },
          {
            "name": "card_background",
            "type": "color",
            "label": "Card Background",
            "section": "appearance"
          },
          {
            "name": "internal_note",
            "type": "text",
            "label": "Internal Note"
          }
        ]
      }
    }
  ]
}

Key behavior:

  • A field's section matches a section's name. A section appears only when at least one field points to it.
  • Fields without a section — like internal_note above — fall into the default Configuration group, shown first.
  • Sections then appear in the order they are listed in sections. Mark one expanded: true to have it open when the form loads; otherwise the first section opens.
  • Grouping is presentation only. You still read every value the same way with sdk.getProps(), keyed by field name, so you can rename or reorder sections without changing your widget code or losing saved values.

For the full section field list and ordering rules, see the Widget Definition Reference.

Populate a Dropdown from an API

Instead of a static options array, a select or autocomplete field can load its choices from an external API using dynamicOptions.

Choosing select vs. autocomplete

TypeStatic optionsDynamic optionsMulti-value
selectYes — via options arrayYes — via dynamicOptionsOptional — set multiple: true
autocompleteNoRequired — via dynamicOptionsOptional — set multiple: true

A select field declares exactly one of options (static) or dynamicOptions (dynamic) — never both. An autocomplete field always requires dynamicOptions.

When to Use Each Pattern

PatternTypeisAsyncSearchWhen to use
Fixed dropdownselect + optionsFixed enum: sort order, layout style
Load-once dropdownselect + dynamicOptionsBounded list (up to ~200 items): categories, regions
Search-as-you-typeautocomplete + dynamicOptionstrueUnbounded list: 10 000+ courses, users, products
Single-fetch typeaheadautocomplete + dynamicOptionsfalseSmall list with chip UI

Allowing Multiple Selections

Add "multiple": true to any select or autocomplete field to allow the user to pick more than one option:

jsonc
{
  "widgets": [
    {
      "title": "Card Grid",
      "type": "my_company_card_grid",
      "category": "custom",
      "source": {
        "path": "widgets/card-grid",
        "entry": "index.html"
      },
      "configuration": {
        "properties": [
          { 
            "name": "tags",
            "type": "autocomplete",
            "label": "Tags",
            "multiple": true,
            "dynamicOptions": { /* endpoint and mapping */ }
          }
        ]
      }
    }
  ]
}

Allowing Editors to Reorder Selections

For a multi-value autocomplete field, add "sortable": true to let editors drag selected items into the order your widget should use:

jsonc
{
  "name": "featured_courses",
  "type": "autocomplete",
  "label": "Featured Courses",
  "multiple": true,
  "sortable": true,
  "dynamicOptions": { /* endpoint and mapping */ }
}

sortable is only valid when type is "autocomplete" and multiple is true. The selected values are saved and returned by sdk.getProps() in the editor's chosen order. For content-backed widgets, the platform also preserves this order when it forwards the array to the content endpoint.

Load-Once Dropdown

jsonc
{
  "widgets": [
    {
      "title": "Card Grid",
      "type": "my_company_card_grid",
      "category": "custom",
      "source": {
        "path": "widgets/card-grid",
        "entry": "index.html"
      },
      "configuration": {
        "properties": [
          { 
            "name": "category",
            "type": "select",
            "label": "Category",
            "dynamicOptions": {
              "endpoint": {
                "endpoint": "https://api.example.com/categories",
                "method": "GET"
              },
              "mapping": {
                "valueKey": "id",
                "labelKey": "name"
              }
            }
          }
        ]
      }
    }
  ]
}

Search-as-You-Type Multi-Select

jsonc
{
  "widgets": [
    {
      "title": "Card Grid",
      "type": "my_company_card_grid",
      "category": "custom",
      "source": {
        "path": "widgets/card-grid",
        "entry": "index.html"
      },
      "configuration": {
        "properties": [
          { 
            "name": "courses",
            "type": "autocomplete",
            "label": "Courses",
            "multiple": true,
            "sortable": true,
            "rules": { "required": true },
            "dynamicOptions": {
              "endpoint": {
                "endpoint": "https://api.skilljar.com/v1/courses?q={q}",
                "method": "GET"
              },
              "valuesEndpoint": {
                "endpoint": "https://api.skilljar.com/v1/courses?ids={value}",
                "method": "GET"
              },
              "mapping": {
                "path": "results",
                "valueKey": "id",
                "labelKey": "title"
              },
              "isAsyncSearch": true
            }
          }
        ]
      }
    }
  ]
}

The valuesEndpoint is optional but recommended: when the configurator reloads, it calls valuesEndpoint with the stored IDs to display fresh labels. Without it, the cached label is shown until the user searches again.

For the full dynamicOptions field schema and runtime behavior, see the Widget Definition Reference.

Read Configuration in Your Widget

Call sdk.getProps() to read the current configuration values. Each key in the returned object corresponds to a property name from configuration.properties.

Reading props

javascript
export async function init(sdk) {
  await sdk.whenReady()

  const props = sdk.getProps()
  console.log(props.cards_per_row) // 3
}

Reacting to changes

When an editor updates configuration in the No-Code Builder, the SDK emits a propsChanged event. Listen for it to keep your widget in sync:

javascript
export async function init(sdk) {
  await sdk.whenReady()

  const props = sdk.getProps()
  render(props)

  sdk.on('propsChanged', (newProps) => {
    render(newProps)
  })
}

Applying props to styles

Use configuration values to set CSS custom properties on your widget, then reference them in your styles:

javascript
function applyStyles(sdk, props) {
  const host = sdk.getContainer().host
  host.style.setProperty('--cards-per-row', props.cards_per_row)
  host.style.setProperty('--card-bg', props.card_background)
}
css
.card-grid {
  display: grid;
  grid-template-columns: repeat(var(--cards-per-row, 3), 1fr);
  gap: 20px;
  background: var(--card-bg, #ffffff);
}

Example

A "Card Grid" widget defines a number (cards_per_row), colors (card_background, card_text_color), and a select (show_icons with options "yes" / "no"). In the registry you add those to configuration.properties and defaultConfig. In your widget code you read them with sdk.getProps() and use the values to build the grid, set colors, and conditionally show icons. When an editor changes "Cards Per Row" to 4 or picks "No" for "Show Icons", the propsChanged event fires and your widget updates.

Troubleshooting

The configuration form does not appear

  • Ensure settings.configurable is true for the widget.
  • Ensure the widget has a configuration object with a non-empty properties array.

Props are empty or undefined

  • Call sdk.getProps() after await sdk.whenReady() — the SDK must be initialized first.
  • Ensure the property has a key in defaultConfig so a default is always available.
  • Ensure the property names in your code match the name in configuration.properties exactly (case-sensitive).

Next Steps

Gainsight CC Developer Portal