API Reference
Fabricate exposes its API through two Foundry globals:
game.fabricate. The main Fabricate instance with service accessors and a quickcraft()helper.globalThis.fabricate(alias:fabricate). Convenience functions for macros.game.fabricate.api. Constructor references for all public classes, plus public constants (HOOKS— the published hook names).
All APIs are available after the fabricate.ready hook fires:
Hooks.on('fabricate.ready', () => {
const recipeManager = game.fabricate.getRecipeManager();
// API is now safe to use
});
Do not call Fabricate APIs before the
fabricate.readyhook. The module initialises during Foundry’sreadyhook, and services are not available until initialisation completes.
Quick Reference
Services And Runtime Methods
game.fabricate.getRecipeManager() // Recipe CRUD and queries
game.fabricate.getCraftingEngine() // Execute crafting
game.fabricate.getCraftingSystemManager() // System and component CRUD
game.fabricate.getCraftingRunManager() // Multi-step run management
game.fabricate.salvageComponent({ actorId, systemId, componentId, interactive }) // Salvage one owned component
game.fabricate.salvageComponents({ actorId, targets, interactive, onProgress }) // Salvage many owned components in one run
game.fabricate.destroyComponents({ actorId, targets, onProgress }) // Permanently destroy many owned components in one run
game.fabricate.getGatheringEnvironmentStore() // Gathering environment persistence
game.fabricate.getGatheringRunManager() // Gathering run persistence
game.fabricate.getGatheringGateAndCheckEvaluator() // Gathering gate/check evaluation
game.fabricate.getGatheringRichStateService() // Gathering rich-state internals
game.fabricate.getItemPilesIntegration() // Item Piles integration facade
game.fabricate.listGatheringForActor({ actor }) // Player-visible gathering listing
game.fabricate.startGatheringAttempt({ actor, environmentId, taskId }) // Start gathering
game.fabricate.getGatheringDropBreakdown({ environmentId, taskId }) // Task drop preview data
game.fabricate.listAlchemyForActor({ actorId, craftingSystemId }) // Leak-safe player alchemy listing
game.fabricate.submitAlchemyAttempt({ actorId, craftingSystemId, submittedComponentIds }) // Brew an alchemy attempt
game.fabricate.getSelectedAlchemySystemId() // Persisted last-selected alchemy system id
game.fabricate.setSelectedAlchemySystemId(id) // Persist the last-selected alchemy system id
game.fabricate.listSelectableActors() // Player-character actors for the actor-selection bar
game.fabricate.getSelectedGatheringActorId() // Persisted remembered gathering actor id
game.fabricate.setSelectedGatheringActorId(id) // Persist the remembered gathering actor id
game.fabricate.getHideUnavailableEnvironments() // Player "hide unavailable environments" toggle (per client/device)
game.fabricate.setHideUnavailableEnvironments(value) // Persist that toggle (per client/device)
game.fabricate.getGatheringConditions() // Current gathering weather/time and vocabularies
game.fabricate.setGatheringWeather(weatherTag) // GM-only gathering weather update
game.fabricate.setGatheringTimeOfDay(timeOfDayTag) // GM-only gathering time update
game.fabricate.setGatheringConditions({ weather, timeOfDay }) // GM-only conditions update
game.fabricate.getGatheringPartyStore() // Fabricate-managed gathering parties (world-level)
game.fabricate.getGatheringRealmStore() // Per-system gathering realm CRUD and settings (was getGatheringRegionStore — deprecated alias kept)
game.fabricate.getGatheringLocationService() // Party current-realm resolution
game.fabricate.getGatheringLocationForActor({ actor, systemId }) // Redaction-safe current-realm summary
game.fabricate.setGatheringPartyRealmOverride({ partyId, systemId, realmIds }) // GM-only realm override (was setGatheringPartyRegionOverride)
game.fabricate.clearGatheringPartyRealmOverride({ partyId, systemId }) // GM-only override clear
game.fabricate.revealGatheringRealmForActor({ actor, systemId, realmId }) // GM-only discovery reveal
game.fabricate.hideGatheringRealmForActor({ actor, systemId, realmId }) // GM-only discovery hide
game.fabricate.getGatheringEconomy({ systemId }) // Gathering economy block
game.fabricate.setGatheringEconomy({ systemId, economy }) // GM-only economy update
game.fabricate.getRecipeVisibilityService() // Visibility and knowledge
game.fabricate.resetActorKnowledge({ actorId, systemId, freeLearnBudget }) // GM-only reset of one actor's learned recipes (one system, or all systems when systemId is null)
game.fabricate.getResolutionModeService() // Mode validation and resolution
Global Macro Helpers
fabricate.createSimpleRecipe(name, ingredients, result)
fabricate.craft(actor, recipeId, options)
fabricate.listRecipes(filters)
fabricate.deleteRecipe(recipeId)
fabricate.getAvailableRecipes(actorOrActors)
fabricate.openRecipeManager()
fabricate.listCraftingSystems()
Class Constructors
const {
Recipe, Ingredient, IngredientGroup,
RecipeManager, CraftingEngine,
getFabricateAppClass,
loadCraftingSystemManagerAppClass, getCraftingSystemManagerAppClass,
CraftingSystemManager,
CraftingRunManager, SalvageRunManager,
GatheringEnvironmentStore, GatheringRunManager,
GatheringRealmStore, GatheringPartyStore,
GatheringLocationService,
GatheringGateAndCheckEvaluator, GatheringEngine,
RecipeVisibilityService, ResolutionModeService,
SignatureValidator, ItemPilesIntegration,
CompendiumImporter, CraftingSystemExporter
} = game.fabricate.api;
The GM crafting-system-manager app is loaded from a separate on-demand chunk so non-GM players never download it. Open it programmatically through the async accessor, which loads (and registers) the class the first time it is called, then caches it:
const AppClass = await game.fabricate.api.loadCraftingSystemManagerAppClass();
AppClass.show();
The synchronous getCraftingSystemManagerAppClass() still exists for backward compatibility, but it now throws until the manager has been opened at least once (i.e. until loadCraftingSystemManagerAppClass(), the header button, or fabricate.openRecipeManager() has loaded the chunk). Prefer the async accessor.
Gathering Runtime Facade
Use the public game.fabricate methods when macros or integrations need to list or start gathering for the current user. The raw gathering engine is not exposed as a service accessor. These facade methods inject the current Foundry user as the viewer before delegating to runtime internals.
Hooks.once('fabricate.ready', async () => {
const actor = game.user.character;
const listing = await game.fabricate.listGatheringForActor({ actor });
const environment = listing.environments[0];
const task = environment?.tasks?.[0];
if (environment && task?.attemptable) {
await game.fabricate.startGatheringAttempt({
actor,
environmentId: environment.id,
taskId: task.id
});
}
});
listGatheringForActor returns the current browsing state plus activeRuns and recent history for the selected actor. Those run lists are retained even when no environment/task rows are currently browseable because the actor is blocked, the environment list is empty, or visibility gates hide all tasks. For non-GM blind or missing-environment rows, the runtime redacts task IDs, result details, tool details, diagnostics, and check internals.
When rememberedActorId is omitted from listGatheringForActor options, it defaults to the persisted last-gathering selection (getSelectedGatheringActorId()), so a fresh listing honors the remembered actor. Passing an explicit rememberedActorId always overrides that default. This includes an explicit null, which forces no remembered actor.
Alchemy Runtime Facade
Use these public game.fabricate methods when macros or integrations need to list or brew alchemy for the current user. They back the player Alchemy Workbench and inject the current Foundry user as the viewer before delegating to runtime internals.
Hooks.once('fabricate.ready', async () => {
const actorId = game.user.character?.id;
const craftingSystemId = game.fabricate.getSelectedAlchemySystemId(); // '' when unset
// Leak-safe listing: learned recipes, owned components, and the COUNT of
// undiscovered recipes only. Undiscovered names, signatures, and results are
// never returned.
const listing = game.fabricate.listAlchemyForActor({ actorId, craftingSystemId });
// Brew a combination: one component id per placed unit (a stack of N appears N times).
const submittedComponentIds = listing.recipes[0]?.concrete
? Object.entries(listing.recipes[0].concrete).flatMap(([id, qty]) => Array(qty).fill(id))
: [];
if (submittedComponentIds.length > 0) {
await game.fabricate.submitAlchemyAttempt({
actorId,
craftingSystemId,
submittedComponentIds,
interactive: true
});
}
});
listAlchemyForActor({ actorId, craftingSystemId, componentSourceActorIds })returns the leak-safe alchemy listing model for the resolved crafting actor, scoped to the chosen alchemy (crafting) system. It is owner-scoped through the same gate as crafting, so a non-owner viewer receives a denied, empty listing rather than another user’s inventory or fizzle memory. A GM sees every enabled recipe as known.actorIdandcomponentSourceActorIdsdefault to the persisted crafting selections when omitted.submitAlchemyAttempt({ actorId, craftingSystemId, submittedComponentIds, componentSourceActorIds, interactive })brews the submitted components as an attempt for the resolved actor. It is owner-scoped likelistAlchemyForActorand delegates to the authoritative engine, which matches the submission against all enabled recipes in the system, known and undiscovered alike. On a match the recipe is discovered and the ingredients are consumed. The system’s alchemy check mode decides the result:nonealways produces the success set, asimplecheck produces the success set on a pass and the reserved failure set on a fail, and atieredcheck routes to the result set for the rolled outcome tier. A failedsimplecheck still counts as a match, so it consumes, produces the failure set, and discovers the recipe. Otherwise the attempt fizzles with no check and no roll, consuming the components only when the system’s Consume on Fail setting is on.interactiveprompts a roll on a matched brew insimpleortieredcheck mode, and defaults tofalsefor macros and automation, so it never triggers a roll on the fizzle path.getSelectedAlchemySystemId()reads the persisted last-selected alchemy system from thefabricate.lastAlchemySystemclient setting, returning''when unset.setSelectedAlchemySystemId(id)persists the selection to that same client setting.
The fabricate.lastAlchemySystem setting is scope: 'client', so the choice is remembered per client/device, not per user account.
Salvage Runtime Facade
salvageComponent({ actorId, systemId, componentId, interactive }) salvages one owned component. It backs the player Inventory tab’s Salvage panel and is the supported entry point for macros and integrations.
Hooks.once('fabricate.ready', async () => {
const actorId = game.user.character?.id;
const systemId = game.fabricate.listCraftingSystems()[0]?.id;
const outcome = await game.fabricate.salvageComponent({
actorId,
systemId,
componentId: 'my-component-id'
});
if (outcome.cancelled) return; // The player dismissed the roll prompt.
if (!outcome.success) {
console.log(`Salvage failed: ${outcome.message}`);
} else if (outcome.results === null) {
console.log(`Salvage started: ${outcome.message}`); // Time-gated run.
} else {
console.log(`Recovered: ${outcome.results.map((item) => item.name).join(', ')}`);
}
});
It takes an actorId, never an actorUuid. The facade resolves that id through the same ownership gate as craftRecipe, and that gate is the only ownership check on the salvage path. CraftingEngine.salvage performs none of its own: it resolves the UUID it is handed and mutates that actor’s items directly. Passing a uuid straight to the engine therefore bypasses the gate, and a stale or foreign uuid throws at the server rather than returning a message. actorId defaults to the persisted last-crafting selection, and an unresolved actor returns { success: false, results: null, message: 'No crafting actor selected' }.
interactive prompts the player to roll (with Advantage / Normal / Disadvantage where the formula allows it, a situational-bonus field, and a roll-mode picker) and posts the roll to chat so Dice So Nice animates it. It defaults to false, so macros and automation stay silent. The player Inventory tab passes true.
The returned object has four shapes, and cancelled is distinct from a failure:
| Outcome | Shape | Meaning |
|---|---|---|
| Cancelled | { success: false, cancelled: true, results: null } | The player dismissed the roll prompt. Nothing was consumed, no tool broke, and any run created by the call was discarded. Do not report this as an error. |
| Started | { success: true, results: null, message } | The component has a time requirement, so a run started and awarded nothing yet. It settles as world time advances. |
| Awarded | { success: true, results: [...], salvageRun } | The salvage resolved and created the result items. |
| Failed | { success: false, message } | An ordinary failure: actor, system or component not found, salvage disabled, validation failed, or the mode requires a roll formula the GM has not authored. |
The engine returns these rather than throwing, so ordinary failures need no try/catch.
Do not thread a result order into this call. For a progressive salvage, the engine captures the player’s standing order (salvage:<componentId> in fabricate.progressiveResultOrder) onto the run record when the run starts, and reads it back at award time. A caller that wants a different order writes that setting first and lets the capture happen.
Bulk Salvage & Destroy Runtime Facade
salvageComponents({ actorId, targets, interactive, onProgress }) salvages many owned components in one gesture, and destroyComponents({ actorId, targets, onProgress }) permanently destroys many owned components (whole stacks) in one gesture. Both back the player Inventory tab’s bulk panel and are the supported entry points for macros and integrations that need to act on several components at once.
Hooks.once('fabricate.ready', async () => {
const actorId = game.user.character?.id;
const systemId = game.fabricate.listCraftingSystems()[0]?.id;
const targets = [
{ systemId, componentId: 'iron-ore' },
{ systemId, componentId: 'oak-branch' }
];
const outcome = await game.fabricate.salvageComponents({ actorId, targets });
if (outcome.cancelled) {
console.log('The batch roll prompt was dismissed; nothing ran.');
} else {
for (const item of outcome.items) {
console.log(`${item.name}: ${item.outcome}`);
}
}
});
Neither facade takes an actor UUID, at any nesting level. Each entry in targets may carry its own actorId, falling back to the call’s own actorId when omitted, and either way that id is resolved through the same ownership gate salvageComponent uses. A target whose actor cannot be resolved is never retargeted onto a different actor and never thrown as an exception. It becomes a row in the returned items[] carrying outcome: 'notPermitted', and every other target in the same call still runs.
targets is Array<{ actorId?: string, systemId: string, componentId: string }>, in the order the caller wants them run. A single call accepts at most 25 targets. A target beyond that limit is refused before anything runs, reported as outcome: 'skipped' with skipReason: 'bulkLimit'.
interactive defaults to true for salvageComponents, unlike salvageComponent’s default of false, because this facade exists for a player gesture rather than automation. It opens at most ONE roll prompt for the whole batch, setting a single situational bonus, roll mode, and advantage choice that is applied to every roll in the run. Every target still rolls its own check against its own crafting system. If the player dismisses that prompt, the call returns { cancelled: true, items: [], counts: {...}, posted: false } immediately. Nothing was queued, consumed, or rolled. destroyComponents opens no prompt at all. The caller owns the confirmation, and this facade executes against the snapshot the confirmation named.
onProgress, when supplied, is called (completed, total) after each target resolves. total counts only the targets the gate accepted, so a run containing a notPermitted row finishes below the caller’s own targets.length. A listener that throws is caught and logged, and it never breaks the run.
salvageComponents Outcomes
salvageComponents returns { cancelled, items, counts, posted }. Each row of items carries { actorId, actorName, systemId, componentId, name, img, outcome, skipReason, rollValue, tierStep, message, results, consumed, tools }, in the caller’s own target order.
| Outcome | Meaning |
|---|---|
succeeded | The check succeeded, or none was required. Usually this creates result items, but a routed/progressive success tier authored with no results succeeds and awards nothing, so read results.length rather than the outcome alone to know what arrived. |
failed | The check failed outright: a rolled failure tier, or a total that matches no authored range. Not enough units and a required tool unavailable are the pre-check failure reasons. A success tier that awarded nothing is succeeded, never failed. |
waiting | The component has a time requirement, so a run started and awarded nothing yet. |
misconfigured | The mode requires a roll formula the GM has not authored. |
skipped | Refused before the engine was called; see skipReason below. |
notPermitted | The target named no actor this call could resolve. |
error | The engine call threw; message carries the error text. |
A skipped row also carries a skipReason: unknownSystem, featureDisabled (the system’s Salvage feature is off), unknownComponent, salvageDisabled (the component itself is not salvageable), duplicate (the same actor, system, and component appeared twice in targets), or bulkLimit.
counts tallies every outcome above, plus total. posted is true only when at least one attempted (non-skipped) component’s crafting system has its Chat output feature on, regardless of whether that component’s own row succeeded. When none does, no aggregate chat card is posted at all, never an empty one. A subject that qualifies is folded into the ONE aggregated result card together with every other qualifying subject from the same run, never one card per component.
destroyComponents Outcomes
destroyComponents returns { items, unitsDeleted, documentsDeleted }. Each row of items carries { actorId, actorName, systemId, componentId, name, img, outcome, skipReason, requested, unitsDeleted, documentsDeleted, staleIds, items, vetoed }. outcome is succeeded (at least one unit was deleted), failed (units were found but none could be deleted), skipped (skipReason of unknownSystem, unknownComponent, or depleted), or notPermitted (the target named no resolvable actor).
Destroy removes the component’s WHOLE stack on the target actor, with no quantity control. It is deliberately not gated on the system’s Salvage feature or the component’s own salvage setup, since deleting an owned item is something a player could already do from the Foundry sheet. It posts no chat card, because a result card reports what an activity produced, and destroying produces nothing.
Actor Selection
These methods back the unified Fabricate window’s actor-selection bar and persist the remembered gathering actor:
Hooks.once('fabricate.ready', () => {
// Player-safe display data for the actor-selection bar.
const actors = game.fabricate.listSelectableActors();
// -> [{ id, uuid, name, img }, …]
const current = game.fabricate.getSelectedGatheringActorId(); // '' when unset
game.fabricate.setSelectedGatheringActorId(actors[0]?.id ?? '');
});
listSelectableActors()returns the current user’s selectable player characters (the GM-configured actor types that count as player characters, always including'character'), owned actors for players, all for GMs. Each record is redaction-safe display data containing only{ id, uuid, name, img }. No other actor internals are exposed. This selection list is narrower than gathering attempt authorization. An owned non-player-character actor stays attempt-authorized throughlistGatheringForActor/startGatheringAttemptbut does not appear in the bar.getSelectedGatheringActorId()reads the persisted remembered selection from thefabricate.lastGatheringActorclient setting, returning''when unset.setSelectedGatheringActorId(id)persists the remembered selection to that same client setting.
Hide Unavailable Environments Toggle
These methods back the player-side Hide unavailable toggle in the Gathering app’s Environments column. The toggle is a view-only presentation preference. It hides only listings the engine reports as locked === true (disabled environments, plus location-gated environments the party is not in). It never changes saved data, the engine listing, or GM configuration.
Hooks.once('fabricate.ready', async () => {
const hidden = game.fabricate.getHideUnavailableEnvironments(); // false by default
await game.fabricate.setHideUnavailableEnvironments(true);
});
getHideUnavailableEnvironments()reads thefabricate.gatheringHideUnavailableEnvironmentsclient setting, returningfalsewhen unset.setHideUnavailableEnvironments(value)persists the boolean preference to that same setting.
The setting is scope: 'client', so it persists in the browser’s localStorage. The choice is remembered per client/device, not per user account. The same account opening Fabricate on a second device or browser starts from the default (off).
Gathering Economy Block
getGatheringEconomy({ systemId }) returns the normalized per-system limitation block, and setGatheringEconomy({ systemId, economy }) (GM-only) persists it. The block carries two independent boolean flags. There is no single mode field:
Hooks.once('fabricate.ready', async () => {
const systemId = game.fabricate.listCraftingSystems()[0]?.id;
// Normalized shape — stamina and resource-node limitations toggle independently.
const economy = game.fabricate.getGatheringEconomy({ systemId });
// -> {
// stamina: { enabled: false, max: '', start: '', regen: { policy, unit, amount, lastRoll } },
// nodes: { enabled: false }
// }
// Anti-dogpiling: turn BOTH limits on. One accepted attempt then both depletes
// the node pool and spends stamina; neither flag on means no limit.
await game.fabricate.setGatheringEconomy({
systemId,
economy: { stamina: { enabled: true }, nodes: { enabled: true } }
});
});
The flags map onto the rich-state service accessors staminaEnabled(systemId) and nodesEnabled(systemId) (the single read used by enforcement, world-time regen/respawn drivers, and every UI surface). The derived economyMode(systemId) accessor is retained for back-compat and now returns 'both' | 'stamina' | 'nodes' | 'none' (the 'both' value is new in 0.8.0). Worlds upgraded from before 0.8.0 have their legacy economy.mode enum migrated into these flags automatically (see Gathering Limitations).
Realms, Parties, And Location
Location-aware gathering adds stores for per-system realms and world-level parties, a current-realm resolver, and GM discovery controls. A Gathering Realm is the Fabricate geography concept (renamed from Gathering Region to avoid the collision with Foundry’s own Scene RegionDocument). A realm maps many-to-one onto Foundry Scene Regions through its scene mappings. The whole subsystem is gated per crafting system by the gatheringRealmSettings.enabled flag (default off, the Enable Travel & Realms toggle in gathering Settings). While it is disabled, getGatheringLocationForActor, the override setters, and the discovery reveal/hide methods are inert (return null / false / no-op). Each method also has a shorter alias on the game.fabricate.gathering facade (getPartyStore, getRealmStore, getLocationService, getLocationForActor, setPartyRealmOverride, clearPartyRealmOverride, revealRealmForActor, hideRealmForActor). The pre-rename *Region* method and alias names are retained as deprecated delegates that warn once and forward, so existing macros keep working:
Hooks.once('fabricate.ready', async () => {
const systemId = game.fabricate.listCraftingSystems()[0]?.id;
const partyStore = game.fabricate.getGatheringPartyStore(); // party CRUD, members, travel actor
const realmStore = game.fabricate.getGatheringRealmStore(); // per-system realm CRUD + settings
const locations = game.fabricate.getGatheringLocationService(); // current-realm resolution
// GM-only writes.
await game.fabricate.setGatheringPartyRealmOverride({ partyId, systemId, realmIds: [realmId] });
await game.fabricate.clearGatheringPartyRealmOverride({ partyId, systemId });
await game.fabricate.revealGatheringRealmForActor({ actor, systemId, realmId });
await game.fabricate.hideGatheringRealmForActor({ actor, systemId, realmId });
// Player-callable, redaction-safe read: secret undiscovered realm ids/names
// are never disclosed to non-GM callers.
const summary = game.fabricate.getGatheringLocationForActor({ actor, systemId });
});
See Gathering Realms & Travel for the full feature guide, including the GM Travel route, environment location rules, and the disclosure policy.
Subscribing To Gathering Hooks
Fabricate publishes Foundry hooks when a gathering attempt resolves, so other modules can react to gathering outcomes (successes, failures, and triggered encounters) without depending on Fabricate internals. Publication is always on — no setting gates it, and it is harmless when nobody subscribes. The hook names are also exposed on game.fabricate.api.HOOKS.gathering so you do not have to hard-code the strings.
| Hook | Fires |
|---|---|
fabricate.gathering.attemptCompleted | Once for every terminal attempt (success or failure, immediate or matured timed run), after all side effects are committed. |
fabricate.gathering.eventTriggered | Once per encounter/event the attempt triggered. |
The attemptCompleted payload is a cloned, JSON-serializable object:
| Field | Description |
|---|---|
schemaVersion | Payload contract version (currently 1). |
status | 'succeeded' or 'failed'. |
initiatedBy | 'immediate' for direct attempts, 'timed' for matured timed runs. |
worldTime | Foundry world time at completion. |
userId, viewerId | Initiating user and viewing user ids. |
actorId, actorUuid, actorName | The gathering actor. |
craftingSystemId, craftingSystemName | The owning crafting system. |
environmentId, environmentName | The gathering environment. |
taskId, taskName | The resolved task. |
runId, runStatus | The persisted gathering run. |
riskLevel, conditions | Risk and the weather/time-of-day snapshot (null for non-rich tasks). |
gatheredItems | [{ actorUuid, itemUuid, componentId, quantity }] created on success. componentId is null when the resolution does not expose it. |
usedTools | [{ componentId, actorUuid, itemUuid, quantity, broken }] consumed by the attempt (breakage internals are not exposed). |
events | The triggered encounters (also emitted individually as eventTriggered). |
checkResult | The normalized resolution detail. |
Where it fires. The hook fires on the client that resolved the attempt. Immediate attempts fire on the acting user’s client; matured timed runs fire once, on the primary GM client (so the updateWorldTime broadcast does not duplicate them across clients).
For a blind task viewed by a non-GM, the payload is redacted to match what that client may see: taskId/taskName, gatheredItems, usedTools, events, and checkResult are omitted, and no eventTriggered hook fires. A subscriber that throws is caught and logged — it never breaks the gathering flow.
Hooks.once('fabricate.ready', () => {
const { ATTEMPT_COMPLETED, EVENT_TRIGGERED } = game.fabricate.api.HOOKS.gathering;
Hooks.on(ATTEMPT_COMPLETED, (result) => {
if (result.status !== 'succeeded') return;
console.log(`${result.actorName} gathered`, result.gatheredItems, `in ${result.environmentName}`);
});
Hooks.on(EVENT_TRIGGERED, ({ actorUuid, event }) => {
console.log(`Encounter for ${actorUuid}:`, event.name);
});
});
Data Persistence
Fabricate stores data in Foundry’s settings and flags:
| Location | Key | Contents |
|---|---|---|
| World setting | fabricate.craftingSystems | All crafting system configurations, including each system’s gathering realms and realm settings |
| World setting | fabricate.gatheringParties | Fabricate-managed gathering parties (members, travel actor, per-system current-realm overrides) |
| World setting | fabricate.recipes | All recipes |
| World setting | fabricate.gatheringEnvironments | Gathering environment and task configurations |
| World setting | fabricate.gatheringConfig | Gathering library, rules, condition vocabularies, and per-system gathering configuration |
| World setting | fabricate.migrationVersion | Last completed Fabricate data migration version |
| World setting | fabricate.theme | Active product UI theme (Fabricate by default, with other presets Mythwright, Ironblood Forge, Hearth & Herb, Starglass Arcana, and the fixed Foundry-inspired Foundry Native palette) |
| World setting | fabricate.experimentalFeatures | Reveals in-development Fabricate surfaces, currently the unimplemented recipe Graph placeholder in the crafting manager, disabled by default |
| Client setting | fabricate.lastCraftingActor | Last selected crafting actor UUID |
| Client setting | fabricate.lastGatheringActor | Last selected gathering actor ID |
| Client setting | fabricate.lastComponentSources | Last selected source actor UUIDs |
| Client setting | fabricate.lastManagedCraftingSystem | Last viewed system in GM admin |
| Client setting | fabricate.lastAlchemySystem | Last selected alchemy system (discipline) for the Alchemy Workbench tab |
| Client setting | fabricate.favouriteRecipes | Favourite recipe IDs for the current client |
| User setting | fabricate.progressiveResultOrder | Player progressive result-stage order, keyed recipe:<recipeId> / salvage:<componentId> to a list of result ids (Object, default {}). Registered with scope: 'user', which is per user within one world. The same player in a second world reads the default. Writing it is an asynchronous, replicated document write that can reject, not a synchronous localStorage write, so a caller must await it. |
| Client setting | fabricate.gatheringHideUnavailableEnvironments | Player “hide unavailable (locked) environments” toggle for the Gathering app Environments column (Boolean, default false, per client/device) |
| Actor flag | fabricate.craftingRuns.active | In-progress crafting runs |
| Actor flag | fabricate.craftingRuns.history | Completed crafting runs |
| Actor flag | fabricate.gatheringRuns.active | In-progress gathering runs |
| Actor flag | fabricate.gatheringRuns.history | Completed gathering runs |
| Actor flag | fabricate.learnedRecipes | Learned recipe records |
| Actor flag | fabricate.discoveredGatheringRealms | Per-system gathering realm discovery entries for the actor (legacy fabricate.discoveredGatheringRegions flag read as a fallback) |
| Item flag | fabricate.toolUsage | { timesUsed } for limitedUses tool tracking (falls back to legacy fabricate.catalystItemUsage when absent) |
| Item flag | fabricate.toolBroken | true when a tool’s flagBroken on-break action has fired |
| Item flag | fabricate.recipeItemUsage | { timesUsed, inert? } for recipe item tracking. inert records that the copy exhausted while its book was set to become inert rather than be destroyed, and nothing in Fabricate clears it once set |
Hooks
| Hook | When | Payload |
|---|---|---|
fabricate.ready | After module initialisation and guarded startup world-time processing complete | None |
fabricate.gathering.attemptCompleted | Once per terminal gathering attempt (success/failure, immediate/timed), after side effects commit | See Subscribing To Gathering Hooks |
fabricate.gathering.eventTriggered | Once per encounter an attempt triggers | See Subscribing To Gathering Hooks |
Startup world-time processing awaits crafting, salvage, and gathering settlement before fabricate.ready fires. Later Foundry updateWorldTime events dispatch the same processors without blocking the hook. Individual processor failures are caught and logged so one subsystem does not prevent the others from running.