Appearance
Are you an LLM? You can read better optimized documentation at /docs/custom-widgets/v2/configurable-widgets.md for this page in Markdown format
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
- You add a
configurationobject (with apropertiesarray) anddefaultConfigto your widget definition inextensions_registry.json. - When someone adds or edits the widget on a page, the No-Code Builder shows a form built from those properties.
- When the widget runs, the SDK makes the current configuration values available via
sdk.getProps(). - If configuration changes while the widget is active, the SDK emits a
propsChangedevent 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
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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"
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Tip:
defaultValue(on each property) anddefaultConfig(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"
}
]
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Key behavior:
- A field's
sectionmatches a section'sname. A section appears only when at least one field points to it. - Fields without a
section— likeinternal_noteabove — fall into the default Configuration group, shown first. - Sections then appear in the order they are listed in
sections. Mark oneexpanded: trueto 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 fieldname, 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
| Type | Static options | Dynamic options | Multi-value |
|---|---|---|---|
select | Yes — via options array | Yes — via dynamicOptions | Optional — set multiple: true |
autocomplete | No | Required — via dynamicOptions | Optional — 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
| Pattern | Type | isAsyncSearch | When to use |
|---|---|---|---|
| Fixed dropdown | select + options | — | Fixed enum: sort order, layout style |
| Load-once dropdown | select + dynamicOptions | — | Bounded list (up to ~200 items): categories, regions |
| Search-as-you-type | autocomplete + dynamicOptions | true | Unbounded list: 10 000+ courses, users, products |
| Single-fetch typeahead | autocomplete + dynamicOptions | false | Small 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 */ }
}
]
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 */ }
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
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"
}
}
}
]
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
}
}
]
}
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
}1
2
3
4
5
6
2
3
4
5
6
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)
})
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
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)
}1
2
3
4
5
2
3
4
5
css
.card-grid {
display: grid;
grid-template-columns: repeat(var(--cards-per-row, 3), 1fr);
gap: 20px;
background: var(--card-bg, #ffffff);
}1
2
3
4
5
6
2
3
4
5
6
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
truefor the widget. - Ensure the widget has a
configurationobject with a non-emptypropertiesarray.
Props are empty or undefined
- Call
sdk.getProps()afterawait sdk.whenReady()— the SDK must be initialized first. - Ensure the property has a key in
defaultConfigso a default is always available. - Ensure the property names in your code match the
nameinconfiguration.propertiesexactly (case-sensitive).
Next Steps
- Widget Runtime — SDK API reference for
getProps()andpropsChanged - Using React — React-specific patterns for reactive props
- Widget Definition Reference — All field types, rules, and configuration structure
- Repository Layout — Organizing your widget repository
- Card Grid
widget.jsonin the template repository — Working example with multiple configuration property types (color pickers, number inputs, selects)

