This guide explains how to define component settings schemas that the Rule Builder UI can render, how to add UI metadata, and how to structure component settings.
What is a settings schema?
Every Trigger, Condition, and Action can declare a settings schema via:
- Interface:
OrderDaemonCompletionManagerCoreRuleComponentsInterfacesComponentInterface - Method:
get_settings_schema(): ?array
The schema is a PHP array that uses a JSON-schema-like structure. The Rule Builder consumes this schema to render fields, validate input, and persist configuration.
General rules:
- Return
nullor[]if your component has no settings. - Keep IDs stable. Saved rule JSON references your schema keys.
- Labels and descriptions must use i18n string IDs resolved with the
order-daemontext domain. - Use stable, semantic keys that won’t change between versions.
public function get_settings_schema(): ?array {
return [
'type' => 'object',
'title' => __('rule_component.condition.order_total.label', 'order-daemon'),
'description' => __('rule_component.condition.order_total.description', 'order-daemon'),
'properties' => [
'operator' => [
'type' => 'string',
'title' => __('rule_component.condition.order_total.operator_label', 'order-daemon'),
'enum' => [
'amount_gt' => __('rule_component.condition.order_total.operator.greater_than', 'order-daemon'),
'amount_lt' => __('rule_component.condition.order_total.operator.less_than', 'order-daemon'),
'amount_gte' => __('rule_component.condition.order_total.operator.greater_than_equal', 'order-daemon'),
'amount_lte' => __('rule_component.condition.order_total.operator.less_than_equal', 'order-daemon'),
],
'ui:radio_inputs' => [
'amount_gt' => 'amount_gt_value',
'amount_lt' => 'amount_lt_value',
'amount_gte' => 'amount_gte_value',
'amount_lte' => 'amount_lte_value',
],
'default' => 'amount_gt',
],
'amount_gt_value' => ['type' => 'number', 'minimum' => 0, 'default' => 100],
'amount_lt_value' => ['type' => 'number', 'minimum' => 0, 'default' => 50],
'amount_gte_value' => ['type' => 'number', 'minimum' => 0, 'default' => 100],
'amount_lte_value' => ['type' => 'number', 'minimum' => 0, 'default' => 50],
],
'required' => ['operator'],
];
}Supported types and common keywords
Root container:
type: object– most component schemas use an object root with propertiesproperties: array– keyed by field IDrequired: string[]– list of required property keys
Field types:
string– with optionalenum,format,patternnumber/integer– with optionalminimum/maximum,multipleOfboolean– renders as a toggle/checkboxarray– withitems(type or schema),uniqueItems,minItems,maxItemsobject– nested objects supported for grouped settings
Common keywords:
title– i18n label for the fielddescription– i18n helper textdefault– default valueenum– fixed list of values (strings or numbers)enumNames– i18n labels for enum values (optional)minimum/maximum– numeric boundspattern– regex for stringsexamples– example values for docs/tooling
i18n guidance: Always wrap labels/descriptions in translation functions with text domain order-daemon. Use stable string keys following the pattern: __('rule_component.{component_type}.{component_id}.{field_name}', 'order-daemon').
UI metadata (ui:* hints)
The Rule Builder supports ui:* hints to influence rendering. These are advisory; the editor may fall back to a reasonable default if a widget is unknown.
Common UI hints
ui:widget– choose a widget:select,textarea,number,text,searchable_checkboxes,tiered_checkboxes,button_radio_group,date_picker,time_pickerui:placeholder– placeholder text (string)ui:searchable– boolean; enables search on large selectsui:radio_inputs– maps radio options to inline input fields
searchable_checkboxes
For large multi-select scenarios like product types:
'product_types' => [
'type' => 'array',
'title' => __('rule_component.condition.product_type.field_label', 'order-daemon'),
'items' => ['type' => 'string', 'enum' => $product_types],
'ui:widget' => 'searchable_checkboxes',
'ui:searchable' => true,
'ui:placeholder' => __('Search product types...', 'order-daemon'),
'default' => ['virtual', 'downloadable'],
],tiered_checkboxes
Organize options into logical groups:
'payment_methods' => [
'type' => 'array',
'title' => __('Payment Methods', 'order-daemon'),
'items' => [
'type' => 'string',
'enum' => [
'credit_card' => 'Credit Card',
'paypal' => 'PayPal',
'bank_transfer' => 'Bank Transfer',
],
],
'ui:widget' => 'tiered_checkboxes',
'ui:tiers' => [
'online' => ['types' => ['credit_card', 'paypal']],
'offline' => ['types' => ['bank_transfer']],
],
],button_radio_group
Renders an enum field as a compact button-group (pill buttons). Ideal for short option lists like operators or match modes. Used by ProductCategoryCondition, ProductTypeCondition, and others.
'operator' => [
'type' => 'string',
'title' => __('rule_component.condition.product_category.operator_label', 'my-plugin'),
'enum' => [
'in' => __('Contains any of', 'my-plugin'),
'not_in' => __('Does not contain', 'my-plugin'),
'all_in' => __('Contains all of', 'my-plugin'),
],
'default' => 'in',
'ui:widget' => 'button_radio_group',
],radio_with_inline_inputs
Renders radio buttons where each option has an associated inline number input. The ui:radio_inputs map points each enum value to a sibling property key that holds its number. Used by OrderTotalAmountCondition.
'operator' => [
'type' => 'string',
'title' => __('Operator', 'my-plugin'),
'enum' => [
'amount_gt' => __('Greater than', 'my-plugin'),
'amount_lt' => __('Less than', 'my-plugin'),
],
'ui:radio_inputs' => [
'amount_gt' => 'amount_gt_value',
'amount_lt' => 'amount_lt_value',
],
'default' => 'amount_gt',
],
'amount_gt_value' => ['type' => 'number', 'minimum' => 0, 'default' => 100],
'amount_lt_value' => ['type' => 'number', 'minimum' => 0, 'default' => 50],date_picker / time_picker Pro
Available in Pro components. Render a native date or time input. Used by TimingCondition.
'date' => [
'type' => 'string',
'title' => __('Date', 'my-plugin'),
'ui:widget' => 'date_picker',
],
'time' => [
'type' => 'string',
'title' => __('Time', 'my-plugin'),
'ui:widget' => 'time_picker',
],Patterns for common UIs
Select lists (taxonomy, product types):
- Provide
enum/enumNameswith values populated dynamically inget_settings_schema(). - Use
ui:searchablefor long lists. For multi-select, settype: 'array'withitems: { type: 'string' }.
Numeric inputs (currencies, totals):
- Prefer
type: 'number'; addminimum/maximumand sensible defaults.
Boolean flags:
type: 'boolean'renders as a toggle or checkbox; add a description for clarity.
Nested groups:
- Use
type: 'object'for grouped settings; provide atitleto render a sub-section.
Conditional fields:
- Keep schemas static and handle conditional show/hide in UI hints where possible.
- If you must switch fields based on another field’s value, prefer a single field with
enum+ui:widgetand handle branching inside your component at runtime.
Dynamic schema example
public function get_settings_schema(): ?array {
$categories = [];
if (function_exists('get_terms')) {
$terms = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false]);
if (!is_wp_error($terms)) {
foreach ($terms as $term) {
$categories[$term->term_id] = $term->name;
}
}
}
if (empty($categories)) {
$categories = ['0' => __('rule_component.condition.product_category.no_categories_found', 'order-daemon')];
}
return [
'type' => 'object',
'title' => __('rule_component.condition.product_category.label', 'order-daemon'),
'properties' => [
'operator' => [
'type' => 'string',
'enum' => [
'in' => __('rule_component.condition.product_category.operator.in', 'order-daemon'),
'not_in' => __('rule_component.condition.product_category.operator.not_in', 'order-daemon'),
'all_in' => __('rule_component.condition.product_category.operator.all_in', 'order-daemon'),
],
'default' => 'in',
],
'category' => [
'type' => 'string',
'enum' => $categories,
'default' => '0',
'ui:widget' => 'select',
],
],
'required' => ['category'],
];
}Validation and defaults
- The editor performs light validation using your schema (required, enum, min/max). Perform server-side validation in your REST/controller or component logic.
- Always set defaults so new fields don’t break existing saved rules. Never remove or rename keys without a migration path.
- For arrays with enum values, ensure payload sanitization on save.
- Use the
sanitize_by_schemamethod in the Evaluator class for consistent validation.
Schema migrations
Settings are persisted as JSON in saved rules. If you change your schema after rules have been saved, follow these rules to avoid breaking existing rules:
- Adding a field – always provide a
default. The Evaluator callssanitize_by_schema()before passing settings toevaluate(), which fills missing keys with their defaults. - Renaming a key – keep the old key working in
evaluate()by aliasing it:$value = $settings['new_key'] ?? $settings['old_key'] ?? $default;. Remove the alias once you’re confident no saved rules reference the old key. - Removing an enum value – never remove a value that may exist in saved rules. Add a fallback branch in
evaluate()that handles the removed value gracefully. - Removing a field entirely – safe to do in the schema (the UI will stop showing it), but keep handling the key in
evaluate()for backwards compatibility.
The sanitize_by_schema() method on Evaluator strips unknown keys and fills missing keys with defaults. Rely on it rather than writing your own sanitization.
Testing and troubleshooting
- Verify i18n: ensure the
order-daemontext domain is loaded and JSON script translations are registered for the Rule Builder assets. - Backwards compatibility: when changing schemas, keep old keys working or add a migration. Avoid removing enum values that may exist in saved rules.
- Performance: large async selects should page results server-side; avoid heavy synchronous PHP in options generation.