Configuration Rules NIDL API Reference
The Configuration Rules API lets you read and author the rules that drive and limit the configured aspects of a Configured Design in Fusion — the same rules you can build on the Rules Canvas, expressed as typed objects in code.
Preview: The Configuration Rules API is provided in preview. Its objects, methods, and properties may change in a future release.
Overview — what a configuration rule is and the object model.
Getting Started — prerequisites, reaching the manager, and the script scaffold the examples use.
Reading Rules — getRules, itemById, itemByIds.
Modifying Rules — add, update, delete, reorder, and round-trip as JSON.
Building Rule Bodies — the typed statement and condition blocks.
Columns and Values — the columns a rule can target and the value to use for each.
Issues, Errors, and Best Practices — handling problems and writing predictable rules.
See also
A configuration rule is a logical rule that limits or drives the values of the configured aspects of a Configured Design. Each rule is evaluated against every configuration in the design — the configurations in order from left to right — and the rules themselves are evaluated from top to bottom. A rule produces one of two kinds of result for a configured aspect:
In the user interface, you author rules on the Rules Canvas by connecting blocks from the Toolbox into conditional statements. The API works with the same rules and the same building blocks, but you construct them as typed objects in code. A rule you create with the API appears on the Rules Canvas, and a rule authored in the user interface can be read and modified with the API.

Access begins with the configurationRulesManager property of the Design object. It returns a ConfigurationRulesManager, or null if the design is not a Configured Design. Because rules are stored with the design rather than held in memory, each read is a discrete request, so read what you need into your own variables rather than calling the manager repeatedly. Through the manager you can:
getRules returns every rule in one request; itemById and itemByIds return specific rules.
modifyRules applies a batch of additions, updates, deletions, and reordering in a single call; addRule and deleteRule are conveniences for the single-rule cases.
Each rule is a ConfigurationRule with a name, an optional comment, an isActive flag, and a body. The body is an ordered list of typed blocks in two families — statement blocks (the actions a rule performs) and condition blocks (the tests an If block evaluates) — plus the typed values those blocks carry. See Building rule bodies for the full block reference.
In the user interface, these blocks sit inside a Rule block on the Rules Canvas that carries the rule's name and on/off state. In the API that container is the ConfigurationRule object itself — its name, isActive, and body — not a block within the body.
Reads and modifications both return a result that carries an issues array alongside the affected rules. An empty issues array means the operation was applied or read cleanly; entries describe non-fatal problems. Fatal problems are reported as an error instead (see Issues, errors, and best practices).
Configuration rules act on the columns of the configuration table's top table — not on theme tables directly, though a top-table theme column's value is a theme-table row key (see Columns and values).
Before using the API, create and edit some configuration rules through Fusion's user interface so you are familiar with how they behave; this document explains the equivalent functionality in the API. All examples are Python, intended to be run from the Fusion Script Editor (Utilities > Scripts and Add-Ins) with a Configured Design open and active.
Design.configurationRulesManager returns null. See the Configurations topic for how to create a Configured Design.Every example in this document runs inside the following scaffold, which connects to the manager and handles fatal errors. Later snippets show only the lines that matter and assume rulesManager, app, ui, and design are in scope from here.
import adsk.core, adsk.fusion, traceback
def
run(context):
ui = None
try:
app = adsk.core.Application.get()
ui = app.userInterface
design: adsk.fusion.Design = app.activeProduct
# The manager is null for a design that is not a Configured Design.
rulesManager = design.configurationRulesManager
if rulesManager is None:
ui.messageBox('The active design is not a Configured Design.')
return
# ---- example code goes here, using `rulesManager` ----
except:
# Fatal problems, such as a missing extension entitlement, arrive here.
if ui:
ui.messageBox('Failed:{}'.format(traceback.format_exc()))
All three read methods return the affected rules together with an issues array. Inspect issues even on a read — a non-empty array describes a non-fatal problem, such as a CONVERSION_WARNING encountered while reading a rule back.
getRules() returns a ConfigurationRulesResult with every rule on the design in evaluation order. Iterate its rules collection rather than indexing the manager.itemById(id) returns a ConfigurationRuleResult for a single rule. It reports an error if the id is empty or does not match an existing rule (in Python, a raised exception), so use it when you expect the rule to exist.itemByIds(ids) returns a ConfigurationRulesResult for a list of ids. A miss is reported as a RULE_NOT_FOUND entry in issues and is simply absent from rules, rather than being reported as an error — unless none of the requested ids match, in which case the call fails with an error (NO_MATCHING_RULES) instead of returning a result. An empty array returns every rule, like getRules.result = rulesManager.getRules()
app.log(f'The design has {len(result.rules)} configuration rule(s).')
for rule in result.rules:
state = 'active' if rule.isActive else 'inactive'
app.log(f' {rule.name} (id={rule.id}, {state})')
for issue in result.issues:
app.log(f' issue: {issue.code} - {issue.message}')
Each ConfigurationRule exposes its id, name, comment, isActive flag, and body. To work with the blocks in a body, see Building rule bodies.
All changes go through modifyRules, which takes a ConfigurationRulesModifyInput built with ConfigurationRulesModifyInput.create(). The input has three optional collections:
rules — the rules to add or update.deleteRuleIds — the ids of rules to delete.orderedRuleIds — the desired final order of the rules.The request is applied in a fixed order — deletes first, then reorder, then adds and updates — but it is not all-or-nothing: a per-rule or per-block problem does not roll back the rest of the batch. If an individual block fails validation, it is replaced with an empty placeholder and a VALIDATION_FAILED issue is recorded, while the rest of the rule and the batch still apply. Always inspect issues after a call. modifyRules returns a ConfigurationRulesResult whose rules collection holds the post-mutation state of the added or updated rules (in input order), which is how you recover the ids assigned to new rules.
A rule with an empty id is treated as new. Create it with ConfigurationRule.create(), populate it, and add it to the input's rules collection. (See Building rule bodies for populating body.)
newRule = adsk.fusion.ConfigurationRule.create()
newRule.name = 'Power requires table'
newRule.comment = 'Created from the API'
# newRule.body = [ ... ] # see Building rule bodies
modifyInput = adsk.fusion.ConfigurationRulesModifyInput.create()
modifyInput.rules = [newRule]
result = rulesManager.modifyRules(modifyInput)
for rule in result.rules:
app.log(f'Added "{rule.name}" with id {rule.id}.')
for issue in result.issues:
app.log(f' issue: {issue.code} - {issue.message}')
Submit a ConfigurationRule with a non-empty id. The name, comment, and isActive fields are merged individually — you can change one without affecting the others. The body, however, is replaced as a whole: setting body replaces the existing body completely. To change a single block, read the rule, modify its body, and submit the whole rule (see Building rule bodies).
# Deactivate a rule without touching anything else about it.
existing = rulesManager.itemById(ruleId).rule
existing.isActive = False
modifyInput = adsk.fusion.ConfigurationRulesModifyInput.create()
modifyInput.rules = [existing]
rulesManager.modifyRules(modifyInput)
List the ids to remove in deleteRuleIds. Deletes run before reorder and add/update. An id that does not match an existing rule is reported as a RULE_NOT_FOUND issue; the remaining ids are still deleted.
modifyInput = adsk.fusion.ConfigurationRulesModifyInput.create()
modifyInput.deleteRuleIds = [ruleId]
rulesManager.modifyRules(modifyInput)
Because rules are evaluated top to bottom, order matters when more than one rule affects the same column. Set orderedRuleIds to the desired final order. When non-empty, it must list exactly one entry for every rule that remains after the delete phase. An empty orderedRuleIds leaves the order unchanged.
currentIds = [rule.id for rule in rulesManager.getRules().rules]
modifyInput = adsk.fusion.ConfigurationRulesModifyInput.create()
modifyInput.orderedRuleIds = list(reversed(currentIds)) # reverse the current order
rulesManager.modifyRules(modifyInput)
For single-rule cases you can skip building a ConfigurationRulesModifyInput:
addRule(name) creates one rule with the given name and an empty body, and returns a ConfigurationRuleResult with the new rule and any issues.deleteRule(id) deletes one rule and returns True if a rule was deleted, or False if no rule with that id exists.added = rulesManager.addRule('Width minimum').rule
app.log(f'Added rule with id {added.id}.')
wasDeleted = rulesManager.deleteRule(added.id)
app.log(f'Deleted: {wasDeleted}')
Use a ConfigurationRulesModifyInput when you need to populate a body before submitting, or to batch several changes together.
A rule's id identifies an existing rule, so to copy a rule you detach the copy from that id. ConfigurationRule.toJson() returns a JSON string capturing the rule's full state; the static ConfigurationRule.fromJson(ruleJson) parses it back into a detached rule. Call clearId() on the copy so the next submit treats it as new.
original = rulesManager.itemById(ruleId).rule
copy = adsk.fusion.ConfigurationRule.fromJson(original.toJson())
copy.clearId()
copy.name = original.name + ' (copy)'
modifyInput = adsk.fusion.ConfigurationRulesModifyInput.create()
modifyInput.rules = [copy]
rulesManager.modifyRules(modifyInput)
Rule names are disambiguated automatically on submit — a second "Width minimum" becomes "Width minimum 2". JSON is also useful for storing a rule outside the design or exchanging rules with external tooling. Round-tripping preserves blocks this version of the API does not recognize: an unknown block kind is kept as a read-only ConfigurationRuleRawStatement or ConfigurationRuleRawCondition and re-emitted unchanged.
A rule body is an ordered list of blocks that fall into two families: statement blocks and condition blocks. Each slot in a rule accepts only one family — a rule's body and an If block's branches take statement blocks, while a condition slot takes condition blocks.
Statement blocks are the actions a rule performs. They derive from ConfigurationRuleStatement and are legal in a rule's body and in an If block's thenBlocks and elseBlocks:
ConfigurationRuleSetBlock — assigns a value to a column (drives or limits it).ConfigurationRuleIfBlock — evaluates a condition and runs statements conditionally.ConfigurationRuleCommentBlock — documents a section of the body; it does nothing when the rule runs.Condition blocks are the tests an If block evaluates. They derive from ConfigurationRuleCondition and are legal only as a condition or as the operand of another condition (an If block's condition, an If branch's condition, the left/right of an And/Or block, or the operand of a Not block):
ConfigurationRuleGetBlock — reads a column and compares it against a value.ConfigurationRuleAndBlock / ConfigurationRuleOrBlock — combine two conditions.ConfigurationRuleNotBlock — negates a condition.Both Get and Set blocks carry a value — a subclass of ConfigurationRuleBlockValue — whose class depends on the column's type and the operator; see Columns and values.
Construct every block with the static create factory on its class, set its fields, and assemble the blocks. This example builds if the width is less than 400 mm, set the shelf count to 1. Column ids come from the configuration table — see Columns and values. Numbers are in internal units, so 400 mm = 40 cm.
# The condition: width < 400 mm.
condition = adsk.fusion.ConfigurationRuleGetBlock.create()
condition.columnId = widthColumnId
condition.operatorType = adsk.fusion.ConfigurationRuleGetOperators.LessThanConfigurationRuleGetOperator
condition.value = adsk.fusion.ConfigurationRuleNumberValue.create(40.0)
# The action: set the shelf count to 1.
action = adsk.fusion.ConfigurationRuleSetBlock.create()
action.columnId = shelfCountColumnId
action.operatorType = adsk.fusion.ConfigurationRuleSetOperators.EqualsConfigurationRuleSetOperator
action.value = adsk.fusion.ConfigurationRuleNumberValue.create(1.0)
# The If block ties them together.
ifBlock = adsk.fusion.ConfigurationRuleIfBlock.create()
ifBlock.condition = condition
ifBlock.thenBlocks = [action]
rule = adsk.fusion.ConfigurationRule.create()
rule.name = 'Narrow shelves have one shelf'
rule.body = [ifBlock]
modifyInput = adsk.fusion.ConfigurationRulesModifyInput.create()
modifyInput.rules = [rule]
rulesManager.modifyRules(modifyInput)
A ConfigurationRuleIfBlock evaluates its condition and runs thenBlocks when it is true. It also carries:
elseIfBranches — an ordered list of ConfigurationRuleIfBranch objects, each with its own condition and thenBlocks.elseBlocks — statements that run when no condition is true.Evaluation follows a fixed order: the primary condition, then each branch in elseIfBranches in order; the first true branch runs its thenBlocks and no others; if none is true, elseBlocks runs.
getOps = adsk.fusion.ConfigurationRuleGetOperators
setOps = adsk.fusion.ConfigurationRuleSetOperators
def
widthLessThan(cm):
block = adsk.fusion.ConfigurationRuleGetBlock.create()
block.columnId = widthColumnId
block.operatorType = getOps.LessThanConfigurationRuleGetOperator
block.value = adsk.fusion.ConfigurationRuleNumberValue.create(cm)
return block
def
setShelves(count):
block = adsk.fusion.ConfigurationRuleSetBlock.create()
block.columnId = shelfCountColumnId
block.operatorType = setOps.EqualsConfigurationRuleSetOperator
block.value = adsk.fusion.ConfigurationRuleNumberValue.create(count)
return block
ifBlock = adsk.fusion.ConfigurationRuleIfBlock.create()
ifBlock.condition = widthLessThan(40.0) # < 400 mm
ifBlock.thenBlocks = [setShelves(1.0)]
branch = adsk.fusion.ConfigurationRuleIfBranch.create()
branch.condition = widthLessThan(80.0) # < 800 mm
branch.thenBlocks = [setShelves(2.0)]
ifBlock.elseIfBranches = [branch]
ifBlock.elseBlocks = [setShelves(3.0)] # otherwise
ConfigurationRuleAndBlock is true when both its left and right operands are true; ConfigurationRuleOrBlock is true when either is; ConfigurationRuleNotBlock is true when its operand is false. Operands are themselves condition blocks, so you can nest them to any depth.
# width >= 400 mm AND height >= 600 mm
widthOk = adsk.fusion.ConfigurationRuleGetBlock.create()
widthOk.columnId = widthColumnId
widthOk.operatorType = getOps.GreaterThanOrEqualConfigurationRuleGetOperator
widthOk.value = adsk.fusion.ConfigurationRuleNumberValue.create(40.0)
heightOk = adsk.fusion.ConfigurationRuleGetBlock.create()
heightOk.columnId = heightColumnId
heightOk.operatorType = getOps.GreaterThanOrEqualConfigurationRuleGetOperator
heightOk.value = adsk.fusion.ConfigurationRuleNumberValue.create(60.0)
bothOk = adsk.fusion.ConfigurationRuleAndBlock.create()
bothOk.left = widthOk
bothOk.right = heightOk
When you read a rule, its body contains the same block objects typed as their abstract base classes. Use the cast method on a concrete class to work with a block as its specific type; cast returns None if the block is not of that type, which is how you branch on block kind.
for block in rulesManager.itemById(ruleId).rule.body:
ifBlock = adsk.fusion.ConfigurationRuleIfBlock.cast(block)
if ifBlock is
not
None:
getBlock = adsk.fusion.ConfigurationRuleGetBlock.cast(ifBlock.condition)
if getBlock is
not
None:
app.log(f'If block reads column {getBlock.columnId}.')
continue
setBlock = adsk.fusion.ConfigurationRuleSetBlock.cast(block)
if setBlock is
not
None:
app.log(f'Set block writes column {setBlock.columnId}.')
A Get or Set block with no operator, no value, or no columnId is an unconfigured placeholder: it is valid to store but does nothing until completed. A block whose columnId cannot be resolved, or whose operator or value is not valid for the column, is also reduced to a placeholder on submit and records a VALIDATION_FAILED issue. The read-only ConfigurationRuleRawStatement and ConfigurationRuleRawCondition wrappers hold block kinds a newer Fusion produced that this version does not recognize; they round-trip unchanged and are never constructed directly.
A Get or Set block targets a column of the configuration table by its columnId, which is the id of a ConfigurationColumn. Never invent a columnId — obtain it from the top table (below). A columnId Fusion does not recognize at all, or an operator or value not valid for the column, is reduced to a placeholder with a VALIDATION_FAILED issue. A columnId Fusion recognizes but does not support for rules (see Known limitations) is reduced the same way but reported as COLUMN_NOT_SUPPORTED instead, so you can tell a typo apart from a column that will never work. Neither case raises an error, so always inspect issues after submitting.
Each column has a leaf type that determines the value class a block must carry:
| Leaf type | Value class |
|---|---|
| number | ConfigurationRuleNumberValue (or ConfigurationRuleGetRangeValue / ConfigurationRuleSetRangeValue for ranges, or ConfigurationRuleNumberSelectionValue for AnyOf/NoneOf) |
| text | ConfigurationRuleStringValue |
| boolean | ConfigurationRuleBooleanValue |
| dropdown / list | ConfigurationRuleStringListValue (a ConfigurationRuleStringValue is accepted on write and reads back as a list) |
Column ids and the valid values for list columns come from Design.configurationTopTable, not from the rules API. Iterate its columns; every ConfigurationColumn has an id, a title, and a type you can test with isinstance.
top = design.configurationTopTable
for column in top.columns:
columnId = column.id # use this as the block's columnId
if isinstance(column, adsk.fusion.ConfigurationParameterColumn):
app.log(f'{columnId}: parameter "{column.parameter.name}"')
elif isinstance(column, adsk.fusion.ConfigurationSuppressColumn):
app.log(f'{columnId}: suppression of "{column.feature.name}"')
elif isinstance(column, adsk.fusion.ConfigurationVisibilityColumn):
app.log(f'{columnId}: visibility of "{column.entity.name}"')
elif isinstance(column, adsk.fusion.ConfigurationInsertColumn):
app.log(f'{columnId}: insert of "{column.occurrence.name}"')
elif isinstance(column, adsk.fusion.ConfigurationThemeColumn):
app.log(f'{columnId}: theme column')
For a dropdown column (a theme column or an insert column) the value you supply is an option key — the id of a ConfigurationRow in the referenced table, not its display name. Enumerate the referenced table's rows to get the keys:
for theme in top.customThemeTables:
for i in range(theme.rows.count):
row = theme.rows.item(i)
app.log(f' option key {row.id} = "{row.name}"')
The Configuration Name aspect has no physical column; address it with the fixed id "CONFIGURATION_NAME", and its valid values are the top table's row names.
| Column type | Controls | Read | Write | Value class |
|---|---|---|---|---|
(numeric) | A numeric parameter | ✅ | ✅ | ConfigurationRuleNumberValue; range values for ranges |
ConfigurationParameterColumn (text) | A text parameter | ✅ | ✅ (Equals only) | ConfigurationRuleStringValue |
ConfigurationSuppressColumn | Feature/occurrence suppression | ✅ | ✅ | ConfigurationRuleBooleanValue |
ConfigurationVisibilityColumn | Entity visibility | ✅ | ✅ | ConfigurationRuleBooleanValue |
ConfigurationThemeColumn | Active theme-table row | ✅ | ✅ | ConfigurationRuleStringListValue (option keys) |
ConfigurationInsertColumn | Inserted configuration row | ✅ | ✅ | ConfigurationRuleStringListValue (option keys) |
Configuration Name ("CONFIGURATION_NAME" | The configuration's name | ✅ | ❌ | ConfigurationRuleStringValue |
| Joint feature aspects | A joint's configurable aspects | ✅ | ✅ | ConfigurationRuleBooleanValue / ConfigurationRuleStringListValue |
The operator must be valid for the column's leaf type. Set the Unknown operator to leave a block as an unconfigured placeholder. Each name is a value of ConfigurationRuleGetOperators or ConfigurationRuleSetOperators, suffixed with the operator kind — for example LessThanConfigurationRuleGetOperator and EqualsConfigurationRuleSetOperator.
| Leaf type | Get operators | Set operators |
|---|---|---|
| number | Equals, NotEquals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, Range, AnyOf, NoneOf | Equals, Minimum, Maximum, Range, AnyOf |
| text | Equals, NotEquals, Contains, NotContains | Equals |
| boolean | Equals, NotEquals | Equals, NotEquals |
| dropdown / list | Equals, NotEquals | Equals, NotEquals |
40.01.5708
ConfigurationRuleStringListValue, even when a single value was written with a ConfigurationRuleStringValue, so prefer the list form.True means the feature is present (not suppressed); for a visibility column, True means visible.# Dropdown/theme values take a LIST of option keys, even for a single choice:
themeValue = adsk.fusion.ConfigurationRuleStringListValue.create([optionKey])
# Boolean values follow the aspect's state:
suppressValue = adsk.fusion.ConfigurationRuleBooleanValue.create(True)
On a Get block, the Range operator tests whether the column's current value falls within [min, max]. Use a ConfigurationRuleGetRangeValue.create(hasMin, min, hasMax, max); a bound that is not set is open on that side.
# True when the width is between 400 mm and 900 mm, inclusive.
inRange = adsk.fusion.ConfigurationRuleGetBlock.create()
inRange.columnId = widthColumnId
inRange.operatorType = getOps.RangeConfigurationRuleGetOperator
inRange.value = adsk.fusion.ConfigurationRuleGetRangeValue.create(True, 40.0, True, 90.0)
On a Set block, the Range operator constrains the column with a lower bound, an upper bound, an increment, or any combination. Use ConfigurationRuleSetRangeValue.create(hasMin, min, hasMax, max, hasIncrement, increment). Each of the three parts — min, max, and increment — is independently optional, and at least one must be set. Only the parts you set take effect: min is the lowest value the user can choose, max the highest, and increment the step between the allowed values in between. When both min and max are set, min must not be greater than max.
# Constrain the width to 400-900 mm in 5 mm steps.
fullRange = adsk.fusion.ConfigurationRuleSetBlock.create()
fullRange.columnId = widthColumnId
fullRange.operatorType = setOps.RangeConfigurationRuleSetOperator
fullRange.value = adsk.fusion.ConfigurationRuleSetRangeValue.create(True, 40.0, True, 90.0, True, 0.5)
A Set block can also use Minimum and Maximum with a plain ConfigurationRuleNumberValueRange expresses the same intent in one block and can carry an increment, so prefer Range. Do not use a get-range value on a Set block or a set-range value on a Get block — the two classes differ (only the set range has an increment), and each belongs with its own operator.
Where Range tests or constrains a span of numbers, AnyOf and NoneOf test or constrain a number column against a discrete set of candidate values. On a Get block, AnyOf is true when the column's current value equals one of the selected entries; NoneOf is true when it equals none of them. On a Set block, AnyOf limits the column to the selected entries — there is no set-side NoneOf; use AnyOf whenever a Set block needs to offer a discrete list of numbers.
Both operators carry a ConfigurationRuleNumberSelectionValue, built from an array of ConfigurationRuleNumberSelectionEntry objects assigned to its entries property. Each entry pairs a numeric value (Fusion internal units) with its own isSelected flag, so an entry can be carried in the block without being part of the current selection. At least one entry must be selected; an all-false selection is rejected.
# True when the width is 400 mm, 600 mm, or 900 mm.
anyOf = adsk.fusion.ConfigurationRuleGetBlock.create()
anyOf.columnId = widthColumnId
anyOf.operatorType = getOps.AnyOfConfigurationRuleGetOperator
anyOf.value = adsk.fusion.ConfigurationRuleNumberSelectionValue.create()
anyOf.value.entries = [
adsk.fusion.ConfigurationRuleNumberSelectionEntry.create(40.0, True),
adsk.fusion.ConfigurationRuleNumberSelectionEntry.create(60.0, True),
adsk.fusion.ConfigurationRuleNumberSelectionEntry.create(90.0, True),
]
NoneOf reads the same way with NoneOfConfigurationRuleGetOperator; to limit a Set block to the same kind of candidate list, use AnyOfConfigurationRuleSetOperator with a ConfigurationRuleNumberSelectionValue built the same way.
The supported-columns table above lists what a rule can read and write today. A few limitations are worth calling out:
COLUMN_NOT_SUPPORTED issue rather than the generic VALIDATION_FAILED, so you can tell "this will never work" apart from a typo'd or otherwise unrecognized id (see Issues, errors, and best practices).ConfigurationPropertyColumn) and expression columns cannot be read or written.Equals; Contains and NotContains exist only on Get blocks. Set NotEquals is valid only on boolean and dropdown columns.NoneOf has no Set-side counterpart — it exists only as a Get operator on number columns; use AnyOf to constrain a Set block to a candidate list.The API distinguishes two kinds of problem: non-fatal problems, reported as issues on the returned result, and fatal problems, reported as errors. An operation can succeed overall while a part of it silently degrades, so understanding the difference matters.
Non-fatal problems become issues. When a single block or rule in a request has a problem, the API records an issue and continues: the offending block is reduced to an empty placeholder and the rest of the rule and the batch still apply. The batch is not rolled back. Inspect the issues collection after every call, including reads and apparently successful writes.
Fatal problems are reported as an error. An invalid request shape, an uninitialised rules pipeline, or a missing extension entitlement on a write is reported as an error rather than returning a result. In Python the error is raised as an exception (a RuntimeError), so wrap calls in try/except; in C++ the call does not throw — retrieve the error with Application::getLastError.
Every issue is a ConfigurationRulesIssue with a code, a message (developer-facing, not localized), a ruleId, a columnId, a path (such as rules[0].body[2]), and a ruleIndex guarded by hasRuleIndex. To correlate an issue back to a rule in your request, use ruleIndex (when hasRuleIndex is True) rather than parsing path.
| Code | Meaning |
|---|---|
VALIDATION_FAILED | A Get or Set block did not pass validation and was replaced with a placeholder — e.g. a columnId Fusion does not recognize at all, or an operator/value not valid for the column. |
COLUMN_NOT_SUPPORTED | A Get or Set block targeted a columnId that Fusion recognizes but does not support for rules (see <a href="#Known-limitations">Known limitations</a>) — distinct from VALIDATION_FAILED, which covers columns Fusion does not recognize at all. The block is replaced with a placeholder the same way. |
RULE_NOT_FOUND | An id in deleteRuleIds, or requested through itemByIds, did not match an existing rule. |
DELETE_FAILED | A rule could not be deleted. |
CREATE_FAILED | A new rule could not be created. |
CONVERSION_WARNING | A non-fatal problem occurred while reading a rule back. |
EXTENSION_REQUIRED | A write was attempted without the Fusion Design Extension. |
Treat any code you do not recognize as an opaque string — future versions may add more.
result = rulesManager.modifyRules(modifyInput)
if
not result.issues:
app.log('Applied cleanly.')
for issue in result.issues:
location = f'rule {issue.ruleIndex}'
if issue.hasRuleIndex else
'request'
app.log(f'{issue.code} at {location} (column "{issue.columnId}"): {issue.message}')
comment, and use ConfigurationRuleCommentBlock entries to document sections of a body.columnId is ignored when the rule runs; inspect issues to find placeholders.elseIfBranches over nested If blocks, and prefer a Range or a multi-value ConfigurationRuleStringListValue over nested And/Or blocks — a range for a span of numbers, a multi-entry list to accept several dropdown options.Rules are evaluated top to bottom, and the statements within a rule run in order. When more than one rule or block affects the same column, order determines the outcome, so use orderedRuleIds to control it deliberately. When multiple Set blocks limit the same column under the same condition, the result is the intersection of their value lists — you can narrow a list this way but not widen it. If two rules leave no value in common, the configuration has no valid value for that aspect, which is an error; lead with the least restrictive condition and review rules that constrain the same aspect.
A rule can both read and write the configuration, but avoid cycles that never settle. A cycle arises when rules read and write each other's columns in a loop — for example, Rule 1 sets Width from Length while Rule 2 sets Length from Width — so the values oscillate rather than settle. Avoid reading and writing the same column within one rule unless the write immediately satisfies the read, and structure rules so their conditions read from different columns than the ones they set.