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: OrderDaemon\CompletionManager\Core\RuleComponents\Interfaces\ComponentInterface
- 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 null or [] if your component has no settings.
- Keep IDs stable. Saved rule JSON references your schema keys.
- Labels and descriptions must be i18n string IDs resolved with the order-daemon text domain.
- Use stable, semantic keys that won’t change between versions.
Example
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'),
'description' => __('rule_component.condition.order_total.operator_description', '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_eq' => __('rule_component.condition.order_total.operator.equal_to', 'order-daemon'),
'amount_ne' => __('rule_component.condition.order_total.operator.not_equal_to', '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_eq' => 'amount_eq_value',
'amount_ne' => 'amount_ne_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_eq_value' => [
'type' => 'number',
'minimum' => 0,
'default' => 75,
],
'amount_ne_value' => [
'type' => 'number',
'minimum' => 0,
'default' => 75,
],
'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 properties.
- properties: array — Keyed by field id.
- required: string[] — List of required property keys.
Field types
- string — With optional enum, format, pattern
- number/integer — With optional minimum/maximum, multipleOf
- boolean — Renders as a toggle/checkbox
- array — With items (type or schema), uniqueItems, minItems, maxItems
- object — Nested objects are supported for grouped settings
Common keywords
- title: i18n label for the field
- description: i18n helper text
- default: default value
- enum: fixed list of values (strings or numbers)
- enumNames: i18n labels for enum values (optional)
- minimum/maximum: numeric bounds
- pattern: regex for strings
- examples: 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') - Examples:
- Condition:
__('rule_component.condition.order_total.label', 'order-daemon') - Action:
__('rule_component.action.complete_order.description', 'order-daemon') - Trigger:
__('rule_component.trigger.payment_complete.title', 'order-daemon')
New in v2.0.0
Enhanced UI Widgets
v2.0.0 introduces several new UI widgets and improvements:
- searchable_checkboxes: A powerful widget for multi-select scenarios with search functionality
- tiered_checkboxes: Organized checkbox groups with category headers
- radio_with_inline_inputs: Radio buttons with associated inline number inputs
- Improved validation: Enhanced client-side validation with better error messages
Dynamic Schema Generation
Components can now generate schemas dynamically based on available data:
public function get_settings_schema(): ?array {
// Get available product categories dynamically
$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;
}
}
}
return [
'type' => 'object',
'properties' => [
'category' => [
'type' => 'string',
'title' => __('rule_component.condition.product_category.label', 'order-daemon'),
'enum' => $categories,
'default' => '0',
'ui:widget' => 'select',
],
],
];
}
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,multiselect,radio,checkbox,textarea,currency,number,text,code,searchable_checkboxes,tiered_checkboxes - ui:placeholder: Placeholder text (string)
- ui:help: Short helper text displayed near the input
- ui:searchable: Boolean; enables search on large selects
- ui:options: Object for widget‑specific options (e.g.,
{ asyncSource: 'categories', min: 0 }) - ui:width: Layout width hint (e.g., ‘sm’, ‘md’, ‘lg’, ‘full’)
- ui:radio_inputs: Maps radio options to inline input fields (new in v2.0.0)
New v2.0.0 Widgets
searchable_checkboxes
Perfect 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:groups' => [
'Online Payments' => [
'credit_card' => 'Credit Card',
'paypal' => 'PayPal',
],
'Offline Payments' => [
'bank_transfer' => 'Bank Transfer',
],
],
],
radio_with_inline_inputs
Create radio buttons with associated inline number inputs:
'operator' => [
'type' => 'string',
'title' => __('Operator', 'order-daemon'),
'enum' => [
'amount_gt' => 'Greater than',
'amount_lt' => 'Less than',
],
'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,
],
Patterns for common UIs
Select lists (taxonomy, product types)
- Provide enum/enumNames or ui:options.asyncSource to let the UI fetch options via REST.
- Use ui:searchable for long lists. For multi‑select, set type: ‘array’ with items: { type: ‘string’ }.
Numeric inputs (currencies, totals)
- Prefer type: ‘number’; add minimum/maximum and sensible defaults.
- For order totals, use ui:widget: ‘currency’ when available to render with the store currency.
Boolean flags
- type: ‘boolean’ renders as a toggle or checkbox; add description for clarity.
Nested groups
- Use type: ‘object’ for grouped settings; provide a title to 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:widget and handle branching inside your component at runtime.
Best Practices for v2.0.0
- Use dynamic schema generation for fields that depend on available data (product types, categories, etc.)
- Leverage new UI widgets like
searchable_checkboxesfor better user experience with large option sets - Maintain backward compatibility when updating schemas – keep old keys working or provide migrations
- Use stable i18n keys following the pattern:
__('rule_component.{component_type}.{component_id}.{field_name}', 'order-daemon') - Provide sensible defaults for all fields to ensure new components work out of the box
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.
End‑to‑end example (Condition)
public function get_settings_schema(): ?array {
// Get available product categories dynamically
$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;
}
}
}
// Add fallback if no categories found
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'),
'description' => __('rule_component.condition.product_category.description', 'order-daemon'),
'properties' => [
'operator' => [
'type' => 'string',
'title' => __('rule_component.condition.product_category.operator_label', 'order-daemon'),
'description' => __('rule_component.condition.product_category.operator_description', 'order-daemon'),
'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',
'title' => __('rule_component.condition.product_category.label', 'order-daemon'),
'description' => __('rule_component.condition.product_category.field_description', 'order-daemon'),
'enum' => $categories,
'default' => '0',
'ui:widget' => 'select',
],
],
'required' => ['category'],
];
}
Testing and troubleshooting
- Verify i18n: Ensure the order-daemon text 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.
Schema Validation Best Practices
- Test your schemas with various data inputs to ensure proper validation
- Verify that all enum values are properly translated
- Test dynamic schema generation with different data states (empty, partial, full)
- Ensure your component handles missing or invalid settings gracefully

