YAML Schema
Complete grammar for packs, document types, property paths, all 22 operations, conditions, and includes.
This document is normative. Every construct here is implemented and covered by an example in Examples.
File Discovery
Section titled “File Discovery”- Author root:
<your-mod>/DataForge/. - Author glob:
<your-mod>/DataForge/**/*.yml(and*.yaml). - Scanner root: the Unreal project directory, which is
FactoryGame/in a packaged game. - Up to six levels below the project root, a directory is a pack root when it is named
DataForge, uses the legacy namedata-editor, or directly containspack.yml. Names are case-insensitive. Root discovery prunesContent,Saved,Intermediate,Binaries,DerivedDataCache,Plugins,Source, andConfig. - Roots may nest. Each YAML file belongs only to its deepest enclosing root.
- All
*.yml/*.yamlfiles under a pack root are parsed as documents. A file normally holds one document, but may hold several separated by---lines. See Multiple Documents Per File below. - Malformed YAML produces an Error and that file is skipped. The rest of the load continues.
Pack Manifest (pack.yml)
Section titled “Pack Manifest (pack.yml)”Optional, at the pack root. Files without a manifest form an implicit pack whose ref and name come
from the detected root’s parent directory. Prefer an explicit stable ref; moving the root can
otherwise change its implicit identity.
ref: MyBalancePack # stable pack reference (save-persistence contract) required if presentname: My Balance Pack # display name (default: ref)version: 1.2.0 # semver, informational + used by conflict reportsdescription: Short player-facing summary # optional; string or { key, ns, source }; shown by settings screensicon: icon.png # optional; pack-relative png/jpg/jpeg/tga/bmp, or a /Game/... UTexture2D object pathpriority: 100 # higher applies earlier within each stage (default 100)enabled: true # false disables the whole pack (default true)debug: false # true logs every applied op from every document in this pack (see Debug Logging)dependencies: # pack refs that must load before this pack - SomeOtherPackredirects: # id renames for generated content (see Content Creation) OldId: NewId # old save class paths resolve to the renamed class (FCoreRedirects)conditions: hasMod: - KAPI - KLib # optional same grammar as a document's `conditions:` (see
# Conditions); gates every document in the pack (AND)conditions: on the manifest is evaluated once, before any file in the pack is even read, a
failing pack is skipped entirely with one Info diagnostic, instead of every document failing its
own copy of the same condition. Use it for “this whole pack is a compat layer for mod X” instead of
repeating a conditions: block with hasMod: X at the top of every document.
description and icon are optional and only used by UIs, see
Pack Configuration. An
icon file must stay inside the pack folder.
dependencies: are topological constraints. Missing or self-referential dependencies reject the
pack, and invalidity propagates to transitive dependants. Dependency cycles are rejected. Among
packs whose dependencies are ready, higher priority runs first and ref ascending breaks ties.
redirects: maps old generated-content ids to new ones. Generated class names are id-based
(Gen_<PackRef>_<Id>), so renaming an id would orphan save data. A redirect entry feeds
FCoreRedirects so class paths in existing saves resolve to the renamed class. Renaming a file
never needs a redirect. Only id renames do.
Document Structure
Section titled “Document Structure”Every document is a YAML map with a root type:
type: cdo # required (or use the `<name>.<type>.yml` filename convention)debug: true # optional log every applied op from this document (see Debug Logging)conditions: ? ... # optional see Conditionsinclude: # optional same-pack files that must apply BEFORE this one - base-values.cdo.yml
# … type-specific contentpriority: belongs to pack.yml, not individual documents. A document-level value is ignored by
runtime and rejected by current standalone schemas. Within one pack, include dependencies apply
first and remaining documents use deterministic relative-file order.
Root type detection order: type: key → filename convention (iron.recipe.yml → recipe).
Unknown types warn once and are skipped (they may belong to a handler from another mod or a later
phase).
Multiple Documents Per File
Section titled “Multiple Documents Per File”A file can hold more than one document by separating them with a --- line (the same convention
Kubernetes multi-manifest YAML files use). This lets a small, self-contained pack live in one file
instead of several:
type: itemitems: - id: MySuperIngot parent: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C properties: - path: mDisplayName value: Super Ingot - path: mResourceSinkPoints value: 100---type: sinkpointstrack: Defaultentries: - item: /KDataForge/Gen/MyPack.Gen_MyPack_MySuperIngot points: 100- Every document still runs through the normal pipeline independently, with its own
debug:,conditions:, andinclude:, staged and ordered exactly like a document from a separate file. - The
<name>.<type>.ymlfilename convention only applies to single-document files. A file’s name can’t encode more than one type, so once a file holds multiple documents, every one of them must set its owntype:key. A document with notype:in a multi-document file is skipped with a warning rather than falling back to the filename. - Diagnostics identify documents as
file.yml[0],file.yml[1], … (zero-based, source order) so a warning always points at the right one. include: file.yml(referencing a multi-document file by its bare name) waits for all of that file’s documents to apply first. You don’t need to know (or reference) how many documents it was split into.- A stray trailing
---with nothing after it produces a harmless empty document and is silently ignored. You don’t need to trim it.
Root types
Section titled “Root types”Each root type is dispatched to its own stage-specific handler. The table below lists every built-in type, the stage it runs in during a load, and the page with its full grammar and worked examples:
| Type | Stage | Detailed page |
|---|---|---|
gametag | GameTags | Game Tags |
asset | Assets | Data Assets |
sound | Assets | Wwise Sounds |
dataasset | Assets | Data Assets |
curve | Assets | Float and Vector Curves |
localization | Localization | Localization |
cdo | CDOChanges | Patching (type: cdo) |
sublevel | CDOChanges | Sublevels |
item | Items | Items |
resource | Items | Resources |
building | Items | Buildings |
unlock | Items | Unlocks |
class | Items | Custom Classes |
recipe | Recipes | Recipes |
schematic | Schematics | Schematics |
research / mam | Research | Research Trees |
sinkpoints | SubsystemMods | Sink Points |
resourcenode | SubsystemMods | Resources |
| custom (third-party) | handler-declared | registered via RegisterHandler, no page, it’s yours |
type: config is not in the table because a config definition is not a staged document. Files named
<name>.config.yml are read first, before pack conditions, and never reach a handler, see
Pack Configuration (type: config) below.
All class generation types (
item,resource,building,unlock, and the genericclass) run in the Items stage, priority-ordered (class→unlock→item→resource→building), so later stages (recipe,schematic,research) can reference every generated class.
Property Patches (type: cdo)
Section titled “Property Patches (type: cdo)”A cdo document patches properties on something that already exists: a class’s default
object, a live UObject such as a UDataAsset instance, or every asset matching a class/tag scan. It does not
generate new content. One or more target selectors pick what gets patched, and one or more
properties: ops describe the write:
type: cdopatches: - # ---- one or more target selectors are required; matches accumulate ---- target: /Game/FactoryGame/Recipes/Smelter/Recipe_IngotIron.Recipe_IngotIron_C # class path → patches the class default object # object path → patches that object directly (`UDataAsset` instances etc.) # a sequence of either → every entry resolved and patched independently # AND/OR: allAssetsOfClass: /Script/KAPI.KAPIDataAssetBase # every asset of that class (and subclasses) found in the asset registry # AND/OR an `ofClass:` scope narrowed by one or more filters (all filters must pass): ofClass: /Script/FactoryGame.FGRecipe # the scope: this class, its subclasses, its assets # required for matchTag / matchName / where; producedIn / ingredient / product default # to /Script/FactoryGame.FGRecipe when omitted matchTag: KMods.AlternateRecipe # scalar or sequence (all must match) tagProperty: mGameplayTags # optional explicit tag container property matchAssets: true # also match data-asset instances (default true) matchName: Recipe_Alternate_* # wildcard on the class name (full path when it contains '/'); # scalar or sequence (any may match) producedIn: /Script/FactoryGame.FGBuildableManufacturer # recipes whose mProducedIn # references this class or a subclass; scalar or sequence (any may match) ingredient: Desc_IronIngot_C # recipes with this item (or a subclass) in mIngredients product: Desc_IronPlate_C # recipes with this item (or a subclass) in mProduct where: # one map, or a sequence of maps that must all hold - path: mManufactoringDuration # property path; `[*]` = any element (read-only) greaterThan: 4 # exactly one operator per clause: # equals | notEquals | lessThan | lessOrEqual | greaterThan | greaterOrEqual # contains | notContains | in [any of] | notIn [any of] | matches (wildcard) | isEmpty (true/false)
# ---- modifiers ---- applyToSubclasses: false # class targets: also patch every derived class CDO applyToSpawnedActors: false # actor classes: re-apply ops to every spawning instance deferOneGameTick: false # wait until one game tick after BeginPlay before resolving/applying
# ---- the ops ---- properties: - path: mManufactoringDuration # see Property Paths op: multiply # see Operations (default: set) value: 0.5Sublevel Blocking (type: sublevel)
Section titled “Sublevel Blocking (type: sublevel)”A sublevel document blocks RefinedRDLib or KBFL sublevel descriptor assets during the
CDOChanges stage. block: must be a non-empty sequence. Each entry is an exact asset object path,
or a map with target: (scalar or sequence), allAssetsOfClass:, and/or an ofClass: scope with
filters (matchTag:, matchName:, where:; assets only, ofClass:
is required):
type: sublevelblock: - /SomeMod/World/DA_OptionalSublevel.DA_OptionalSublevel - target: - /AnotherMod/World/DA_Nodes_A.DA_Nodes_A - /AnotherMod/World/DA_Nodes_B.DA_Nodes_B - allAssetsOfClass: /Script/RefinedRDLib.RRDLSublevelAsset - ofClass: /Script/RefinedRDLib.RRDLSublevelAsset matchName: DA_Optional_*Exact paths use direct object loading. Bulk class scans include derived classes but depend on the
Asset Registry being populated. Other asset families warn and must be patched through type: cdo.
The provider mods are optional soft integrations. Full behavior and diagnostics:
Sublevels.
Property Paths
Section titled “Property Paths”path: addresses a single property, or a nested member reached through arrays, sets, maps, or
object references. It uses a small path grammar shared by every operation below:
Segment(.Segment)* Segment = Name([<int>] | ["key"] | [key])*| Example | Meaning |
|---|---|
mManufactoringDuration | plain property |
mIngredients[0].Amount | struct member of array element 0 |
mSomeMap["Iron"].Value | struct member of map value for key Iron |
mSomeSet[2] | third element of a set (logical order) |
mMeshComponent.RelativeScale3D | dereference through an object-typed member (components, subobjects) |
mIngredients[*].ItemClass | any element (cdo where: filters only, never in properties: ops) |
Blueprint-defined properties match on their authored name (case-insensitive), so mangled BP names never appear in YAML.
Operations
Section titled “Operations”op: values and their argument keys. Full worked examples: Operations.
| Op | Applies to | Arguments |
|---|---|---|
set (default) | everything | value |
add, subtract, multiply, divide, min, max | numeric | value |
clamp | numeric | min, max |
append | array, set, map, string/name/text, tag container, bitmask | value (map for map; scalar or sequence otherwise) |
prepend | array, set, string/name/text, tag container, bitmask | value (scalar or sequence) |
insert | array | index, value |
remove | array (by value), set (by value), map (by key), tag container, bitmask | value |
remove_at | array | index |
clear | array, set, map, string/name/text, tag container, bitmask | none |
swap | array | index, with |
replace | array | index, value |
duplicate | array | index |
sort, reverse | array | none |
unique | array (set: no-op) | none |
copy, move | everything | from (property path on the same object; move clears the source) |
Unsupported op×type combinations are diagnostics at load time, never silent no-ops.
Special-cased properties
Section titled “Special-cased properties”A few properties need more than a plain reflected write. KDataForge handles them automatically:
FGameplayTagContainer: acceptsset/append/prepend/remove/clearwith plain tag names (no native-struct text form needed).mStackSize(anyFGItemDescriptor-derived target): this is anEStackSizeenum (SS_ONE/SS_SMALL/SS_MEDIUM/SS_BIG/SS_HUGE/SS_FLUID), not a plain integer. Set it with an enum value name like any other enum property. The engine also caches item stack sizes, so writing the property alone would not change anything in-game. This is why KBFL ships a dedicatedUKBFLCDOItemStackSizehelper. Any op that writesmStackSizealso resets the descriptor’s cached stack size (mCachedStackSize), so the change takes effect out of the box. There are two caveats. Inventories that already contain the item may need the item re-added (or a game reload) before slots reflect the new limit. Also, if you use KBFL’s own CDO assets for stack sizes, don’t patch the same descriptor from both sides. Example with the full caveat: Balancing → Items.
Value Forms (by property type)
Section titled “Value Forms (by property type)”Each reflected property type accepts its own YAML shape when used as an operation’s value: (or
min/max/with argument). The table below is the authoritative mapping from UE property type
to accepted YAML form:
| Property type | YAML form |
|---|---|
bool | true / false / yes / no / on / off / 1 / 0 |
int32, int64, uint*, uint8 | integer scalar |
float, double | number scalar |
| enum | value name (RF_SOLID, EResourceForm::RF_SOLID) or numeric value (must be a valid ordinal, rejected otherwise) |
bitmask (int32/etc. with meta=(Bitmask, BitmaskEnum=...)) | one flag name (Above), a block YAML sequence with one flag per item (recommended for multiple flags), or an "Above|Left" string; all named forms are Shipping-safe and names are OR-ed against actual-mask enums (UseEnumValuesAsMaskValuesInEditor=true). Raw pre-computed integers (3) remain accepted; bit-index enums require integer masks. Export emits a name sequence. Append/Prepend OR-add flags, Remove clears named bits, and Clear writes zero |
FString, FName | string scalar |
FText | string scalar (culture-invariant) or block map with key, ns, and source fields (localized); key is mandatory in map form, so a source-only map is invalid |
| object reference | full object path; a bare generated-content id (e.g. SuperIngotIcon for a dataasset/asset entry, see Generated Content Short Ids); ""/none → null |
| class reference | class path (/Script/Module.Class or /Game/....Asset_C; _C auto-retried), or a bare generated-content id (e.g. MySuperIngot for an item/resource/building/class/… entry, see Generated Content Short Ids), or, as a last resort, a bare class name (Desc_Water) resolved by name against every class currently loaded in memory; ambiguous bare-name forms log an error (LogKDataForge, listing every match) and use the first match (sorted by full class path), so prefer full paths in published packs when it matters |
TSoftObjectPtr / TSoftClassPtr | path string (not loaded) |
| struct | native text form ("(X=1,Y=2,Z=3)"), block map with named fields, or block positional sequence |
FGameplayTag | tag name string |
FGameplayTagContainer | sequence of tag names |
TArray | sequence |
TSet | sequence |
TMap | map (keys converted via the key property type) |
| nested structs | nested maps, any depth |
Typed references and templates
Section titled “Typed references and templates”Any value above can instead be produced from pack configuration, game settings or reflected game data. Full rules and examples: Templates and Typed References.
| Form | Meaning |
|---|---|
cfg:<id>, cfg:<pack>:<id>, session:<id>, ags:<id>, option:<id> | A whole, unquoted scalar. Replaced before the stages run by the typed value: numbers and booleans unquoted, strings quoted, a multiple field as a sequence. Quoted forms stay literal. |
{cfg:<id>}, {session:<id>}, {ags:<id>}, {option:<id>} | A pre-pass template inside any string of any document. Replaced by the value as text. |
{<prefix>:<target>:<member>} with class, item, resource, building, recipe, schematic, research, category, dataasset, asset | An apply-time template in value, min and max of an operation. <member> is a property path or a function call Name(args). A string that is exactly one token keeps the result’s type. |
\{ directly before {<prefix>: | Escape: the token stays literal text. |
Typed references and pre-pass templates are not replaced in the values of the keys id, type,
include and ref, in map keys, or inside conditions: blocks. Templates nest up to four levels.
A plain scalar that starts with { is a YAML flow mapping, so quote such values.
Gameplay Tag Registration (type: gametag)
Section titled “Gameplay Tag Registration (type: gametag)”A gametag document registers new FGameplayTag names with the engine’s gameplay tag tree before
any other document type runs. This matters because tags referenced from cdo targeting
(matchTag:) or from a FGameplayTagContainer property write must already exist to resolve:
type: gametagtags: - tag: MyPack.Machines.Special # map form with comment comment: Marks special machines - MyPack.Simple # scalar shorthandTags are registered at runtime by appending to the gameplay-tag settings list in memory, never written to config files, with a tag-tree rebuild when needed. This is the only runtime channel that survives the engine’s own tree reconstructs. Register tags before using them in cdo documents: the GameTags stage always runs first.
Content Creation (item, resource, building, recipe, schematic, research, unlock)
Section titled “Content Creation (item, resource, building, recipe, schematic, research, unlock)”These seven root types generate new classes at runtime. All share the same entry grammar. Only the plural list key and the default parent differ:
type: item # or resource / building / recipe / schematic / research / unlockitems: # plural key per type see table below - id: MySuperIngot # required stable id, the save-persistence contract parent: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C # optional properties: # identical op entries to cdo patches, applied to the new class's CDO - path: mDisplayName value: Super Ingot - path: mStackSize value: SS_HUGE # EStackSize enum, not a plain integer see Special-cased properties| Type | Plural key | Default parent |
|---|---|---|
item | items | /Script/FactoryGame.FGItemDescriptor |
resource | resources | /Script/FactoryGame.FGResourceDescriptor |
building | buildings | /Script/FactoryGame.FGBuildingDescriptor |
recipe | recipes | /Script/FactoryGame.FGRecipe |
schematic | schematics | /Script/FactoryGame.FGSchematic |
research | research | /Script/FactoryGame.FGResearchTree |
unlock | unlocks | none: a concrete FGUnlock subclass must be given as parent |
Generated class identity (save-persistence contract)
Section titled “Generated class identity (save-persistence contract)”- Classes live in package
/KDataForge/Gen/<PackRef>and are namedGen_<PackRef>_<Id>: deterministic and id-based, never derived from file paths or load order. - Reference a generated class elsewhere (recipes, unlocks, sinkpoints, other YAML) as
/KDataForge/Gen/<PackRef>.Gen_<PackRef>_<Id>with no_Csuffix. - The
idis the contract. Renaming a file is safe. Renaming anidneeds a pack redirect (redirects:inpack.yml). - Classes are created during GameInstance INITIALIZATION strictly before any save loads, so save class-path resolution finds them in memory.
- Constraint: generated classes have the parent’s layout, with no new reflected properties or
components. All variation lives in CDO defaults (which is exactly what
properties:sets).
Generated Content Short Ids
Section titled “Generated Content Short Ids”Anywhere a class or object reference is accepted (parent:, properties: values, target:,
item: on sinkpoints, class:, …), a bare id (just the id: a document gave the content, e.g.
MySuperIngot) resolves in addition to the full /KDataForge/Gen/<PackRef>.Gen_<PackRef>_<Id>
path. Both forms always work; use whichever reads better:
type: itemitems: - id: PureSuperIngot parent: SuperIngot # short id same as /KDataForge/Gen/MyPack.Gen_MyPack_SuperIngot- Resolution order: the id is looked up in the current document’s own pack first (the common
case: most references are to something the same pack just generated). If it isn’t there, every
pack’s generated content is searched; a match in exactly one other pack still resolves. Matches in
more than one pack log an error (
LogKDataForge, listing every match) and apply to the first match (sorted by full path), the same ambiguity handling as the bare class-name shortcut above. Qualify with the full path to remove any ambiguity or to reference another pack’s content unambiguously. - Covers both generated classes (
item/resource/building/recipe/schematic/research/unlock/class) and generated asset instances (dataasset/asset, e.g. an icon’s id inmSmallIcon: SuperIngotIconinstead of/KDataForge/GenAssets/MyPack.Gen_MyPack_SuperIngotIcon). - Only tried for name-only input (no
/and no.at all). Anything that looks like a path is always treated as a path, never as an id. - Works everywhere a class/object reference is resolved:
properties:values,target:,allAssetsOfClass:,ofClass:,parent:, register-onlyclass:,item:on sinkpoints, and spawn-time actor patches (applyToSpawnedActors: true) re-applied to newly spawned actors.
Registration (recipe, schematic, research)
Section titled “Registration (recipe, schematic, research)”type: reciperecipes: - id: Recipe_MySuperIngot register: true # default omit unless you want registration off properties: - path: mManufactoringDuration value: 4Removing registered content (recipe, schematic, research)
Section titled “Removing registered content (recipe, schematic, research)”These three document types also accept a document-level remove: sequence. It may be the whole
document or appear beside the normal entries list. An entry is either a bare class reference (full
path, bare class name, or generated-content short id) that removes exactly that class, or a filter
map using the scope filters — matchTag:, matchName:,
producedIn:, ingredient:, product:, where:, optional ofClass: — that removes every class of
the type passing all of them:
type: schematicremove: - /Game/FactoryGame/Schematics/Progression/Schematic_1-1.Schematic_1-1_C - Schematic_ObsoleteFromAnotherPack - where: # filter map: ofClass defaults to FGSchematic here path: mTechTier greaterOrEqual: 8
# Optional: registration/generation entries can coexist. Explicit removal runs after registration.schematics: - id: Schematic_Replacement- Only
recipe,schematic, andresearch(includingtype: mam) support this key.item,resource,building,unlock, andclassdo not have a live registry-removal path. - A bare reference matches by exact class; removing a parent does not remove its subclasses. A filter
map’s
ofClass:defaults toFGRecipe/FGSchematic/FGResearchTreeand must stay within that base; a map without any filter key is an error. - Filter rules are stored as predicates and evaluated against every class of the kind the game presents (recipes: on every grant; schematics/research: at world registration), so classes that load later are still caught.
- Schematic and research-tree removal is save-clean and reverses on the next session after the pack is removed. Existing purchase/unlock state is not revoked.
- Recipe removal prevents future adds and sweeps recipes restored from the server save. The available-recipe list is saved, but it self-heals on a later load without the pack when purchased schematics reapply their unlocks.
- Removing a schematic does not remove recipes it already granted. Pair schematic and recipe removal rules when the content itself must become unavailable.
See Removing Content for the consolidated guide, and Recipes, Schematics, and Research Trees for kind-specific notes.
register: true(default) queues the class for per-world registration into SML’sUModContentRegistry, before the registry freezes at world begin play. Auto-propagation applies: registering a schematic registers its recipes. Recipes register their items.- Register-only mode: use
class:instead ofid:to register an existing class without generating one:
type: schematicschematics: - class: /Game/SomeMod/Schematics/Schematic_Hidden.Schematic_Hidden_C # register, don't generateNodes: placing a schematic in a research tree
Section titled “Nodes: placing a schematic in a research tree”A research entry modifies its visual M.A.M. graph directly. addNodes: appends node instances; it
does not update or deduplicate an existing node with the same schematic. A usable node
normally supplies the displayed schematic: and a coordinate:. parents: creates prerequisite
relationships; with autoPath: true on the research entry, KDataForge also creates straight roads
for those relationships:
type: researchresearch: - id: MyResearchTree autoPath: true addNodes: - schematic: Schematic_First coordinate: X: 0 Y: 0 - schematic: Schematic_Second coordinate: X: 2 Y: 0 parents: - schematic: Schematic_Firstschematic:accepts a generated short id or a fullUFGSchematicclass path.coordinate:is a block map with numericX:andY:fields. Supply it for a node that participates in graph relationships or roads.parents:,nodesToUnhide:, andunhiddenBy:are optional. Each reference may name another node’sschematic:or a literal coordinate. References may point forward or backward within the sameaddNodes:list, or to a node already present on the target tree.autoPath:defaults tofalse. When enabled, each resolvedparents:relationship gets a straight, two-point visual road. Leave it off and providechildrenAndRoads:on the parent for a bent or otherwise custom route.class:optionally selects aUFGResearchTreeNodesubclass;properties:applies ordinary property operations to that node. A convenience field missing from a custom node logs a warning and is skipped rather than failing the whole document.
A custom road uses a child coordinate and every point along the path. childrenAndRoads: can be one
a block map with key and points fields, or a sequence of those maps when a node has multiple children:
addNodes: - schematic: Schematic_First coordinate: X: 0 Y: 0 childrenAndRoads: - key: X: 2 Y: 0 points: - X: 0 Y: 0 - X: 0 Y: 1 - X: 2 Y: 1 - X: 2 Y: 0 - key: X: 0 Y: 2 points: - X: 0 Y: 0 - X: 0 Y: 2Use tree-entry-level removeNodes: to remove nodes by schematic class or short id. Each selector
may be a scalar (Schematic_Obsolete) or a block map with schematic: Schematic_Obsolete. KDataForge
performs all removals before appending the same entry’s addNodes: list and removes every surviving
Parents, NodesToUnhide, UnhiddenBy, and ChildrenAndRoads reference to each removed
coordinate. The same entry may remove one node and add another; autoPath: applies to additions:
research: - class: /Game/FactoryGame/Schematics/Research/Mycelia_RS/ResearchTree_Mycelia.ResearchTree_Mycelia_C register: false autoPath: true removeNodes: - Schematic_Obsolete addNodes: - schematic: Schematic_Replacement coordinate: X: 3 Y: 1 parents: - schematic: Schematic_ExistingParentautoPath: is a sibling of addNodes: on the research entry and generates straight roads for its
resolved parents: relationships. To move a node, select and re-add the same schematic with its new
coordinate. nodes: remains a legacy alias. If both keys exist,
addNodes: wins and KDataForge warns that nodes: was ignored.
Instanced Dependencies on Research Trees
Section titled “Instanced Dependencies on Research Trees”Research entries accept unlockDependencies: and visibilityDependencies:. Both use the
block maps containing class: and properties: in the instanced FGAvailabilityDependency shape; they append to
mUnlockDependencies and mVisibilityDependencies, respectively.
research: - id: MyResearchTree unlockDependencies: - class: /Script/FactoryGame.FGSchematicPurchasedDependency properties: - path: mSchematics value: - Schematic_MyPrerequisite visibilityDependencies: - class: /Script/FactoryGame.FGGamePhaseReachedDependency properties: - path: mGamePhase value: EGP_EarlyGameInstanced Unlocks on Schematics (unlocks:)
Section titled “Instanced Unlocks on Schematics (unlocks:)”Schematic entries accept an unlocks: list. Each entry creates an instanced FGUnlock
subobject on the schematic’s CDO. This works with any concrete FGUnlock subclass the base game
ships, not just FGUnlockRecipe; see the
Base-Game Unlock Classes for the full list:
type: schematicschematics: - id: Schematic_MySuperIngot properties: - path: mDisplayName value: My Super Ingot unlocks: - class: /Game/FactoryGame/Unlocks/BP_UnlockRecipe.BP_UnlockRecipe_C properties: - path: mRecipes value: - /KDataForge/Gen/MyPack.Gen_MyPack_Recipe_MySuperIngotKnown native FactoryGame/KMods class paths automatically remap to their Blueprint implementation. Exact Blueprint paths remain valid. Concrete native classes without Blueprints stay native; abstract classes without concrete variants cannot be instanced.
Schematic shortcut unlocks
Section titled “Schematic shortcut unlocks”In addition to generic unlocks:, schematic entries accept recipes: (sequence of UFGRecipe
classes), schematics: (sequence of UFGSchematic classes), positive-integer handSlots: and
inventorySlots:, and scannableResources: (sequence of block maps containing resource: and nodeType:, with
nodeType: Node by default). Each shortcut creates the corresponding normal FGUnlock instance;
class references resolve exactly like every other generated, vanilla, or mod class reference.
Instanced Dependencies on Schematics (dependencies:)
Section titled “Instanced Dependencies on Schematics (dependencies:)”Schematic entries also accept a dependencies: list (the same instanced-object mechanism as
unlocks:), targeting mSchematicDependencies instead of mUnlocks. Each entry creates an
instanced FGAvailabilityDependency subobject on the schematic’s CDO:
type: schematicschematics: - id: Schematic_AdvancedSuperIngot properties: - path: mDisplayName value: Advanced Super Ingot dependencies: - class: /Script/FactoryGame.FGSchematicPurchasedDependency properties: - path: mSchematics value: - /KDataForge/Gen/MyPack.Gen_MyPack_Schematic_MySuperIngot - path: mRequireAllSchematicsToBePurchased value: truePair it with mDependenciesBlocksSchematicAccess/mHiddenUntilDependenciesMet (ordinary
properties:) to control whether the schematic is merely blocked from purchase or hidden entirely
until the dependency is met. See
Type: schematic → Gating a schematic on prerequisites
for the full worked example and other FGAvailabilityDependency subclasses worth knowing about.
Generic Class Generation (type: class)
Section titled “Generic Class Generation (type: class)”The seven preset types above are the recommended, ergonomic forms. class is the escape hatch
for everything else: it generates a new class from any parent, whether native C++
(/Script/Module.Class) or Blueprint (/Game/....Thing_C). Examples include equipment
descriptors, vehicle descriptors, item/schematic categories, FGUnlock subclasses, other mods’
classes gated behind hasMod, reparented buildables, and more.
type: classdebug: true # optional (see Debug Logging)classes: - id: MyCustomThing parent: /Script/FactoryGame.FGEquipmentDescriptor # ANY class: native C++ or /Game/...Thing_C registerAs: recipe # OPTIONAL: recipe | schematic | research queues SML content # registration for kinds that need it; omit for everything else properties: - path: mDisplayName value: My Equipment # cdo-style op entries applied to the new class's CDO- Same save-stable identity as the preset kinds:
/KDataForge/Gen/<PackRef>.Gen_<PackRef>_<Id>. Manifest, tombstones, andredirects:all apply identically. - Runs in the Items stage (same as
item/resource/building/unlock), sorecipe,schematic, andresearchdocuments can reference the generated classes. registerAs:queues the class into SML’sUModContentRegistrylike the corresponding preset type would. It’s only needed for the three registered kinds. Everything else just needs to exist.- The constraint stays: generated classes copy the parent’s layout, with no new reflected properties or components. All variation is CDO defaults.
Worked examples (equipment descriptor, category, modded parent behind hasMod) live at
Custom Classes. For reparenting a buildable
as part of a complete building, see
A Complete Building.
2D Image Assets (type: asset)
Section titled “2D Image Assets (type: asset)”Loads image files from the pack and turns them into real, referenceable UTexture2D assets:
type: assetassets: - id: MyIcon file: icons/my-icon.png # relative to the pack root; png / jpg / tga / bmp- Decoded at load into a transient
UTexture2Dat/KDataForge/GenAssets/<PackRef>.Gen_<PackRef>_<Id>and announced to the asset registry. - Reference it from any
properties:entry like any other object path:value: /KDataForge/GenAssets/MyPack.Gen_MyPack_MyIcon. - Image files are decoded during the next GameInstance initialization after they change.
- 2D only: no FBX/glTF, ever, by design. DDS and WAV image formats aren’t supported yet (PNG/JPG/TGA/BMP are).
Icon usage example (item mSmallIcon / mPersistentBigIcon):
Data Assets → 2D image assets.
Dynamic DataAsset Instances (type: dataasset)
Section titled “Dynamic DataAsset Instances (type: dataasset)”Creates real UDataAsset instances (not classes) at runtime, using package
/KDataForge/GenAssets/<PackRef> and name Gen_<PackRef>_<Id>. It announces them to the asset
registry so scanning subsystems can find them. UPrimaryDataAsset subclasses are the common
consumer-facing case, but the runtime validator accepts any UDataAsset subclass.
type: dataassetconditions: hasMod: KAPIassets: - id: CleanerItem_MyFluid class: /Script/KAPI.KAPICleanerItemDescription # any UDataAsset subclass properties: - path: mProductionTime value: 4.0 - path: mInFluid value: ItemClass: /Game/FactoryGame/Resource/RawResources/Water/Desc_Water.Desc_Water_C Amount: 1000After the initial load, KDataForge automatically triggers consumer rescans when data assets were modified or added:
- KAPI: called via reflection only when the KAPI mod is loaded, no hard dependency needed.
- RefinedRDApi: called via reflection only when the RefinedRDApi mod is loaded, with no dependency needed. It covers RefinedPower / FicsitFarming boiler/cooler/heater/turbine/generator/dirt data assets.
Existing data assets (KAPI’s, RRDA’s, any mod’s) are already editable via cdo documents
(target: object path, allAssetsOfClass:, matchTag:). The dataasset type is only for
creating new instances.
Data-asset entries may also contain unlocks: and dependencies: lists. They append instanced
objects to mUnlocks and mTaskDependencies when selected class exposes those arrays. This is
intended for UKAPIDeliveryTask; incompatible classes report missing array at runtime. Entry shape
and native-to-Blueprint remapping match schematic lists.
Float and Vector Curve Assets (type: curve)
Section titled “Float and Vector Curve Assets (type: curve)”Creates new or edits existing UCurveFloat and UCurveVector assets. keys: replaces complete
key set on each entry.
type: curvecurveType: float # float | vectorcurves: - id: DamageFalloff # exactly one of id or target # target: /Game/Curves/Curve_Damage.Curve_Damage defaultValue: 0 # vector defaults use X/Y/Z fields in a block mapping preInfinityExtrap: constant postInfinityExtrap: linear isEventCurve: false # float only keys: - time: 0 value: 1 # vectors also accept block X/Y/Z fields or block sequence items interpMode: cubic # constant | linear | cubic tangentMode: auto # auto | smartAuto | user | break tangentWeightMode: none # none | arrive | leave | both arriveTangent: 0 leaveTangent: 0 arriveTangentWeight: 0 leaveTangentWeight: 0preInfinityExtrap and postInfinityExtrap accept none, constant, linear, cycle,
cycleWithOffset, or oscillate. Times must be finite and unique within one entry. Vector values
and tangent fields accept an X/Y/Z map or three-number sequence. Generated curves use object path
/KDataForge/GenAssets/<PackRef>.Gen_<PackRef>_<Id> and have no _C suffix. See
Float and Vector Curves for full examples and
troubleshooting.
AWESOME Sink Point Values (type: sinkpoints)
Section titled “AWESOME Sink Point Values (type: sinkpoints)”A sinkpoints document sets the AWESOME Sink point value for one or more items on a named track.
The FGResourceSinkSubsystem consults this whenever it looks up how many points an item is worth:
type: sinkpointstrack: Default # Default | Explorationentries: - item: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C points: 100 overridden: 50 # optional - ofClass: /Script/FactoryGame.FGItemDescriptor # filter form instead of `item:` — every loaded where: # item descriptor passing the filters gets these points path: mForm in: [RF_LIQUID, RF_GAS] points: 0An entry names one item:, or replaces it with an ofClass: scope (default FGItemDescriptor)
plus filters (matchTag:, matchName:, where:, …). Filter entries
expand against the item descriptor classes loaded when the document applies.
Not live-reloadable (registry freeze): sinkpoint changes need a session restart.
Resource Node Removal (type: resourcenode)
Section titled “Resource Node Removal (type: resourcenode)”A resourcenode document requires a non-empty remove: sequence. A scalar is shorthand for a map
with only resource:. Valid entries still apply when another entry reports an error:
type: resourcenoderemove: - /Game/FactoryGame/Resource/RawResources/Uranium/Desc_OreUranium.Desc_OreUranium_C - resource: MyGeneratedResource nodeTypes: [Node, FrackingCore] allowOccupied: false removeFromScanner: true - ofClass: /Script/FactoryGame.FGResourceDescriptor # filter form instead of `resource:` matchName: Desc_OreUranium* # every loaded descriptor passing the filters nodeTypes: [Node]resource:must resolve to aUFGResourceDescriptorclass. Generated-content short ids work. Alternatively an entry carries anofClass:scope (defaultFGResourceDescriptor) plus filters (matchTag:,matchName:,where:, …) and applies its options to every loaded descriptor class that passes.nodeTypes:is optional. Omitted or empty means all types; accepted values areNode,FrackingSatellite,FrackingCore,Geyser, andDeposit, with or without theEResourceNodeType::prefix.allowOccupied:defaults tofalse. The default skips and warns about node groups used by an extractor.truecan invalidate an extractor’s node reference.removeFromScanner:defaults totrue. It also strips vanilla scanner pairs and matching KAPI allow-list data, then refreshes scanner/radar caches.
Runtime Text Entries (type: localization)
Section titled “Runtime Text Entries (type: localization)”A localization document injects or overrides FText string table entries at runtime. It is
scoped to a namespace and optionally a single culture, and it needs no separate localization asset
shipped:
type: localizationnamespace: MyPackculture: de # optional; prefix-matched against current language; omit = allentries: SimpleKey: 'Übersetzter Text' KeyWithSource: source: 'Original English' text: 'Deutscher Text' # override existing textsculture:is prefix-matched against the current language (dematchesde-DE). Omitting it applies the entries for every language.- The scalar form sets the text for
namespace/key. A block map withsourceandtextfields overrides existing texts by their original source string. - Localization documents apply during GameInstance initialization. Restart the session after edits.
Pack Configuration (type: config)
Section titled “Pack Configuration (type: config)”A file named <name>.config.yml (or .config.yaml) defines typed settings for its pack. Config
definitions are read before pack conditions and before any document, are not part of the document
list, and are never staged. Guide and examples:
Pack Configuration.
type: config # optional; any other value in a *.config.yml file is an errorcategory: SampleFeaturesCategory # optional default category (UFGCategory subclass)# conditions: optional, static keys only (gameVersion, hasMod, modVersion, hasClass)fields: # required, at least one - id: balance.ironSpeed type: number valueType: float name: Iron smelting speed description: How many times faster the Iron Ingot recipe runs. default: 2 min: 0.25 max: 4 step: 0.25 display: slider apply: hotDocument keys: type, category, conditions, conditionBehavior (or conditionBehaivor),
debug, fields. Other keys are ignored with a warning. type: config in a file that is not named
*.config.yml is an error.
Field keys:
| Key | Applies to | Rule |
|---|---|---|
id | all | Required. Dot-separated segments [A-Za-z_][A-Za-z0-9_]*, at most 200 characters; unique per pack ignoring case; no id may be the dotted prefix of another; the lower-case words true, false, in, contains are reserved (True is a valid id). A pack that defines configuration needs a ref of at most 50 characters. |
type | all | Required: text, number, boolean, select. |
valueType | all | text: string (default), text, name, tag. number: int32 (default), int8, int16, int64, uint8, float, double. boolean: bool (default), uint8. select: string (default), text, name, tag, int8, int16, int32, int64, uint8, float, double, bool. |
name, description | all | Required. A non-empty string or {key, ns, source}. |
tooltip | all | Optional {title, text}, each a string or {key, ns, source}. |
category | all | A UFGCategory subclass: bare id of a class generated in the pack, class path, or /KDataForge/Gen/<PackRef>.Gen_<PackRef>_<Id>. Required on the field unless the document sets one. |
default | all | Required. A list when multiple: true. Must satisfy the type’s constraints. |
apply | all | hot or cold (default). |
order | all | Integer. Default: declaration index across the pack’s config files. |
multiple | text, number, select | true makes a list (text, number) or a multi-select (select). Not allowed for boolean. |
min, max | number | Whole numbers for integer types; min <= max. Apply to every element of a list. |
step | number | Greater than 0. |
decimals | number | 0 to 9. Default: digits of step, else 4 (float) or 6 (double). |
display | number | slider (needs min < max, not with multiple) or input. Default: slider when both bounds exist. |
multiline | text | Only with valueType: text. |
maxLength | text | 1 to 1024, default 1024. |
tagRoot | text with valueType: tag | A gameplay tag; only tags below it are accepted. |
options | select | Required, at least one {value, label}. Values unique and of the valueType; label is a string or {key, ns, source}. |
minSelected, maxSelected | select with multiple | minSelected is at most the number of options; maxSelected is not below minSelected and at most 64. |
minCount, maxCount | text, number with multiple | List length bounds, at most 64. A list without maxCount holds at most 64 entries. |
descriptionClass, properties | all | Reserved, ignored with a warning. |
A field with an error is skipped; the rest of the file and pack still load. The values file is
<ProjectDir>/Configs/Packs/<PackRef>.config.yml (FactoryGame/Configs/Packs/ in a packaged
game). Values are read through cfg:, {cfg:} and config:, see
Typed references and templates and Conditions.
Debug Logging
Section titled “Debug Logging”Every applied op can be traced. Three levels, from narrow to global:
| Level | How | Effect |
|---|---|---|
| Document | debug: true top-level key in any document type | Every applied op from that document logs |
| Pack | debug: true in pack.yml | Every applied op from every document of the pack logs |
| Global | console variable KDF.Debug 1 (or launch arg -KDFDebug) | Everything logs, including editor-originated ops |
Log lines go to the game log at Log verbosity, one per applied op:
[KDF DEBUG] <file>: <target path>.<property path> <op> : <pre value> -> <post value>Walkthrough (where the log file lives, reading the lines): Getting Started → Debugging.
Conditions
Section titled “Conditions”Condition objects use AND by default; conditionBehaivor: OR opts into alternatives. A failing
combined result skips the document with an Info diagnostic.
conditions: gameVersion: '>=416000.0.0' # semver range vs the game version, reported as <changelist>.0.0 # (SML convention) gate on the game's build changelist, e.g. the # CL from the game's main menu / `>=491125.0.0` for the SML baseline hasMod: KAPI # scalar… # hasMod may instead use a block sequence; `Ref@<range>` pins a version modVersion: # explicit map form for version constraints SML: '>=3.12.0' hasClass: /Script/FactoryGame.FGBuildableFrackingActivator # hasClass may instead use a block sequenceconditions: may also be an array of condition objects. Within each object, condition keys are
AND-combined first; ifNotMatch: true then negates that object. Top-level conditionBehaivor
combines the object results using AND or OR (case-insensitive) and defaults to AND. Correctly
spelled conditionBehavior is accepted as an alias. ifNotMatch defaults to false.
conditionBehaivor: AND # optional; defaultconditions: - hasMod: - KAPI - KLib # require both - hasMod: IncompatibleMod # require this mod to be absent ifNotMatch: true - modVersion: # reject this version range if mod is loaded OptionalIntegration: '^2.0.0' ifNotMatch: trueAn inverted hasMod: Mod@<range> object matches when the mod is absent or its loaded version does
not satisfy <range>. Inversion never hides malformed condition keys, types, or version ranges.
Because inversion applies to the entire object, use separate inverted objects when excluding
multiple mods independently.
conditionBehaivor: ORconditions: - hasMod: RefinedPower - hasMod: FicsitFarmingOR passes when at least one object matches; AND requires every object. Each object is evaluated
and optionally inverted before aggregation. Invalid behavior values or malformed condition objects
fail closed even if another OR branch matches. An empty array passes under AND and fails under
OR.
Version ranges use SML’s semver syntax: >=1.2.3, ^3.12.0, ~1.2.0, exact 1.2.3, unions with
||.
config: conditions and list-entry conditions
Section titled “config: conditions and list-entry conditions”A condition object also accepts config:, an expression string or a list of expression strings
(all must be true) over pack configuration fields and game settings. Grammar, types and error codes:
Config Expressions.
conditions: config: features.enableBonus && balance.ironSpeed >= 2 # or: config: [features.enableBonus, 'balance.ironSpeed >= 2']config: is allowed in pack.yml, in a document’s conditions: and on list entries, and is an
error inside a config definition. The keys conditions, conditionBehavior and
conditionBehaivor are also allowed on any map entry of any list at any depth (operations,
patches, ingredients, unlocks, value payload members, …). Before the document is applied an entry
whose condition is false is removed, and the condition keys of surviving entries are stripped. See
Conditions & Packs.
Dependency Ordering (include:)
Section titled “Dependency Ordering (include:)”include: lists relative file paths (same pack) whose documents must apply before this one
within the same stage. It is ordering, not textual inclusion. Cycles fall back to path order with a
warning.
Determinism
Section titled “Determinism”Apply order is deterministic: dependency-aware pack topology (priority descending and ref
ascending among ready packs) → stage → handler (priority descending, root type ascending) →
same-stage include: constraints → relative path. KDataForge does not negotiate a multiplayer pack
checksum or stream general operations between peers; only pack configuration
values are sent from the host to clients. See
Architecture.
