Skip to content
k-mods.com

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.

  • 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 name data-editor, or directly contains pack.yml. Names are case-insensitive. Root discovery prunes Content, Saved, Intermediate, Binaries, DerivedDataCache, Plugins, Source, and Config.
  • Roots may nest. Each YAML file belongs only to its deepest enclosing root.
  • All *.yml / *.yaml files 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.

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 present
name: My Balance Pack # display name (default: ref)
version: 1.2.0 # semver, informational + used by conflict reports
description: Short player-facing summary # optional; string or { key, ns, source }; shown by settings screens
icon: icon.png # optional; pack-relative png/jpg/jpeg/tga/bmp, or a /Game/... UTexture2D object path
priority: 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
- SomeOtherPack
redirects:
# 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.

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 Conditions
include:
# optional same-pack files that must apply BEFORE this one
- base-values.cdo.yml
# … type-specific content

priority: 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).

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: item
items:
- id: MySuperIngot
parent: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C
properties:
- path: mDisplayName
value: Super Ingot
- path: mResourceSinkPoints
value: 100
---
type: sinkpoints
track: Default
entries:
- item: /KDataForge/Gen/MyPack.Gen_MyPack_MySuperIngot
points: 100
  • Every document still runs through the normal pipeline independently, with its own debug:, conditions:, and include:, staged and ordered exactly like a document from a separate file.
  • The <name>.<type>.yml filename 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 own type: key. A document with no type: 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.

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:

TypeStageDetailed page
gametagGameTagsGame Tags
assetAssetsData Assets
soundAssetsWwise Sounds
dataassetAssetsData Assets
curveAssetsFloat and Vector Curves
localizationLocalizationLocalization
cdoCDOChangesPatching (type: cdo)
sublevelCDOChangesSublevels
itemItemsItems
resourceItemsResources
buildingItemsBuildings
unlockItemsUnlocks
classItemsCustom Classes
recipeRecipesRecipes
schematicSchematicsSchematics
research / mamResearchResearch Trees
sinkpointsSubsystemModsSink Points
resourcenodeSubsystemModsResources
custom (third-party)handler-declaredregistered 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 generic class) run in the Items stage, priority-ordered (class → unlock → item → resource → building), so later stages (recipe, schematic, research) can reference every generated class.

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: cdo
patches:
- # ---- 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.5

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: sublevel
block:
- /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.

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])*
ExampleMeaning
mManufactoringDurationplain property
mIngredients[0].Amountstruct member of array element 0
mSomeMap["Iron"].Valuestruct member of map value for key Iron
mSomeSet[2]third element of a set (logical order)
mMeshComponent.RelativeScale3Ddereference through an object-typed member (components, subobjects)
mIngredients[*].ItemClassany 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.

op: values and their argument keys. Full worked examples: Operations.

OpApplies toArguments
set (default)everythingvalue
add, subtract, multiply, divide, min, maxnumericvalue
clampnumericmin, max
appendarray, set, map, string/name/text, tag container, bitmaskvalue (map for map; scalar or sequence otherwise)
prependarray, set, string/name/text, tag container, bitmaskvalue (scalar or sequence)
insertarrayindex, value
removearray (by value), set (by value), map (by key), tag container, bitmaskvalue
remove_atarrayindex
cleararray, set, map, string/name/text, tag container, bitmasknone
swaparrayindex, with
replacearrayindex, value
duplicatearrayindex
sort, reversearraynone
uniquearray (set: no-op)none
copy, moveeverythingfrom (property path on the same object; move clears the source)

Unsupported op×type combinations are diagnostics at load time, never silent no-ops.

A few properties need more than a plain reflected write. KDataForge handles them automatically:

  • FGameplayTagContainer: accepts set/append/prepend/remove/clear with plain tag names (no native-struct text form needed).
  • mStackSize (any FGItemDescriptor-derived target): this is an EStackSize enum (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 dedicated UKBFLCDOItemStackSize helper. Any op that writes mStackSize also 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.

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 typeYAML form
booltrue / false / yes / no / on / off / 1 / 0
int32, int64, uint*, uint8integer scalar
float, doublenumber scalar
enumvalue 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, FNamestring scalar
FTextstring 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 referencefull object path; a bare generated-content id (e.g. SuperIngotIcon for a dataasset/asset entry, see Generated Content Short Ids); ""/none → null
class referenceclass 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 / TSoftClassPtrpath string (not loaded)
structnative text form ("(X=1,Y=2,Z=3)"), block map with named fields, or block positional sequence
FGameplayTagtag name string
FGameplayTagContainersequence of tag names
TArraysequence
TSetsequence
TMapmap (keys converted via the key property type)
nested structsnested maps, any depth

Any value above can instead be produced from pack configuration, game settings or reflected game data. Full rules and examples: Templates and Typed References.

FormMeaning
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, assetAn 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.

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: gametag
tags:
- tag: MyPack.Machines.Special # map form with comment
comment: Marks special machines
- MyPack.Simple # scalar shorthand

Tags 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 / unlock
items: # 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
TypePlural keyDefault parent
itemitems/Script/FactoryGame.FGItemDescriptor
resourceresources/Script/FactoryGame.FGResourceDescriptor
buildingbuildings/Script/FactoryGame.FGBuildingDescriptor
reciperecipes/Script/FactoryGame.FGRecipe
schematicschematics/Script/FactoryGame.FGSchematic
researchresearch/Script/FactoryGame.FGResearchTree
unlockunlocksnone: 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 named Gen_<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 _C suffix.
  • The id is the contract. Renaming a file is safe. Renaming an id needs a pack redirect (redirects: in pack.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).

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: item
items:
- 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 in mSmallIcon: SuperIngotIcon instead 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-only class:, 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: recipe
recipes:
- id: Recipe_MySuperIngot
register: true # default omit unless you want registration off
properties:
- path: mManufactoringDuration
value: 4

Removing 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: schematic
remove:
- /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, and research (including type: mam) support this key. item, resource, building, unlock, and class do 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 to FGRecipe / FGSchematic / FGResearchTree and 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’s UModContentRegistry, 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 of id: to register an existing class without generating one:
type: schematic
schematics:
- class: /Game/SomeMod/Schematics/Schematic_Hidden.Schematic_Hidden_C # register, don't generate

Nodes: 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: research
research:
- id: MyResearchTree
autoPath: true
addNodes:
- schematic: Schematic_First
coordinate:
X: 0
Y: 0
- schematic: Schematic_Second
coordinate:
X: 2
Y: 0
parents:
- schematic: Schematic_First
  • schematic: accepts a generated short id or a full UFGSchematic class path.
  • coordinate: is a block map with numeric X: and Y: fields. Supply it for a node that participates in graph relationships or roads.
  • parents:, nodesToUnhide:, and unhiddenBy: are optional. Each reference may name another node’s schematic: or a literal coordinate. References may point forward or backward within the same addNodes: list, or to a node already present on the target tree.
  • autoPath: defaults to false. When enabled, each resolved parents: relationship gets a straight, two-point visual road. Leave it off and provide childrenAndRoads: on the parent for a bent or otherwise custom route.
  • class: optionally selects a UFGResearchTreeNode subclass; 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: 2

Use 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_ExistingParent

autoPath: 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.

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_EarlyGame

Instanced 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: schematic
schematics:
- 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_MySuperIngot

Known 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.

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: schematic
schematics:
- 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: true

Pair 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.

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: class
debug: 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, and redirects: all apply identically.
  • Runs in the Items stage (same as item/resource/building/unlock), so recipe, schematic, and research documents can reference the generated classes.
  • registerAs: queues the class into SML’s UModContentRegistry like 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.

Loads image files from the pack and turns them into real, referenceable UTexture2D assets:

type: asset
assets:
- id: MyIcon
file: icons/my-icon.png # relative to the pack root; png / jpg / tga / bmp
  • Decoded at load into a transient UTexture2D at /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: dataasset
conditions:
hasMod: KAPI
assets:
- 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: 1000

After 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: curve
curveType: float # float | vector
curves:
- 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: 0

preInfinityExtrap 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: sinkpoints
track: Default # Default | Exploration
entries:
- 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: 0

An 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: resourcenode
remove:
- /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 a UFGResourceDescriptor class. Generated-content short ids work. Alternatively an entry carries an ofClass: scope (default FGResourceDescriptor) 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 are Node, FrackingSatellite, FrackingCore, Geyser, and Deposit, with or without the EResourceNodeType:: prefix.
  • allowOccupied: defaults to false. The default skips and warns about node groups used by an extractor. true can invalidate an extractor’s node reference.
  • removeFromScanner: defaults to true. It also strips vanilla scanner pairs and matching KAPI allow-list data, then refreshes scanner/radar caches.

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: localization
namespace: MyPack
culture: de # optional; prefix-matched against current language; omit = all
entries:
SimpleKey: 'Übersetzter Text'
KeyWithSource:
source: 'Original English'
text: 'Deutscher Text'
# override existing texts
  • culture: is prefix-matched against the current language (de matches de-DE). Omitting it applies the entries for every language.
  • The scalar form sets the text for namespace/key. A block map with source and text fields overrides existing texts by their original source string.
  • Localization documents apply during GameInstance initialization. Restart the session after edits.

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 error
category: 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: hot

Document 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:

KeyApplies toRule
idallRequired. 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.
typeallRequired: text, number, boolean, select.
valueTypealltext: 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, descriptionallRequired. A non-empty string or {key, ns, source}.
tooltipallOptional {title, text}, each a string or {key, ns, source}.
categoryallA 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.
defaultallRequired. A list when multiple: true. Must satisfy the type’s constraints.
applyallhot or cold (default).
orderallInteger. Default: declaration index across the pack’s config files.
multipletext, number, selecttrue makes a list (text, number) or a multi-select (select). Not allowed for boolean.
min, maxnumberWhole numbers for integer types; min <= max. Apply to every element of a list.
stepnumberGreater than 0.
decimalsnumber0 to 9. Default: digits of step, else 4 (float) or 6 (double).
displaynumberslider (needs min < max, not with multiple) or input. Default: slider when both bounds exist.
multilinetextOnly with valueType: text.
maxLengthtext1 to 1024, default 1024.
tagRoottext with valueType: tagA gameplay tag; only tags below it are accepted.
optionsselectRequired, at least one {value, label}. Values unique and of the valueType; label is a string or {key, ns, source}.
minSelected, maxSelectedselect with multipleminSelected is at most the number of options; maxSelected is not below minSelected and at most 64.
minCount, maxCounttext, number with multipleList length bounds, at most 64. A list without maxCount holds at most 64 entries.
descriptionClass, propertiesallReserved, 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.

Every applied op can be traced. Three levels, from narrow to global:

LevelHowEffect
Documentdebug: true top-level key in any document typeEvery applied op from that document logs
Packdebug: true in pack.ymlEvery applied op from every document of the pack logs
Globalconsole 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.

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 sequence

conditions: 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; default
conditions:
- 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: true

An 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: OR
conditions:
- hasMod: RefinedPower
- hasMod: FicsitFarming

OR 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.

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.

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.