CraftingEngine
Executes the crafting pipeline. It validates ingredients, runs checks, consumes items, and creates results.
Access: game.fabricate.getCraftingEngine()
Methods
craft(craftingActor, componentSourceActors, recipe, ingredientSetId, options)
The main crafting method. Runs the full pipeline for a single step.
| Parameter | Type | Description |
|---|---|---|
craftingActor | Actor | The actor receiving crafted items |
componentSourceActors | Actor[] | Actors supplying ingredients |
recipe | Recipe | The recipe to craft |
ingredientSetId | string \| null | Which ingredient set to use. null auto-selects the first satisfiable set |
options | object | Optional: { runId, resultGroupId } |
Returns: Promise<{ success: boolean, results: Item[], message: string }>
Pipeline Steps
When craft() is called, the engine:
- Validates ingredients. Checks all groups in the selected ingredient set are satisfiable.
- Validates tools. Checks all required tools (resolved from
toolIds) are present in the source actors’ inventories and pass their requirement. - Validates essences. If essences are enabled, checks essence requirements.
- Runs crafting check. When the resolution mode has a usable check (an authored roll formula), rolls it and interprets the result per the resolution mode. See Crafting Checks.
- Applies failure consumption policy. If the check fails, consumes ingredients and/or applies tool breakage according to
craftingCheck.consumptionsettings. By default, ingredients are consumed (consumeIngredientsOnFail: true) and tools are not broken (breakToolsOnFail: false, renamed from the legacyconsumeCatalystsOnFail). See Failure consumption policy. - Resolves result groups. Determines which result group(s) to create based on mode and check result.
- Consumes ingredients. Removes consumed items from source actors.
- Applies tool breakage. Runs each tool’s breakage mechanic (
limitedUses/breakageChance/diceExpression) and its on-break action, recordingusedToolsevidence. - Creates results. Creates new items on the crafting actor.
- Applies property macros. When
system.features.propertyMacrosistrue, first runs each contributing, enabled essence’s ownpropertyMacroUuidmacro (inessenceDefinitionsorder), then the result’s ownpropertyMacroUuidmacro, applying each returned property change to the created item as it is returned. Requiressystem.features.essencesfor the per-essence half. A macro whose return cannot be applied, or an essence macro whose own body throws, is skipped in isolation, leaving every other essence macro and the result’s own macro unaffected. Only a script Macro runs; a non-script or unresolvablepropertyMacroUuidis skipped silently. See Essences. - Transfers effects. If
system.features.essences,system.features.effectTransfer, andrecipe.transferEffectsare alltrue, collects active effects from thesourceItemUuidof each contributing, enabled essence definition and copies them to the result item. A disabled essence’s active effects are never collected. See Effect Transfer.
Step 5 only executes when the crafting check returns a failure result or check-result validation fails. Pre-check failures (missing ingredients, missing or unsatisfied tools, invalid recipe, missing actor) return immediately without consuming anything.
Crafting Check Execution
Step 4 of the pipeline rolls the check for the active resolution mode. A check is only “usable” when its resolution-mode sub-object carries an authored roll formula.
| Resolution mode | Sub-object | Usable when |
|---|---|---|
simple | system.craftingCheck.simple | simple.rollFormula is set |
routedByCheck | system.craftingCheck.routed | routed.rollFormula is set |
routedByIngredients | system.craftingCheck.simple | simple.rollFormula is set (the check is optional and does not select the result) |
progressive | system.craftingCheck.progressive | progressive.rollFormula is set |
alchemy, checkMode: 'none' | — | never (a matched brew always succeeds) |
alchemy, checkMode: 'simple' | system.craftingCheck.simple | mandatory (simple.rollFormula must be set) |
alchemy, checkMode: 'tiered' | system.craftingCheck.routed | mandatory (routed.rollFormula must be set) |
In simple mode the check is optional. It runs only when simple.rollFormula is set and the system enables crafting checks (craftingCheck.enabled is true or the system’s craftingChecks feature is on).
In alchemy mode the check is driven by the system-level alchemy.checkMode, not the craftingChecks toggle. none runs no check, and a matched brew always succeeds. simple runs the mandatory pass/fail check from craftingCheck.simple. tiered runs the mandatory routed check from craftingCheck.routed, exactly like routedByCheck. Both simple and tiered are mandatory, so a missing roll formula is a misconfiguration that fails the attempt loudly before anything is consumed.
Routed-by-check and progressive modes both require a usable check. When the resolution mode requires a check but no roll formula is configured, the engine fails loudly with success: false and a message of the form <mode> mode requires a configured crafting check roll formula, so the misconfiguration is visible. When an optional check has no roll formula to run, the engine treats it as a no-op success.
Dynamic DC Macro
The simple pass/fail check can compute its DC from a macro instead of a static value. Before the macro runs, Fabricate resolves an anchor DC: the recipe’s selected difficulty tier’s DC when recipe.checkTierId names a tier that still exists, and the check’s configured static dc otherwise. When simple.dcMode is "dynamic", the engine runs the macro at system.craftingCheck.simple.macroUuid, passes it that anchor DC, and uses the macro’s returned number as the DC. If the macro is absent or throws, the engine falls back to the anchor DC.
The routed check’s system.craftingCheck.routed slot carries the same DC source chooser and DC macro, with the macro at system.craftingCheck.routed.macroUuid. It resolves through the same anchor-then-macro path the simple slot uses, so the anchor-DC prose above applies to it unchanged. A routed relative check’s tier thresholds (dc + outcome.dc) shift with the resolved DC exactly as the simple check’s DC does. A routed fixed check has no DC and does not offer the DC source chooser. This dynamic-DC macro is the only macro the crafting check still uses, and it only computes the DC. It never resolves the check outcome itself.
Create a Script Macro and use its command as the following example.
console.log('Fabricate Dynamic DC inputs', {
recipe: scope.recipe,
craftingSystem: scope.craftingSystem,
craftingActor: scope.craftingActor,
candidateIngredientSet: scope.candidateIngredientSet,
anchorDc: scope.anchorDc,
});
return scope.candidateIngredientSet ? 17 : 15;
Use scope as the recommended payload identifier. scope.recipe is serialized recipe data. scope.craftingSystem is the normalized crafting system object. scope.craftingActor is the crafting Actor. scope.candidateIngredientSet is the selected ingredient set, or null when no set is selected. scope.anchorDc is the anchor DC described above, resolved before the macro runs.
Fabricate also provides context and args as aliases of the identical object. The aliases have object identity, so scope === context && context === args is true. This is not full native Macro#execute behavior. Native Foundry macro execution uses a rest copy for its scope and adds its own speaker, actor, token, and character locals. Fabricate provides none of those native locals.
Fabricate evaluates Number(result) on the return value. When that number is finite, Fabricate truncates it to an integer and uses it as the DC. An absent Macro, a thrown error, or a non-finite result all leave the anchor DC in force.
Foundry runtime globals game, foundry, ui, and fromUuid remain directly available to the Macro. Fabricate directly evaluates the Script Macro command instead of calling native Macro#execute. This avoids the current player’s Macro document permission gate for a GM-configured Macro during a player-initiated craft. The bypass adds no server or document authority. The script still runs as the current player.
Effect Transfer Gating
Effect transfer in step 12 is controlled by an essence-based pipeline and requires all three of the following flags to be set:
system.features.essences === true. The essences system must be enabled. Effect transfer is built on top of essence definitions and requires this feature to resolve source items.system.features.effectTransfer === true. The GM opts the system in via the Features card in the Crafting Admin panel.recipe.transferEffects === true. The recipe author opts this specific recipe in.
If any of these flags is false, _transferEffects is not called and no effects are copied.
How effects are collected. When all three flags are met, the engine determines which essence IDs were contributed by the resolved ingredients, then for each contributing essence looks up its EssenceDefinition in system.essenceDefinitions. If the definition has a sourceItemUuid, that item is fetched via fromUuid() and its active effects are collected. All collected effects are applied to the created result item in a single createEmbeddedDocuments('ActiveEffect', ...) call. Essence definitions with no sourceItemUuid, or whose UUID no longer resolves, are silently skipped.
The old ingredient-level
extractEffects/effectFilterapproach has been removed. SettingextractEffects: trueon an ingredient has no effect. Configure effect transfer through essence definitions and theirsourceItemUuidfield instead.
// This recipe will transfer effects when the system has
// features.essences: true AND features.effectTransfer: true.
const recipe = {
transferEffects: true,
// ...other recipe fields
};
// This recipe will NOT transfer effects regardless of system settings.
const nonTransferRecipe = {
transferEffects: false,
// ...other recipe fields
};
Essence definitions drive which effects are transferred. If the resolved ingredients contribute a “fire” essence, and the system has a Fire essence definition with a sourceItemUuid pointing to a “Flame Shard” item, all active effects on that Flame Shard are copied to the result. See Essences for how to configure essence definitions with source items.
Example
const engine = game.fabricate.getCraftingEngine();
const rm = game.fabricate.getRecipeManager();
const recipe = rm.getRecipe('my-recipe-id');
const actor = game.user.character;
const result = await engine.craft(
actor, // crafting actor
[actor], // ingredient sources
recipe, // recipe
null, // auto-select ingredient set
{} // no special options
);
if (result.success) {
console.log(`Crafted: ${result.results.map(i => i.name).join(', ')}`);
} else {
console.log(`Failed: ${result.message}`);
}
Using the Quick Helper
For simpler cases, use the top-level craft() method.
const result = await game.fabricate.craft(actor, 'recipe-id', {
componentSourceActors: [actor, partyChest]
});
Or from a macro:
await fabricate.craft(game.user.character, 'recipe-id');