Operations
What each of the 22 property operations does, which property kinds support it, and one working example apiece.
Every entry in a type: cdo patch’s properties: list names a target path and an operation
(op:) that decides how the entry’s value (or its other arguments) combines with whatever is
currently stored at that path. If you leave op: out, it defaults to set, a plain overwrite.
That is why most of the simple examples elsewhere in these docs never mention an op at all.
This page walks through all 22 operations the engine recognizes. They are grouped by the kind of property they act on: numeric math and clamping, container mutation for arrays, sets, maps, and tag containers, string concatenation, array reordering and deduplication, and cross-property copy or move.
See Patching (type: cdo) for the full patch structure these entries live inside. See Property Types for what each underlying property looks like in YAML.
set (default): everything
Section titled “set (default): everything”set overwrites the property outright. It works on any property type, which is why it can be
left implicit. On mDisplayName (an FString), setting a value simply replaces the string:
- path: mDisplayName value: 'Reinforced Iron Ingot' # op: set is impliedBitmask operations
Section titled “Bitmask operations”set accepts one flag name or a sequence of flag names. KDataForge resolves and ORs names from the
property’s BitmaskEnum. Named forms are Shipping-safe. Raw numeric masks remain accepted.
- path: mParentTask value: Above- path: mParentTask value: - Above - Leftadd / subtract: numeric
Section titled “add / subtract: numeric”add and subtract do in-place arithmetic on a plain numeric property. They add or subtract
value from whatever is currently stored. Both reject enum-backed integers (see the callout
below), since there is no meaningful “current + 1” for an enum ordinal:
- path: mTechTier op: add value: 1- path: mTimeToComplete op: subtract value: 30multiply / divide: numeric
Section titled “multiply / divide: numeric”multiply and divide scale a numeric property by value. Setting mManufactoringDuration to a
fraction of itself halves the crafting time, while dividing mEnergyValue doubles the effective
fuel efficiency. Dividing by 0 is rejected outright, rather than producing infinity or NaN:
- path: mManufactoringDuration op: multiply value: 0.5- path: mEnergyValue op: divide value: 2min / max: numeric (result = min/max of current and value)
Section titled “min / max: numeric (result = min/max of current and value)”min enforces a ceiling: the property is replaced with whichever is smaller, the current value or
value. max enforces a floor, replacing the property with whichever is larger. Neither can push
the property past value in the opposite direction, which makes them useful for “never exceed” or
“never fall below” balancing passes across many objects at once:
- path: mResourceSinkPoints op: min value: 100 # cap at 100- path: mResourceSinkPoints op: max value: 50 # raise to at least 50clamp: numeric
Section titled “clamp: numeric”clamp is the two-sided version of min/max: instead of a single value, it takes min and max
keys and pins the current value inside that range in one step:
- path: mMenuPriority op: clamp min: 0 max: 999append: array / set / map / string / tag container / bitmask
Section titled “append: array / set / map / string / tag container / bitmask”append adds to the end of a container, or concatenates onto a string. It behaves differently
depending on the target property’s kind.
For arrays and sets, value can be a single item or a sequence to add several items at once. For
strings, it concatenates text. For tag containers, it adds tags. For maps, value must be a map,
and KDataForge merges those key/value pairs into the existing map:
- path: mIngredients # array: adds element(s) at the end op: append value: ItemClass: /Game/FactoryGame/Resource/RawResources/Coal/Desc_Coal.Desc_Coal_C Amount: 1- path: mDisplayName # string: concatenates op: append value: ' (rebalanced)'- path: mGameplayTags # tag container: adds tags op: append value: - MyPack.Rebalanced- path: mSomeMap # map: merges entries op: append value: Iron: 10- path: mParentTask # bitmask: OR-adds flags op: append value: - Above - LeftFor bitmasks, append ORs named flags into current mask. It does not accept raw integer masks.
An empty list adds nothing and is not an error, so value: cfg:tags.extra works while the player’s list
is empty. Leaving value out entirely is still an error. prepend and insert treat an empty list the same
way (insert still checks its index).
For instanced UObject arrays, an appended value can be a { class, properties } map; KDataForge
constructs the new subobject before inserting it into the array:
- path: mUnlocks op: append value: class: /Game/FactoryGame/Unlocks/BP_UnlockRecipe.BP_UnlockRecipe_C properties: - path: mRecipes value: - Recipe_MyGeneratedRecipeprepend: array / set / string (tag containers and bitmasks: same as append)
Section titled “prepend: array / set / string (tag containers and bitmasks: same as append)”prepend mirrors append, but it adds at the front instead of the end for arrays, and inserts
before the existing text for strings. Tag containers treat it identically to append, since a tag
container has no ordering. Sets also treat it like append, because sets have no ordering.
Bitmasks also treat it like append and OR-add named flags. Maps do not support prepend at all:
only append merges into a map.
- path: mDisplayName op: prepend value: '[MP] 'insert: array
Section titled “insert: array”insert places one or more elements at a specific index inside an array, rather than always at
the end, and shifts later elements back. An out-of-range index fails with an error, instead of
clamping to the array’s bounds. Instanced UObject array entries may use the same { class, properties } map accepted by set, append, and prepend:
- path: mIngredients op: insert index: 1 value: ItemClass: /Game/FactoryGame/Resource/RawResources/OreCopper/Desc_OreCopper.Desc_OreCopper_C Amount: 1remove: array (by value) / set (by value) / map (by key) / tag container / bitmask
Section titled “remove: array (by value) / set (by value) / map (by key) / tag container / bitmask”remove deletes by content rather than by position. On arrays and sets, it deletes every element
that matches value exactly. An array remove that matches nothing fails, rather than silently
doing nothing, and that includes an empty list. On maps, value is the key to delete, and on
tag containers, value is the tag (or list of tags) to remove. On bitmasks, it clears every named
bit with AND-NOT:
- path: mIngredients # removes every element equal to the value op: remove value: ItemClass: /Game/FactoryGame/Resource/RawResources/OreIron/Desc_OreIron.Desc_OreIron_C Amount: 1- path: mSomeMap # map: value is the key op: remove value: Iron- path: mGameplayTags op: remove value: KMods.WikiExclude- path: mParentTask # bitmask: clears named bits op: remove value: Leftremove_at: array
Section titled “remove_at: array”remove_at deletes the element at a specific position instead of matching by value. It is the
right tool when you know the slot but not, or don’t want to spell out, the exact contents:
- path: mProduct op: remove_at index: 1clear: array / set / map / string / tag container / bitmask
Section titled “clear: array / set / map / string / tag container / bitmask”clear empties a container-shaped property entirely: arrays, sets, maps, and tag containers all
lose every element. Strings are reset to empty, rather than to their class-default text. On a
metadata-recognized bitmask, clear writes 0:
- path: mSubCategories op: clear- path: mParentTask # bitmask: sets mask to 0 op: clearswap: array
Section titled “swap: array”swap exchanges the elements at two positions, index and with, in an array without touching
anything else in the list. It is useful for reordering a couple of entries without rewriting the
whole array:
- path: mIngredients op: swap index: 0 with: 1replace: array
Section titled “replace: array”replace overwrites the element at index with a new value, keeping every other element and
the array’s length unchanged:
- path: mProduct op: replace index: 0 value: ItemClass: /Game/FactoryGame/Resource/Parts/SteelIngot/Desc_SteelIngot.Desc_SteelIngot_C Amount: 2duplicate: array (copies element to the end)
Section titled “duplicate: array (copies element to the end)”duplicate copies the element at index and appends the copy to the end of the same array. It is
handy for cloning an existing ingredient or product entry as a starting point, before tweaking it
with a follow-up replace:
- path: mProduct op: duplicate index: 0sort / reverse: array
Section titled “sort / reverse: array”sort reorders an array in ascending order. Numeric and string inner types sort by their actual
value. Anything else, such as structs or object references, sorts by its exported text form.
reverse simply flips the current order of every element:
- path: mRelevantEvents # numeric/string inner types sort by value; others by text form op: sort- path: mSubCategories op: reverseunique: array (removes later duplicates; sets: no-op)
Section titled “unique: array (removes later duplicates; sets: no-op)”unique walks an array and removes any element that is byte-identical to an earlier one, keeping
the first occurrence of each value. Running it on a set is always a no-op, since a set is already
guaranteed to contain no duplicates:
- path: mSubCategories op: uniquecopy: any property (from another path on the same object)
Section titled “copy: any property (from another path on the same object)”copy reads the value at another property path (from) on the same object and writes it into the
target property. When source and destination are exactly the same property type, it copies the raw
value directly. Otherwise, it round-trips through each property’s text representation.
This means copying between compatible but different types, for example a plain string into a name, can still work, as long as the text parses back cleanly for the destination type:
- path: mAbbreviatedDisplayName op: copy from: mDisplayNamemove: copy, then reset the source to its default
Section titled “move: copy, then reset the source to its default”move performs the exact same copy as copy, then additionally clears the source property back
to its default value. This makes it the right choice when a value should live at the new path
instead of the old one, rather than in both places:
- path: mDescription op: move from: mScannerDisplayText