Configurable properties for Lux experiences

configurable-properties.md
Raw file

A property becomes configurable and visible in the props pane when you add a decorator to it. The decorator's options set the field type that the props pane renders, and also set the storage key that the resolved value is read back from.

@config decorator

JavaScript
import {AIUXElement, config} from '@servicenow/aiux-components-core';import {customElement} from 'lit/decorators.js';import {html} from 'lit';
@customElement('my-widget')export default class MyWidget extends AIUXElement {  @config({    label: 'Display Density',    description: 'Controls how compact the widget appears',    group: 'Appearance',    type: 'select',    choices: [      {value: 'compact', label: 'Compact'},      {value: 'normal', label: 'Normal'},      {value: 'comfortable', label: 'Comfortable'}    ]  })  density = 'normal'; // ← the developer default
  render() {    return html`<div class="widget density-${this.density}">…</div>`;  }
  _onDensityChange(e) {    this.updateConfig('density', e.detail.value);  }}

The decorator does three things:

  1. Registers metadata. The label, description, group, choices, and type you pass go into the component's manifest. The props pane auto-generates the settings UI from it, and AIEL reads the same manifest.
  2. Turns the field into a getter: get density() { return this.aiuxConfigData?.density ?? 'normal'; }. The this.aiuxConfigData object is populated from the aiux-config-data attribute on the element, which carries the fully-resolved payload for this user.
  3. Makes your initial value the bottom layer: the developer default, used when no other layer has a value.

Two constraints that produce silent bugs if you miss them:

  • Never assign to a @config field. The statement this.density = 'compact' is a no-op, because the field is a getter. Use this.updateConfig('density', 'compact'), or setPersonalization or setConfigValue from the config service.
  • Never combine @config with a parent-controlled @property(). The getter reads from aiuxConfigData, and a parent setting the same attribute conflicts with it. Use @state(), or no Lit decorator at all, for the backing field.

@config options

Option Type Description
label (required) string Name shown in the config UI, and the name AIEL uses to expose the property.
description string Longer explanation, rendered as tooltip text.
group string (default "General") Groups related settings in the panel. See Grouping a crowded panel.
order number Sort position within the group; lower values appear first.
choices {value, label}[] A closed set of values. Renders a select; combine with multi: true for a multi-select.
type string Explicit field type. Takes priority over the legacy constructor flags. See Field types.
table string Required with type: 'reference-picker'; the table to search.
min / max / step number Constraints for number fields.
rows / maxlength / minlength / placeholder number or string Options for textarea and string fields. rows defaults to 3.
pattern string Regex validation for string and URL inputs.
dependsOn object Conditional visibility. See Conditional fields.
list {table, query?, view?, columns?} Renders a pencil button opening a list iframe modal. Use with type: Array; the stored value is the resulting ordered record set.

Field types

type Field rendered Extra options
'string' (fallback) Text input maxlength, minlength, placeholder, pattern
'url' URL input same as string
'textarea' Textarea rows, maxlength, minlength, placeholder
Boolean Toggle switch
Number Number input min, max, step
choices[] without multi Drop-down list choices required
'select-multi' Multi-select choices required
'datetime' Date and time picker hideTime renders date-only
'color' Color swatch picker choices optional; defaults to a built-in palette
'reference-picker' Single record typeahead table required
'table-picker' Table name typeahead items optionally pre-seeds the list
'table-reference' Two-step: pick table, then record
'role-picker' Role typeahead over sys_user_role items optionally pre-seeds the list
'condition-builder' Condition builder modal tableName fixes the table; omit for a dynamic picker
'custom' Author-supplied element component required: the element tag name. Pilot.

Values come out of storage as JSON strings, and type drives the coercion.

Python
@config({label: 'Show SLA Fields', type: 'boolean'})showSlaFields = true;
@config({label: 'Page Size', type: 'number', min: 5, max: 100, step: 5})pageSize = 20;
@config({label: 'Default Assignment Group', type: 'reference-picker', table: 'sys_user_group'})defaultGroup = '';
@config({label: 'Notes', type: 'textarea', rows: 5, placeholder: 'Enter notes…'})notes = '';

Legacy constructor flags (referencePicker: true with table, tablePicker: true, tableReference: true, rolePicker: true): resolve to the equivalent string type values for backward compatibility. Use the explicit string type in new code; the explicit string type value is used when both are set.

Grouping a crowded panel

Give each property a group id, and optionally declare static configGroups to control order and labels:

Python
class MyWidget extends AIUXElement {  static configGroups = [    {id: 'text', label: 'Text fields'},    {id: 'selection', label: 'Selection fields'},    {id: 'data', label: 'Data fields', defaultExpanded: false}  ];
  @config({label: 'Title', type: String, group: 'text'})  title;}

Groups render in the declared order, or in first-appearance order if you omit configGroups. Each group's defaultExpanded defaults to true. Properties with no group, or with a group id not listed, render ungrouped after the accordions.

Grouping earns its keep when the pane is crowded. What matters is how far an admin has to navigate to reach the property they configure most often, not the number of groups.

Conditional fields (dependsOn)

A field can declare that it appears only when a sibling satisfies a condition.

Mode Schema Shown when
exists dependsOn: {key: 'parentProp'} Parent value is non-empty
equals dependsOn: {key: 'parentProp', value: 'exact'} Parent strictly equals value
one-of dependsOn: {key: 'parentProp', value: ['a', 'b']} Parent is in the array
CSS
@config({  label: 'Temperature',  type: Number,  dependsOn: {key: 'cookingMethod', value: ['grill', 'oven']}})temperature;

When a parent value changes, any field whose condition is no longer satisfied resets to its default, and the cascade propagates transitively. Circular dependsOn chains are detected at decoration time and logged as an error rather than thrown. The circular segment is silently ignored, which can produce unexpected auto-clear behavior.

Configuration ids for nested components

A page's resolved payload is one flat object with dot-namespaced keys: "other.button.density": "compact". A component gets its slice of that payload by carrying a configuration id.

In a region template, write the attribute.

JavaScript
// regions/home/body.jsexport default () => region`  <row>    <column span=${8}><stat-card aiux-config-id="stat-card-1" /></column>  </row>`;

The aiux-config-id attribute is the one region-DSL editor attribute the runtime does not consume and drop. It compiles to exactly the binding a hand-written elementId(host, 'stat-card-1') on aiux-config-data produces, and that directive stamps data-aiux-element-id on the rendered element. So the widget becomes an ordinary configuration entry: stored in sys_aix_configuration under <id>.<prop>, read back by the same @config getter, reset by the same call.

Three rules apply to the attribute:

  • It must be a non-empty string. A bare aiux-config-id with no value throws, because the value is used both as a DOM id and as an aiuxConfigData key prefix.
  • It is valid only on a widget tag, not on row or column.
  • Without it, the widget cannot be configured at all: see Placing Lux widgets in regions for what that looks like to an admin.

Avoid / in an id. The id travels in a URL path segment when a reset is sent, where a slash either gets rejected or normalized back into a separator. Widgets the visual editor adds are stamped <tag>__<nonce> for this reason.

In a hand-written Lit template, call the function. The elementId(this, 'id') call is the underlying mechanism, and what you write when the parent is a plain Lit render() rather than a region:

JavaScript
import {AIUXElement, elementId, config} from '@servicenow/aiux-components-core';
@customElement('some-page')class SomePage extends AIUXElement {  @config({label: 'Greeting'})  greeting = 'Hello there!';
  render() {    return html`      <h1>${this.greeting}</h1>      <other-element aiux-config-data=${elementId(this, 'other')}></other-element>    `;  }}

Either way, each element does exactly two things with its slice:

  1. Peels its own keys: assigns values whose key matches one of its own @config field names.
  2. Forwards subtrees: for every id it declares, slices all keys prefixed id., strips the prefix, and passes the rest down.

So given greeting, other.propA, and other.button.density, the page keeps greeting and forwards the other two. A page author only needs to specify their direct children's ids; the child is responsible for slicing and forwarding to its own children. The id must be unique among siblings at that level, and a child absent from the payload gets an empty object, so its @config defaults take over.

Storage key format

Element Stored key
Top-level page property propKey
Direct child (id chain ['header']) header.propKey
Grandchild (id chain ['header', 'badge']) header.badge.propKey

The tag column on the stored row is always the top-level element's custom-element name, not the child's.

Config service methods

The config service is a module, not a DOM element, and it works on top-level element tag names; it does not work on nested children directly.

Method What it does
setConfigValue(tag, key, value, options) Change the admin-layer value. Optimistic: applies immediately, rolls back if the save fails.
setPersonalization(tag, key, value, options) Change the user's own value, saved to sys_user_preference. Optimistic with rollback.
resetConfig(tag, key, options) Clear the current value, falling back to the next lower layer. {all: true} clears every layer down to the developer default.
resetPersonalization(tag, key) Clear this user's preference only.
JavaScript
import {  setPersonalization,  resetPersonalization,  resetConfig} from '@servicenow/aiux-config-service';
await setPersonalization('itsm-incident-list', 'density', 'compact');await resetPersonalization('itsm-incident-list', 'density');await resetConfig('itsm-incident-list', 'density', {all: true});

Configuration modes

@config is one of four modes a property or a whole widget can use, in increasing order of author effort.

Order Mode Declared with Editing UI Saves to
1 Independent config @config on a field The framework props pane: value field plus lock toggle per property sys_aix_configuration
2 External config @externalConfig on the class Same pane, plus a link out to a list, record, or URL The author's own table
3 Custom URL static configMode = 'inline-simple' Pane stays; a button opens an author-supplied URL in an iframe Wherever that tool saves, not through the framework
4 Self-config static configMode = 'inline-auto' The widget renders its own editor, fully interactive sys_aix_configuration, through the framework's save pipeline

Mode 1 is the default unless the widget is genuinely complex: list, form, action bar, data visualization. Modes 3 and 4 are the path for those specific widget families.

Modes combine. The configMode static accepts a comma-separated list: 'inline-auto,inline-simple', and either mode can be paired with @config decorators on the same widget. The editor then shows both the deep-link button and the props-pane pencil on one outline. A widget can also declare multiple @externalConfig entries.

See Inline configuration for each mode in detail.

Actions (@action)

@action, a decorator for exposing component methods as AI-invokable, shortcut-configurable actions, is design-stage only. Nothing described for it exists in the current codebase, and it has no entry in the customer-facing configuration documentation. Don't build against it.

Related topics

Configuration model

Inline configuration

https://raw.githubusercontent.com/ServiceNow/ServiceNowDocs/brazil/markdown/application-development/admin-experience.md

[schema-reference]

Search documentation
Search document titles and file paths on the brazil branch