Data Models
These model classes are exported via game.fabricate.api:
const { Recipe, Ingredient, IngredientGroup, IngredientSet, Result } = game.fabricate.api;
IngredientSet and Result are documented below as recipe data shapes. They are normalised through recipe and manager APIs, but they are not exported as public constructors.
Recipe
new Recipe({
id, // string -- auto-generated if omitted
name, // string
description, // string
category, // string
craftingSystemId, // string -- links to a crafting system
enabled, // boolean (default true)
locked, // boolean (default false)
allowPlayerResultReorder, // boolean (default true) -- progressive mode only
linkedRecipeItemUuid, // string | null
visibility: {
restricted, // boolean (default false)
allowedUserIds, // string[]
},
ingredientSets, // Ingredient-set data[] (single-step)
steps, // object[] (multi-step)
resultGroups, // object[]
toolIds, // string[] (library Tool ids required for crafting)
transferEffects, // boolean (default false)
resultSelection: {
// object (legacy, retired — round-trip only)
provider, // "ingredientSet" | "check"
},
metadata, // object (created, modified, author, version)
});
allowPlayerResultReorderis the GM-authored permission for player re-ordering of this recipe’s progressive result stages (issue 651). It defaults totrue, and an absent key reads astrue, which is why the 1.17.0 migration does not seed it. Only an explicitfalsepins the authored stage order. It is read in progressive mode only, and it replaces the retired system-levelcraftingCheck.progressive.allowPlayerReorder. The salvage equivalent iscomponent.salvage.allowPlayerResultReorder.
The per-recipe
resultSelection.provideris retired (issue 554). No live resolution mode reads it: alchemy routing moved to the system-level alchemy check mode (system.alchemy.checkMode), and the two routed crafting modes (routedByIngredientsandroutedByCheck) derive their routing basis from the system mode. The field is still normalised so a legacy recipe round-trips until migration strips it, but authoring no longer sets it.routedByIngredientsroutes by eachIngredientSet.resultGroupId, androutedByCheckroutes by the system’s routed crafting-check outcome. The legacyoutcomeRoutingfield and the legacymapped/tieredand singleroutedmodes are normalised on load.mappedbecomesroutedByIngredientsandtieredbecomesroutedByCheck.
Key methods:
| Method | Returns | Description |
|---|---|---|
validate() | {valid, errors} | Validates recipe structure |
getResultDescription() | string | Human-readable result summary |
isSimpleRecipe() | boolean | True if no tags/essences/tools/steps |
getExecutionSteps() | object[] | Steps array (converts implicit step if single-step) |
toJSON() | object | Serialise to JSON |
Recipe.fromJSON(data) | Recipe | Deserialise from JSON |
Recipe.createSimple(name, ingredients, result) | Recipe | Helper for simple recipes |
IngredientSet Data Shape
{
(id, // string
name, // string
ingredientGroups, // IngredientGroup[] -- all must be satisfied (AND)
essences, // { [essenceId]: quantity }
toolIds, // string[] (library Tool ids required for this set)
resultGroupId); // string | null (routedByIngredients routing target)
}
Key methods:
| Method | Returns | Description |
|---|---|---|
Recipe.validate() | {valid, errors} | Validates nested ingredient-set data as part of the recipe |
RecipeManager.evaluateCraftability(recipe, actors) | object | Evaluates whether an actor inventory can satisfy ingredient-set data |
IngredientGroup
IngredientGroup.fromJSON({
id, // string
name, // string
options, // Ingredient[] -- any one satisfies the group (OR)
});
Ingredient
new Ingredient({
quantity, // number (default 1)
match: {
type, // "component" | "tags" | "currency" | "essence"
componentId, // string (for component type)
tags, // string[] (for tags type)
tagMatch, // "any" | "all" (for tags type)
unit, // string (currency unit id, for currency type)
essenceId, // string (for essence type)
amount, // number (currency cost or essence amount, for currency/essence types)
},
extractEffects, // boolean (default false)
effectFilter, // string | null (regex for filtering effects)
});
The
match.typevalue"component"replaces the previous"systemItem". Thematch.componentIdfield replaces the previousmatch.systemItemId. Use the new names for all new data.
Key methods:
| Method | Returns | Description |
|---|---|---|
matches(item) | boolean | Check if a Foundry item satisfies this ingredient |
getDescription() | string | Human-readable description |
validate() | {valid, errors} | Validates structure |
Tool
The standalone
Catalystmodel was removed in0.6.0. Tools are not constructed viagame.fabricate.api. They are authored in the per-system Tools library through the Crafting System Manager and referenced by id (toolIds). See Tools for the full concept.
A Tool entry stored under system.tools (the craftingSystems setting) has this shape:
{
(id, // string (library id, referenced by toolIds)
componentId, // string | null (optional managed-component link; null for an item-sourced tool)
name, // string | null (display snapshot captured at registration/migration)
img, // string | null (display snapshot image)
registeredItemUuid, // string | null (the tool's own registered source item uuid)
originItemUuid, // string | null (the tool's own canonical/compendium source uuid)
aliasItemUuids, // string[] (additional source references for matching)
label, // string (optional user-authored display label, distinct from the snapshot)
requirement, // null | { formula } (a Foundry roll expression; required when set)
prerequisites, // { enabled, ids, gateMode: 'bonus' | 'usability' }
bonus, // { enabled, expression }
breakage, // { mode: 'limitedUses', maxUses } |
// { mode: 'breakageChance', breakageChance } |
// { mode: 'diceExpression', formula, threshold }
checkBreakable, // boolean (default true; participates in check-driven breakage)
onBreak, // { mode: 'destroy' } | { mode: 'flagBroken' } |
// { mode: 'replaceWith', replacementTarget }
repairRequirements); // IngredientGroup[] (for flagBroken; may be empty)
}
A Tool is first-class as of issue 561: it carries its own source references (registeredItemUuid / originItemUuid / aliasItemUuids, renamed in issue 560) and a name / img display snapshot, so it can be registered directly from an Item without importing that Item as a component. componentId is optional. It is null for an item-sourced tool and populated only for a tool that is also a managed component (a whetstone) or one migrated from a legacy component-linked tool. A valid Tool carries either a componentId or its own source references.
prerequisites references the crafting system’s shared character prerequisites. Its gate mode either controls whether the character can use the Tool or suppresses its enabled bonus. bonus is an optional check expression. checkBreakable: false excludes the Tool from check-driven breakage only. It does not replace the retained tool-specific breakage mechanic. For replaceWith, replacementTarget is exactly one managed Component or direct Item UUID. The current Tool Studio authors managed Component targets only. Direct Item targets remain readable and executable for backward compatibility with existing data. repairRequirements holds the optional ingredient groups used to repair a Tool marked as broken.
Per-item usage for limitedUses tools is tracked under Item.flags.fabricate.toolUsage = { timesUsed }. The counter changes only while the system uses Tool-specific breakage. Under check-driven authority the retained Tool-specific configuration and its counters are inactive and unchanged. The flagBroken on-break action sets Item.flags.fabricate.toolBroken = true. A tool’s durable identity is stamped on its source Item as Item.flags.fabricate.roles[systemId].toolId, a sibling of the component role flag, so the same Item can be both a component and a tool.
Result Data Shape
{
(id, // string
componentId, // string (managed component reference)
itemUuid, // string (direct Foundry item reference)
quantity, // number (default 1)
propertyMacroUuid); // string | null
}
The field was previously named
systemItemId. UsecomponentIdfor all new data.
Result data is validated as part of recipe validation and consumed by the crafting engine when a result group is awarded.
Related methods:
| Method | Returns | Description |
|---|---|---|
Recipe.validate() | {valid, errors} | Validates nested result data as part of the recipe |
Recipe.getResultDescription() | string | Human-readable result summary |