Rule Sets
Rule Sets let you declaratively control whether a property is editable, required, what option values are allowed in a dropdown, and what min/max range is permitted on a number — based on the current state of the entity. A Rule Set is attached to a Type and evaluated by the UI when an entity of that Type is opened or edited.
Use a Rule Set when the constraints on a property need to change based on the value of other properties. Use a Formula when a value needs to be computed, and a Validation Function when a save needs to be blocked with a message.
When to use a Rule Set vs. something else¶
| You need to… | Use |
|---|---|
| Lock / unlock / require a field when another field equals X | Rule Set |
| Narrow the allowed option values of a dropdown based on another property | Rule Set (optionSetValues) |
| Enforce a min/max on a number when some condition is met | Rule Set (minValue / maxValue) |
| Compute a property's value from other properties | Formula |
| Block a save with a custom error message (e.g. block publishing a Plan until invalid data is corrected) | Validation Function |
| Fixed parent → child cascade between dropdowns | Option Set Hierarchy |
Rule Sets and Validation Functions can both declare required-ness. Prefer Rule Sets for state-dependent required-ness (because they also handle editability in the same place), and Validation Functions for always-on checks where you need a custom error message.
Structure of a Rule Set¶
A Rule Set is a JSON document with three top-level fields — name, identifier, and a ruleSet array. Each entry in the array is one conditional block — a criteria describing when the rules apply, and a rules array describing what happens to which properties when they do.
{
"name": "my-rule-set",
"identifier": "my-rule-set-id",
"ruleSet": [
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "status" },
"filterConditionType": "equals",
"criteriaValue": "draft"
}
]
},
"rules": [
{ "slug": "msrp", "editable": false }
]
}
]
}
The top-level name and identifier fields are used by the CLI (and by Configuration Copy exports). When pasting into the Admin Console, only the contents of the ruleSet array are rendered — name and identifier are set via the Admin Console UI itself, not the JSON body. Both are required at creation. identifier is immutable — if you include it in an update payload, the service silently strips it before applying the rest of your changes.
Rule properties — what each one does¶
A TypeRule entry inside the rules array targets one property (by slug) and can apply any combination of the following.
| Property | Type | Effect |
|---|---|---|
slug |
string |
The slug of the property the rule applies to. Use "ALL" to apply the rule to every property on the type. |
excludedSlugs |
string[] |
Only valid when slug is "ALL". Slugs listed here are not affected by the rule. |
editable |
boolean |
false locks the property under this criteria. true is the default — you rarely need to write it. |
required |
boolean |
true marks the property as required under this criteria. Save is blocked if empty. |
optionSetValues |
string[] |
Allow-list of option values (use the option's value, not its display label). Values outside this list won't appear in the picker. |
invalidValues |
string[] |
Explicit deny-list of values for this property under this criteria. |
minValue |
number |
Minimum allowed numeric value under this criteria. |
maxValue |
number |
Maximum allowed numeric value under this criteria. |
validationFunction |
string |
A validation function body that overrides the property's default validation under this criteria. |
editable: true is redundant
The default is editable. Only write editable: false to lock a property.
Filter condition types¶
filterConditionType in propertyCriteria uses these exact strings (lowercase, underscore-separated):
equals | not_equal_to
starts_with | ends_with | contains
less_than | greater_than | less_than_or_equal | greater_than_or_equal
is_any_of | is_none_of
is_empty | is_not_empty
is_in_list | are_any_empty
Which condition types are valid depends on the property type you're filtering on:
| Property type | Valid conditions |
|---|---|
Number, Sequence, Currency, Formula, Percent |
equals, not_equal_to, less_than, greater_than, greater_than_or_equal, less_than_or_equal, is_empty, is_not_empty |
Text, String, ObjectReference, UserList |
equals, not_equal_to, contains, is_any_of, is_none_of, starts_with, ends_with, is_empty, is_not_empty, is_in_list |
SingleSelect |
equals, not_equal_to, is_any_of, is_none_of, is_empty, is_not_empty |
MultiSelect |
equals, is_any_of, is_none_of, is_empty, is_not_empty |
Boolean |
equals, not_equal_to |
Date |
equals, not_equal_to, greater_than, less_than, greater_than_or_equal, less_than_or_equal, is_empty, is_not_empty |
Image |
is_empty, is_not_empty |
SizeRange |
is_any_of, is_none_of, is_empty, is_not_empty |
TypeReference |
equals, not_equal_to, is_any_of, is_none_of, is_empty, is_not_empty |
How a Rule Set is evaluated¶
- The outer
ruleSetarray is OR. Each conditional block is tested independently. If its criteria match, its rules apply. - The inner
propertyCriteriaarray is AND. All conditions in a block must match for the block to fire. - Editability is "locked wins". A property is considered NOT editable if any rule that applies to it (directly or via
"ALL") declareseditable: false. If no rule locks it, it stays editable. - Validation results are accumulated. Required / min / max / optionSetValues / invalidValues from all matching rules are combined into the property's effective constraint set.
"ALL"applies a rule to every property on the type. UseexcludedSlugsto carve out exceptions.
Handling "Only editable WHEN X" Logic¶
The Rule Set language has no "editable only when" construct. You describe the conditions under which the property should be locked, and set editable: false in the rule.
Requirement: "setNumberOfPcs is editable when styleCategory = 'sets'."
Pattern: lock it when styleCategory is anything else.
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "styleCategory" },
"filterConditionType": "not_equal_to",
"criteriaValue": "sets"
}
]
},
"rules": [{ "slug": "setNumberOfPcs", "editable": false }]
}
When the property should be editable under multiple OR branches, write one entry in the outer ruleSet array per branch, each describing the NOT of that branch. A property becomes locked as soon as any matching entry applies editable: false to it, so each lock branch must only match when none of the intended-editable conditions hold.
Requirement: "setNumberOfPcs is editable when styleCategory = 'sets' OR (styleClass = 'giftBoxSet' AND productSegment = 'softAccessories')."
{
"name": "Set # of Pieces",
"ruleSet": [
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "productSegment" },
"filterConditionType": "not_equal_to",
"criteriaValue": "softAccessories"
},
{
"filterPropertyDefinition": { "slug": "styleCategory" },
"filterConditionType": "not_equal_to",
"criteriaValue": "sets"
}
]
},
"rules": [{ "slug": "setNumberOfPcs", "editable": false }]
},
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "styleClass" },
"filterConditionType": "not_equal_to",
"criteriaValue": "giftBoxSet"
},
{
"filterPropertyDefinition": { "slug": "styleCategory" },
"filterConditionType": "not_equal_to",
"criteriaValue": "sets"
}
]
},
"rules": [{ "slug": "setNumberOfPcs", "editable": false }]
}
]
}
Read it as: "lock when (productSegment ≠ softAccessories AND styleCategory ≠ sets)" OR "lock when (styleClass ≠ giftBoxSet AND styleCategory ≠ sets)". Negating both: editable when styleCategory = sets, OR when productSegment = softAccessories AND styleClass = giftBoxSet.
Common patterns¶
Lock a property when another property is empty¶
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "merchSizeGroup" },
"filterConditionType": "is_empty"
}
]
},
"rules": [{ "slug": "merchDivisionCode", "editable": false }]
}
Require a property when a flag is set¶
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "status" },
"filterConditionType": "equals",
"criteriaValue": "ACTIVE"
}
]
},
"rules": [{ "slug": "msrp", "required": true }]
}
Narrow a dropdown's allowed values¶
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "productSegment" },
"filterConditionType": "equals",
"criteriaValue": "softAccessories"
}
]
},
"rules": [
{ "slug": "styleClass", "optionSetValues": ["giftBoxSet", "giftCard"] }
]
}
Clamp a number¶
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "tier" },
"filterConditionType": "equals",
"criteriaValue": "basic"
}
]
},
"rules": [{ "slug": "targetMarkup", "minValue": 1.5, "maxValue": 2.5 }]
}
Lock every property except one when in read-only state¶
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "state" },
"filterConditionType": "equals",
"criteriaValue": "READ_ONLY"
}
]
},
"rules": [
{ "slug": "ALL", "excludedSlugs": ["internalNotes"], "editable": false }
]
}
Override a property's validation function under one condition¶
If a property's default validationFunction isn't strict enough in some state, override it for that criteria block:
{
"criteria": {
"propertyCriteria": [
{
"filterPropertyDefinition": { "slug": "region" },
"filterConditionType": "equals",
"criteriaValue": "EU"
}
]
},
"rules": [
{
"slug": "msrp",
"validationFunction": "if (obj.msrp < 1) return [{message:'EU MSRP must be ≥ 1', type:'ERROR'}]; return [];"
}
]
}
The override replaces (does not merge with) the property's default validation for that block.
Authoring from a CSV or table of requirements¶
A common shape for a Rule Set specification is a table — one row per (property, condition combination, constraint). The mistake to avoid is generating one rule set entry per row. Instead, pool rows by their unique condition combination and emit one entry in the outer ruleSet array per unique combination, with all affected properties listed in its rules array.
Example. Given this spec table:
| Property | When productSegment |
When styleCategory |
Effect |
|---|---|---|---|
setNumberOfPcs |
softAccessories |
sets |
editable: true |
setNumberOfPcs |
(any other) | (any other) | editable: false |
giftPackagingType |
softAccessories |
sets |
editable: true, required: true |
giftPackagingType |
(any other) | (any other) | editable: false |
There is one unique lock condition (productSegment ≠ softAccessories AND styleCategory ≠ sets) — so one outer ruleSet entry covering both properties:
{
"criteria": {
"propertyCriteria": [
{ "filterPropertyDefinition": { "slug": "productSegment" }, "filterConditionType": "not_equal_to", "criteriaValue": "softAccessories" },
{ "filterPropertyDefinition": { "slug": "styleCategory" }, "filterConditionType": "not_equal_to", "criteriaValue": "sets" }
]
},
"rules": [
{ "slug": "setNumberOfPcs", "editable": false },
{ "slug": "giftPackagingType", "editable": false }
]
}
Then add a second outer entry for the always-on required: true on giftPackagingType (criteria can be empty or the always-true case if the property must always be required when not locked).
The rule: one outer ruleSet entry per unique condition combination, not per row. Two properties with the same lock condition collapse into one entry — keeping the JSON readable and the rule set easier to update later.
Gotchas¶
- Outer array is OR, inner is AND. Mixing these up produces rules that never fire or fire always.
- Slugs must match
TypePropertyslugs exactly (case-sensitive). A typo is silent — the rule won't fire and the rule set is effectively a no-op for that property. optionSetValuesis an allow-list, not a filter. Values outside the list are completely removed from the picker, not just hidden.- Rule Sets evaluate on UI render — not server-side. An API write that violates a required / min / max constraint will only be blocked if the relevant server-side validator runs (which depends on the service). Do not rely on a Rule Set as the sole integrity guard for API traffic.
- Interaction with Option Set Hierarchies. If the property being locked is either a parent or a child in an Option Set Hierarchy,
editable: falsealone is often not enough — a pre-existing value that the new cascade now considers invalid will stay in the data and surface as an OSH validation error on the next save. Pair the Rule Set with either a formula that clears the value when the lock criteria fire, or a one-time migration that resets orphaned values before the Rule Set is enabled. identifieris immutable. The update endpoint silently stripsidentifierfrom incoming payloads, so attempts to rename a Rule Set's identifier are no-ops. To change it, delete and recreate the Rule Set (and update anything that references the old identifier).identifieris unique per org. Creating a second Rule Set with the same identifier fails withRuleset with this identifier already exists.validationFunctionoverrides replace, not merge. If a property already has a rich default validation function, a rule-block override completely replaces it for that block.
Applying the Rule Set¶
Paste the JSON into the Type Rule Set configuration in the Admin Console, attached to the target Type. Test in a non-prod org before deploying to production — rule sets affect the editor experience for every user of that Type.