Skip to content

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 ruleSet array is OR. Each conditional block is tested independently. If its criteria match, its rules apply.
  • The inner propertyCriteria array 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") declares editable: 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. Use excludedSlugs to 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 TypeProperty slugs exactly (case-sensitive). A typo is silent — the rule won't fire and the rule set is effectively a no-op for that property.
  • optionSetValues is 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: false alone 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.
  • identifier is immutable. The update endpoint silently strips identifier from 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).
  • identifier is unique per org. Creating a second Rule Set with the same identifier fails with Ruleset with this identifier already exists.
  • validationFunction overrides 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.