Type: recipe
Generating new recipes from YAML, registering existing recipe classes, and how registration into SML's content registry actually happens.
The recipe type runs in the Recipes stage, which comes after the whole Items stage. That
means a recipe can freely reference anything item/resource/building/unlock you just
generated. It loads changes only during session initialization, so a change here needs a restart. Its entries key is
recipes, its default parent is /Script/FactoryGame.FGRecipe, and its required base class is
/Script/FactoryGame.FGRecipe (or a subclass of it). It is also the first type to support
register-only mode via class:.
recipe is the first content-creation type you’ll meet that supports two modes: generate a
brand-new recipe class (id:), or register an existing class you didn’t generate (class:).
Both modes end with the recipe queued into SML’s UModContentRegistry (unless you turn that off).
Mode 1: generate a new recipe
Section titled “Mode 1: generate a new recipe”A generated recipe is just a subclass of FGRecipe whose CDO you set up entirely through
properties:. mIngredients and mProduct describe the item/amount pairs consumed and produced,
mManufactoringDuration is the craft time in seconds, and mProducedIn lists which buildable(s)
can craft it. super-ingot.recipe.yml puts all of that together for a recipe that turns 2 iron
ingots into 1 of a generated item, craftable in a Smelter Mk.1:
type: reciperecipes: - id: Recipe_MySuperIngot properties: - path: mDisplayName value: Super Ingot - path: mIngredients value: - ItemClass: /Game/FactoryGame/Resource/Parts/IronIngot/Desc_IronIngot.Desc_IronIngot_C Amount: 2 - path: mProduct value: - ItemClass: /KDataForge/Gen/MyPack.Gen_MyPack_MySuperIngot Amount: 1 - path: mManufactoringDuration value: 4.0 - path: mProducedIn value: - /Game/FactoryGame/Buildable/Factory/SmelterMk1/Build_SmelterMk1.Build_SmelterMk1_CNo parent: is needed here, since it defaults to FGRecipe. No include: is needed for the
Gen_MyPack_MySuperIngot product reference either, because Items has already fully run by the
time Recipes starts.
Mode 2: register an existing class, no generation
Section titled “Mode 2: register an existing class, no generation”Sometimes you, or another mod, already has a compiled or Blueprint recipe class sitting unused.
Maybe it’s a hidden recipe another mod ships but never registers, or one you built yourself in the
editor as a Blueprint. Swap id: for class: and KDataForge registers it without generating
anything:
type: reciperecipes: - class: /Game/SomeMod/Recipes/Recipe_Hidden.Recipe_Hidden_C # register, don't generateproperties: still works in this mode if you want to tweak the existing class’s CDO at the same
time you register it.
Removing recipes
Section titled “Removing recipes”A type: recipe document can also declare a top-level remove: sequence. Each entry is either a
bare class reference (a full Unreal class path, a bare loaded class name, or a generated content id
from this or another loaded KDataForge pack) or a filter map that removes every recipe passing
the scope filters
(producedIn:, ingredient:, product:, matchName:, matchTag:, where:; the scope defaults
to FGRecipe). Removal-only documents are valid, and remove: can also be mixed with normal
recipes: entries in the same document:
type: reciperemove: - /Game/FactoryGame/Recipes/Constructor/Recipe_IronPlate.Recipe_IronPlate_C - Recipe_IronPlateReinforced_C - Recipe_MyGeneratedRecipe - producedIn: Build_HadronCollider_C # every Particle Accelerator recipe - matchName: Recipe_Alternate_* # every alternate recipeA bare reference names the exact recipe class to suppress; it does not remove subclasses. If a
generated id is ambiguous across packs, KDataForge logs the same ambiguity diagnostic used for
other short-id class references. Filter maps are stored as rules and re-checked on every recipe
grant, so recipes that load later in the session are covered. Removals are applied after queued
registrations, so an explicit remove: wins over a register: for the same class. The
consolidated guide, including how recipe removal interacts with schematics and saves, is
Removing Content.
Recipe removal is the one content removal kind that writes back into the save. SML has no
RemoveRecipe API, and the authoritative list is the save-backed recipe manager list. KDataForge
therefore blocks future recipe grants with native hooks and sweeps recipes restored from an
existing save during world initialization. Uninstalling the pack self-heals on the next load because
purchased schematics re-apply their recipe unlocks without the removal hook.
Packaged runtime builds install both singular and batch recipe-add prevention hooks. Unreal Editor PIE still runs the initialization sweep, but those native hooks are compiled out there; a recipe granted again later in the same PIE session can therefore reappear. Verify prevention behavior in a packaged game session.
Removal never revokes schematic purchases. If a schematic was already purchased, that purchase stays
in the save; only the named recipe is kept out of the available recipe list while the pack is
installed. To hide the unlock source as well, pair this with
schematic remove:.
register: and auto-propagation
Section titled “register: and auto-propagation”Every recipe entry, generated or register-only, accepts a register: flag. It decides whether the
class actually gets queued for UModContentRegistry registration at all, as opposed to merely
existing so other YAML can point at it:
type: reciperecipes: - id: Recipe_MySuperIngot register: true # default omit unless you specifically want to skip registration properties: - path: mManufactoringDuration value: 4register: true is the default, so the recipe is queued unless you set it to false explicitly.
SML’s own auto-propagation then takes over: registering a schematic that unlocks this recipe also
registers the recipe, and the recipe’s ingredient and product items, automatically. You don’t have
to register every layer by hand, just the top of the chain you actually care about, which is
usually the schematic.
Set register: false if you want the class to exist, so other YAML can reference it, but not be
independently discoverable. This is useful for an internal helper recipe that only ever appears
nested inside something else.
Editing existing recipes in bulk
Section titled “Editing existing recipes in bulk”Changing recipes that already exist is a type: cdo job, and recipes have three selectors of their
own: producedIn: (crafted in a building, or any building deriving from a base class),
ingredient: and product: (consume / produce an item, or any item deriving from a base class).
They default their scope to FGRecipe, accept a single class or a list, and combine with each
other and with where::
type: cdopatches: - producedIn: /Script/FactoryGame.FGBuildableManufacturer # every machine recipe … ingredient: Desc_IronIngot_C # … that consumes Iron Ingot properties: - path: mManufactoringDuration op: multiply value: 0.8Every form is covered on Targeting, and Balancing has copyable examples.
Troubleshooting
Section titled “Troubleshooting”- “My new item has no recipe in a fresh save”: check that something registers the recipe.
Either
register: truehere (the default), or a schematic that unlocks it, whose own registration auto-propagates down to this recipe, will do it. A recipe that’s generated but never registered anywhere simply never reachesUModContentRegistry, and the game won’t know it exists. - “class: gave me an error about register-only mode”:
class:(register-only) is only valid forrecipe,schematic, andresearchdocuments.item/resource/building/unlockcan only generate (id:). See Type: item for why. - A full pack tying a generated item + this recipe + a schematic + translations together lives in End-to-End Pack.
