Programmatic API
The module exposes an API on window.Tokenizer2 and game.modules.get("tokenizer-2").api once the tokenizer-2.ready hook fires.
Functions
| Method | Purpose |
|---|---|
openEditor(actor, token?) | Open the editor for an existing actor (the same entry the portrait click uses) |
openEditorStandalone(opts) | Open the editor with no actor - for flows that compose art before an actor exists |
tokenize(actor, opts?) | Headless single-actor tokenize (no UI) |
tokenizeBatch(actors, opts?) | Headless batch tokenize |
exportLayers(layers, opts?) | Render a layer stack to a blob/dataURL |
promptConfig(opts?) | Show a config dialog; resolves to a config object suitable for tokenize() (or null if cancelled) |
createImageLayer(src, name?) / createRingLayer(config?) | Build layer objects for a layerStack passed to exportLayers() or openEditorStandalone() |
registerCustomFrame(entry) | Add a frame to the Frame browser's Custom tab (see below) |
applyPrototypeToSceneTokens(actor, scene?) | Copy the actor's prototype token image (texture and dynamic ring) onto its tokens on a scene, the viewed one by default; resolves to the number of tokens updated |
lib.ActorFolders.collectFolderActors(folder, opts?) | Collect the world actors in an Actor folder (by document, id, or name) for tokenizeBatch() |
cleanupActorFlags(opts?) | Re-run the one-time cleanup that strips embedded image data from actor flags (GM), e.g. after unlocking a compendium |
ornaments | Actor-driven ornaments: describe a game system's actors, ask which ornament an actor gets, and get an ornament's overlay image (see Ornaments) |
conditions | Condition overlays: which overlay a game system's status shows, an actor's current ones, and an overlay's image (see Conditions) |
numbers | Number badges on copies of the same creature on a scene (see Number badges) |
settingsBackup | Export and import Tokenizer 2's settings, and list the backups in module storage (see Settings backup) |
apiVersion | The API version, "major.minor"; see the Plugin API |
pluginRegistry / frameRegistry | The plugin and frame registries |
tokenize() and exportLayers() return the rendered image as blob and as dataURL, a blob: object URL.
When tokenize() saves, blob is the image it saved: a WebM for an animated token.
The object URL is only created the first time you read dataURL, so batch runs that never read it do not keep every image in memory.
If you do read it, call URL.revokeObjectURL(result.dataURL) once you no longer display it.
tokenizeBatch() takes an onProgress(current, total, actor) callback.
Callers processing a large set in chunks can pass progressOffset and progressTotal so progress is reported against the whole set.
Actors with a player owner, or of type character or pc, save to the PC folders; all others save to the NPC folders.
The editor and the API use the same rule.
tokenize()'s filename may be given with or without its extension ("Goblin" or "Goblin.webp"); the export format's extension is used either way.
A wildcard actor's image is always saved into its wildcard folder as the next numbered variant, so saveFolder and filename do not apply to it.
Its portrait is the actor's image (img), as in the editor, since the wildcard path is not an image.
What tokenize() adds
tokenize() builds the token the editor builds when it first opens an actor with no saved design.
The world's settings decide what goes in: the default colour layer and background, the portrait fitted inside the ring, and the ring itself.
A token whose Dynamic Ring is already on keeps it.
Its own art stays as it is when cut out on transparency (made to pop out of the ring); square art, or another portrait (portraitSrc, useActorImg), is masked to the ring.
Otherwise, with Auto-apply default ring on, it gets the Dynamic Ring (Use Foundry Dynamic Ring by default) or its disposition frame baked in.
A Dynamic Ring's own background replaces the default background and colour layer; under a ring with no background they stay, inside the ring.
The actor's ornament goes on top when the world's ornament rules give it one.
An option you pass decides over the settings:
frameSrcandframeRulesbake their frame in even with Auto-apply default ring off.backgroundSrcis added even with Auto-apply default background off.forceDynamicRinguses the Dynamic Ring whatever the settings say;forceBakedRingbakes the frame and leaves the token's ring off.skipRingadds no ring, andskipBackgroundadds no background and no colour layer.
Two editor rules do not apply: tokenize() adds all of this to an image already in the save folder too, and never makes the token oversized by default.
How tokenize() saves
tokenize() saves the token as the editor's Save does:
- An animated frame makes an animated token, a WebM encoded with the Animation settings;
exportSizeandexportFormatonly apply to still images. A wildcard token is always saved as a still image. - A Dynamic Ring token gets its ring colours.
With Save Regular Texture with Dynamic Token and Save Dynamic Ring into Regular Texture on, it also gets a second image with the ring drawn in (named with
-ring) as its regular texture. - A token whose Dynamic Ring is already on at a larger size keeps that size.
When nothing in it reaches past its grid square, it is saved as a 1x token, as the editor does, and
layersare scaled to match.
compositeDynamicRing, which draws the Dynamic Ring into the image for previews, only applies with save: false.
Tokenizing a world folder
lib.ActorFolders collects the actors in an Actor folder, the same way the folder macro does:
const api = game.modules.get("tokenizer-2").api;
const actors = api.lib.ActorFolders.collectFolderActors("Heroes", {
recursive: true,
skipTokenized: true,
});
await api.tokenizeBatch(actors, {
onProgress: (current, total, actor) => console.log(`${current}/${total} ${actor.name}`),
});
openEditorStandalone(options)
Opens the full editor without an Actor document.
Saves still render and upload the image(s) to disk, but nothing is written to a document - instead the generated paths and the prototype-token patch are returned so the caller can apply them to an actor it creates afterward.
Designed for character-creation wizards (e.g. Hero Mancer) that let the user pick portrait/token art before the actor is created.
const result = await game.modules.get("tokenizer-2").api.openEditorStandalone({
name: "Aria the Wizard", // drives the save filename
type: "character", // "character"/"pc" route to the PC save folder
sourceImage: "path/to/portrait.png",
// optional:
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY, // default-ring selection
hasPlayerOwner: true, // overrides PC detection (defaults true for character/pc)
targetFolder: "[data] my/folder",// overrides the configured pc/npc save location
userId: game.user.id, // advisory - used only for a FILES_UPLOAD permission check
onComplete: (res) => console.log(res),
// optional re-edit: re-seed a prior design instead of starting fresh (see below)
layerStack: previousResult.layerStack,
});
Re-editing a prior design. To let a user reopen and tweak a token they already composed (e.g. a Hero Mancer submission), store the previous call's result.layerStack and pass it back in.
The editor reopens showing that stack instead of seeding fresh from sourceImage.
Optional companions for full fidelity:
| Option | Purpose |
|---|---|
layerStack | Token layer stack to re-seed (a prior result.layerStack) |
avatarLayerStack | Avatar layer stack to re-seed |
isDynamicRingMode | Force dynamic-ring mode (else inferred from the layer stack) |
isOversized | Force oversized state at 2x (else inferred from layer transforms) |
oversizedScale | Force the oversized scale, 1.3 or 2 (1 is not oversized); wins over isOversized. Added in API 1.2 |
oversizedRingFill | At 1.3, a Dynamic Ring grows with the token so the ring fills the square. Added in API 1.2 |
avatarAspect / avatarCanvasSize | Re-seed avatar aspect ratio / canvas size |
// First pass:
const r1 = await game.modules.get("tokenizer-2").api.openEditorStandalone({ name: "Aria", sourceImage: "portrait.png" });
// Later, re-open on the same design:
const r2 = await game.modules.get("tokenizer-2").api.openEditorStandalone({ name: "Aria", layerStack: r1.layerStack });
Returns a Promise that resolves when the editor closes (the onComplete callback fires with the same value):
- On save, an object:
{ tokenPath?, avatarPath?, prototypeToken?, avatarUpdate?, layerStack?, avatarLayerStack? }tokenPath/avatarPath- server paths of the uploaded image(s)prototypeToken- a dotted patch (prototypeToken.texture.src,.texture.scaleX/Y, and in dynamic-ring modeprototypeToken.ring.*) to apply to the new actoravatarUpdate-{ [avatarKey]: path }patch for the actor's portrait fieldlayerStack- the token's serialised layer stack (when the token was saved), to store as a flag for future non-destructive re-editsavatarLayerStack- the avatar's serialised layer stack (when the avatar was saved, API 1.5)
- On close without saving,
null. - When another editor is already open, it is closed first and asks about its unsaved work; if the user keeps it open, nothing new opens and the result is
nullat once.
Recipe for applying the result to a freshly-created actor:
if (result) {
await actor.update({ ...result.prototypeToken, ...result.avatarUpdate });
if (result.layerStack) await actor.setFlag("tokenizer-2", "layerStack", result.layerStack);
if (result.avatarLayerStack) await actor.setFlag("tokenizer-2", "avatarLayerStack", result.avatarLayerStack);
// Opening Tokenizer 2 on this actor later rehydrates the saved composition.
}
Notes
- Only one editor can be open at a time; calling this closes any open editor first.
- Files are written to the configured PC/NPC save locations (by
type) unlesstargetFolderis supplied; the in-editor Save dialog still lets the user tweak folder/filename. - The default ring is applied only if the Apply Default Ring setting is enabled; a standalone actor has no real disposition, so it defaults to friendly (or the supplied
disposition).
promptConfig(options)
Shows the same options form as the compendium Auto Token window (frame, frame rules, new seed for every token, mask, portrait fit, image source, wildcard handling, save folder) but returns the chosen settings instead of running a batch. Designed for external modules (e.g. DDB Importer) that prompt the user once and apply the same config to many imported actors.
const api = game.modules.get("tokenizer-2").api;
const config = await api.promptConfig({
defaults: { saveFolder: "[data] tokenizer/imports" }, // optional
// defaults may also set frameRules (true, false or a list of rules) and newFrameSeed;
// frame rules start on or off as the world's were last used
// title: "...", description: "...", previewActor: someActor,
});
if (!config) return; // user cancelled
for (const actor of importedActors) {
await api.tokenize(actor, { ...config, filename: actor.name });
}
Returns Promise<object|null> - the object has the same shape as tokenize() options (forceDynamicRing, forceBakedRing, frameSrc, maskSrc, portraitFit, useActorImg, wildcardMode, saveFolder, frameRules, newFrameSeed) and can be spread directly into tokenize().
frameRules is the list of rules chosen (or null when they are off), so the config works on its own later.
The preview uses a synthetic standalone actor when no previewActor is supplied.
Pre-creation use: tokenize(actorData, { updateActor: false })
tokenize() can also be used before an actor document exists - for example, when DDB Importer assembles a monster's actor-data and wants to embed the rendered token into that data before calling Actor.create().
Pass updateActor: false to skip every actor mutation while still uploading the file:
const api = game.modules.get("tokenizer-2").api;
const cfg = await api.promptConfig({ defaults: { saveFolder: "[data] imports/tokens" } });
const { path, prototypeToken, layers } = await api.tokenize(actorData, {
...cfg,
filename: actorData.name,
updateActor: false,
});
// Merge the patch into the actor-data being assembled. The keys are dotted, so
// expand before merging.
foundry.utils.mergeObject(actorData, foundry.utils.expandObject(prototypeToken));
actorData.flags ??= {};
actorData.flags["tokenizer-2"] = { layerStack: layers };
await Actor.create(actorData);
With updateActor: false:
- The blob (and its
-ringcopy, when the settings ask for one) is still uploaded to the configured (or supplied) save folder. actor.update,actor.setFlag, and the placed-token patch are all skipped.result.prototypeTokencarries the exact dotted-key patch that the writes would have used, including the cache-bustedprototypeToken.texture.src.result.layersis the layer stack as the editor keeps it, ready forsetFlagor merging intoflags["tokenizer-2"].layerStack. It leaves out the Dynamic Ring preview and the masks made from it, which the editor makes again when the token is opened.
Frame rules and new seeds: tokenize(actor, { frameRules, newFrameSeed })
frameRules picks each actor's frame by rules, as Auto Token's frame rules do (added in API 1.2).
Pass true for the world's frame rules for the current game system, or a list of rules of your own.
A rule is an ornament rule with a frame path in place of the ornament.
The first rule the actor matches gives the frame; an actor matching none gets frameSrc, or its usual frame when there is no frameSrc.
await Tokenizer2.tokenize(actor, {
frameSrc: "tokenizer/frames/plain.webp", // for actors no rule matches
frameRules: [
{ fact: "creatureType", op: "is", value: "undead", frame: "tokenizer/frames/bone.webp" },
{ fact: "cr", op: ">=", value: 10, frame: "tokenizer/frames/gold.webp" },
{ fact: "field", path: "system.traits.size", op: "is", value: "tiny", frame: "tokenizer/frames/small.webp" },
],
});
newFrameSeed: true draws a frame made in the Ring Generator again with a random seed, so each token gets a ring of its own.
The new ring is saved to variants/ in the Generated Frames folder (with save: false, it is only kept in memory).
It adds a few seconds per token, and does nothing for frames without a Ring Generator design or with the Dynamic Ring.
Extending the Frame & Mask Browser
External modules can add their own tabs (sections) and categories (subsections) to either browser via two Hooks fired once during the Foundry ready phase:
Hooks.on("tokenizer-2.registerFrames", (registry) => {
// Static section - built up-front, appears immediately on next browser open
registry.registerSection({
id: "my-pack",
label: "My Pack", // literal or i18n key (auto-localized when label contains a ".")
subsections: [
{
label: "Heroes", // subsection divider, optional
frames: [
{ src: "modules/my-pack/heroes/aragorn.webp", label: "Aragorn" },
{ src: "modules/my-pack/heroes/legolas.webp", label: "Legolas" },
],
},
{ label: "Villains", frames: [/* ... */] },
],
});
// Async loader - runs once on first browser open, result cached
registry.registerLoader({
id: "my-scanned-pack",
label: "MY_PACK.TAB_LABEL", // i18n key
load: async () => {
const result = await foundry.applications.apps.FilePicker.implementation
.browse("data", "modules/my-pack/scanned-frames");
return {
subsections: [{
label: null,
frames: result.files.map((src) => ({
src,
label: src.split("/").pop().replace(/\.\w+$/, ""),
})),
}],
};
},
});
});
Hooks.on("tokenizer-2.registerMasks", (registry) => {
// Same API, but the registered sections appear in the Mask Browser instead
registry.registerSection({ id: "my-mask-pack", label: "My Masks", subsections: [/* ... */] });
});
Registered sections appear after the built-in tabs (Tokenizer, OMFG, TheGreatNacho, Token Frames) and before the Custom tab. Favourites and recents tracking work automatically for any registered frame.
Other registry methods:
registry.unregister(kind, id)- remove a previously-registered section.kindis"frame"or"mask".registry.clearCache(id)- drop a cached loader result so the next browser open re-invokesload. Useful when your module's settings change.
Constraints:
- Registration must happen inside the
tokenizer-2.registerFrames/tokenizer-2.registerMaskslisteners (same constraint astokenizer-2.registerPlugins). Modules registering later miss the hook. - Section
idmust be unique within its kind; duplicates are rejected and logged. - Loader failures (
throwornullreturn) are logged and the section is omitted that open; the next open retries. - Sections with zero subsections are hidden from the browser.
The registry singleton is exposed as game.modules.get("tokenizer-2").api.frameRegistry for inspection at runtime.
registerCustomFrame(entry)
Publishes a frame into the browser's Custom tab without the user having to add it by hand - intended for modules that import frames (e.g. DDB Importer).
await Tokenizer2.registerCustomFrame({
src: "worlds/my-world/frames/gold-ring.webp",
label: "Gold Ring", // optional, defaults to a prettified filename
group: "My Pack", // optional; omit for the flat custom list
shared: true, // optional; GM only - publish to the whole world
});
Returns true when written and false when the src is already present.
Without shared, the frame goes into the calling user's own custom frames.
With shared: true it goes into the world's shared library instead, so every user sees it; a non-GM caller falls back to their own list and logs a warning, so callers do not need their own GM check.
Each call fires the tokenizer-2.customFrameRegistered hook with { src, label, group, shared }.
Ornaments
Added in API 1.1.
An ornament is a crest laid over a token's frame or ring: a crown (boss), skull (villain), laurels (elite), shield (ally), one to five pips (tier1 to tier5), targeting brackets (target) or an army rank (rank-<rank>, such as rank-captain; added in API 1.2).
API 1.2 also adds a dragon's head (dragon), demon horns (fiend), feathered wings (angelic), antlers (beast), crossed swords (warrior), crossed arrows (ranger), a staff (caster), an anchor (sailor), a compass rose (navigator) and a ship's wheel (helm).
API 1.4 adds a glowing eye (aberration), a tentacle pair (kraken), claws (monstrosity), a spider's web (spider), a beetle (vermin), a wax seal (noble), a knight's helm (knight), a plague doctor's mask (plague), theatre masks (bard), a rose (druid), a lotus (fey), an open book (wizard), an astrolabe (sage), a potion (alchemist), a key (rogue), gears (artificer) and a sensor pod (construct).
When a GM turns on Give tokens ornaments from their actors (Settings, Ornaments tab), rules pick one from each actor's facts, and Auto-Tokenize, batch tokenizing and the editor's first open apply it.
The same tab's Ornament designs say where each kind sits (and how), and can swap the built-in crest for the GM's own picture; quick picks such as "boss" and "tier3" follow them, ranks take the rank design's finish and tier pips its marker.
The ornament option of tokenize()
await Tokenizer2.tokenize(actor, { ornament: "auto" }); // the default: the world's rules, if they are on
await Tokenizer2.tokenize(actor, { ornament: "boss" }); // a crown, whatever the rules say
await Tokenizer2.tokenize(actor, { ornament: "tier" }); // pips for the actor's tier
await Tokenizer2.tokenize(actor, { ornament: { type: "crown", points: 3, scale: 1.5 } });
await Tokenizer2.tokenize(actor, { ornament: "none" });
An explicit ornament applies even when the world's rules are off.
The ornament is added as the top layer (isOrnament: true), set lower over the ring so it fits the token.
It is ignored when you pass your own layers.
Ornament data takes the Ring Generator's ornament settings: type (crown, skull, laurels, shield, pips, badge, rank, emblem, scroll, wings, horns, dragonHead, brackets or image), scale, and per type points, leaves, spread, device, count, spacing and marker.
Pips take a marker: diamond (the default), star, orb, chevron or tally.
An emblem takes a device: star, crescent, lion, eagle, wolf, dragon, compass, anchor, wheel, sword, axe, arrow, staff or spear; a weapon can be crossed with a second ("same" or another weapon) at crossAngle degrees (15-60).
Its materials (ornament for the device, ornamentGem for a grip, haft, stock or hub) follow the device unless set.
A shield takes cross, chevron or any emblem device as its device (with the same crossing settings), and a field: plain, quartered, perPale, perBend or perChevron, whose second colour is the ornamentField material.
A scroll is a banner along the ring: text (up to 24 letters or digits), font, tails (swallowtail, curled or none), length (reference units round the ring, up to 2800; 0 fits the text), scale (0.6-4), angle (90, the bottom, by default), inset and follow ("curve", the default, or "straight" for a flat banner); its materials are ornament (the ribbon), ornamentGem (the back of its folds) and ornamentText.
wings take a style (feathered, bat or dragon) and horns a style (bull, ram, demon or antlers); both are mirrored across the ring unless mirror is false, wings start at angle -30 turned up (rotation -30) and horns at -60. A dragonHead sits at the top. Their materials follow the style unless set.
A rank is army rank insignia: rank (lanceCorporal, corporal, sergeant, staffSergeant, warrantOfficer2, warrantOfficer1, secondLieutenant, lieutenant, captain, major, lieutenantColonel, colonel, brigadier, majorGeneral, lieutenantGeneral, general or fieldMarshal) and finish ("dress", "combat" or "metal"), bottom left and upright unless placed elsewhere; its materials (ornament for the insignia, ornamentGem for the tab) follow the finish unless set.
A badge is a numbered badge, bottom right unless placed elsewhere: number (0-999), format ("arabic", "padded", "roman" or "alpha"), pad (digits of a padded number, 1-3), prefix (up to three letters or digits) and separator ("none", "dash", "dot" or "space"), font (sans, serif, mono, stencil, segment or blackletter), shape (disc, hexagon, diamond, shield, dogtag, ribbon, medal or label, the number in an outline with no plate), seat ("edge", "inside" on the band, or "outside"), stretch (a dog tag or ribbon widens for a long label), plain (drawn in the flat colours of its materials, without lighting or the ring's style), glow and glowColour, and unlit (a seven-segment display shows its unlit bars).
Its materials are ornament (the rim), ornamentGem (the background), ornamentNumeral (the label; the rim's when unset) ornamentUnlit (unlit display bars), and a medal's ornamentRibbon and ornamentStripe.
A badge stays upright: its rotation turns only the label.
brackets are a lock-on reticle drawn in light only: colour ([r, g, b], 0-1), radiusOut, arc, ticks and dashed.
API 1.4 adds these types, each with a style (a seal's is its symbol) and scale, their materials following the style unless set (ornament and ornamentGem are its two main parts, such as a seal's wax and ribbons):
seal (symbol: letter, star, crown or shield; letter, one letter; ribbon), upright at the top left;
eye (human, reptile, slit or orb); insect (beetle or dragonfly); helmet (knight, gladiator, plague or theatre);
flower (rose, lotus, sunflower or thorn); charm (key, bell, lantern, hourglass or potion, hanging below the ring, upright, on a cord 0-240 long: by default 70, or none for the key and potion);
scholar (book, quill, scrollcase or astrolabe); mechanism (cogs, pistons, aerial or sensor).
Lit styles (the orb eye, the plague doctor's goggles, the lantern and potion, the aerial and sensor) take a light colour ([r, g, b], 0-1).
Three follow the ring's curve and take only angle, inset and the copy settings: tentacles (suckers; a mirrored pair unless mirror is false, flip curls one the other way), claws (fingers, 2-5) and web (side: "opening" across the opening or "rim" out from the rim; spokes, rings, spider).
Every ornament also takes its placement: angle (degrees, -90 is the top, 90 the bottom, -45 the top right), inset (reference units into the ring; negative raises it), rotation (degrees clockwise), upright (turned upright wherever it sits) and follow ("straight" or "curve" along the ring).
An image ornament draws a picture: src (its path), size (reference units across, 40-600), colours ("image" keeps its colours, "material" embosses it in gold) and relief.
An actor's ornament flag (actor.setFlag("tokenizer-2", "ornament", "elite")) takes any of the same strings and wins over the rules.
ornaments.forActor(actor, options?)
Returns the ornament an actor gets (a quick pick id such as "boss", ornament data, or null), without rendering anything.
Options are choice (default "auto", as for tokenize()) and token (a placed token, whose disposition wins).
ornaments.overlay(ornament, options?)
Returns a promise of the path of an ornament's overlay image, rendering it and saving it under ornaments/ in the Generated Frames folder the first time.
Options are fit (default true: set lower to fit a token that is not oversized; false keeps it above the ring, on a larger canvas marked -c2560 in the file name), style (default: the Ring Generator style setting) and designs (default: the world's ornament designs).
It resolves to null when the overlay is not saved yet and the user cannot upload files, or when an image ornament's picture cannot be read.
ornaments.registerSystem(id, description)
Describes a game system's actors so GMs can write rules for it, or replaces a built-in description.
dnd5e, pf2e, sf2e and shadowdark are built in; any other system gets the common facts pc, npc, disposition and actorType.
Every system also has the field fact, which reads the data path a rule gives as its path (such as { fact: "field", path: "system.details.type.value", op: "is", value: "undead" }): numbers compare as numbers, text ignores case, and has looks in a list or for part of a text.
A fact is a number, boolean, choice (with options) or list (an array of strings, such as traits, tested with has and hasNot); options may be a function, read when the rules are shown.
Hooks.once("tokenizer-2.ready", (api) => {
api.ornaments.registerSystem("my-system", {
label: "My System",
pcTypes: ["hero"],
facts: {
threat: { type: "number", get: (actor) => actor.system.threat },
boss: { type: "boolean", get: (actor) => actor.system.isBoss === true },
tier: { type: "number", get: (actor) => Math.ceil(actor.system.threat / 4) },
},
rules: [
{ fact: "pc", op: "is", value: true, ornament: "none" },
{ fact: "boss", op: "is", value: true, ornament: "boss" },
{ fact: "threat", op: ">=", value: 8, ornament: "tier" },
],
});
});
| Field | Description |
|---|---|
label | Shown in the settings |
pcTypes | Actor types that are player characters (player-owned actors always are) |
facts | { name: { type, get(actor, token), options?, label? } }; type is number, boolean or choice (with options). A fact that returns undefined or throws is skipped. A tier fact (1-5) is what the tier ornament reads |
rules | The default rules, used until a GM edits them |
A rule is { fact, op, value, ornament }.
op is is, isNot, >= or <= (the comparisons for number facts only).
ornament is a quick pick id, "tier" or "none" (which stops the check with no ornament).
Rules are checked from the top and the first match wins.
ornaments.system(id?) returns a system's description with the common facts (default: the current system), ornaments.systems() the ids with their own description, and ornaments.choices the values a rule's ornament can take.
Conditions
Added in API 1.1.
A condition overlay is a transparent image of one or more of 88 game conditions (blinded, frightened, grappled, prone, flying, cover, persistent fire damage and so on) drawn around the ring, which stacks over any frame.
The Ring Generator draws conditions into a ring (its Effects list), and with Show conditions on map tokens on (Settings, Ornaments tab) every token on the map shows rings for its current statuses; these calls are what a module building tokens, or adding a condition to an existing one, works with.
Overlays are drawn once in the Ring Generator style and kept under conditions/ in the Generated Frames folder.
They are drawn on the 2560 canvas (the file name ends -c2560), so they line up with every frame when shown 1.25 times the frame's size.
On map tokens the frame follows the art's texture scale, an oversized token's middle half, Foundry's dynamic ring and its subject scale, and is the square in the middle of a rectangular token.
| Member | Description |
|---|---|
names | Every condition overlay's name, e.g. "blinded", "dmg-fire" |
families | The names grouped by family: senses, stealth, mind, fear, restraint, consciousness, body, buffs, movement, defence, magic, passions and damage |
forStatus(statusId, systemId?) | The overlay a status shows in a system (default: the current one): a name, null for a status with nothing to show (prone, flying, attitudes), or undefined for a status the map does not know |
forActor(actor, options?) | The overlays an actor's current statuses show, each once: [{ overlay, status, value }]; value is the condition's value where it has one (frightened 2, exhaustion 3) |
map(systemId?) | A copy of a system's status map, { statusId: overlay or null } |
registerMap(systemId, map) | Add to or override a system's map; overlay names that do not exist are ignored |
persistentDamage | Persistent damage by damage type, in Pathfinder and D&D5e names, to its overlay |
overlay(names, options?) | A promise of the path of an overlay showing one or more conditions, rendered and saved the first time; style defaults to the Ring Generator style setting. Resolves to null for no known condition, or when the overlay is not saved yet and the user cannot upload files |
addLayer(layerManager, canvasEngine, src, options?) | Add an overlay as the top layer of a layer stack (isCondition: true); options are name, conditions and oversized (true for a 2x token, or the token's oversized scale, such as 1.3, since API 1.2) |
shownOnToken(actor, max?) | The overlays a map token shows for an actor: its conditions in status order, at most max (default 3), dead on its own |
prepareTokenImages(names?) | Make the 1024px images map tokens show, for every condition or these, in the ring style setting (GM); resolves to how many were made |
redrawTokens() | Redraw the conditions on every token on the canvas |
all() | Every ring the world has: the built-in ones, then the world's own |
byFamily() | The world's rings by family, its own rings in the families they were given (or custom) |
label(name) | A ring's name to show: the world's name for it, or its translated built-in name |
recipe(name) | A ring's building blocks as the world has them (edited, custom or built-in) |
library() | The world's edited and new rings: { id: { label, family, steps } } |
stepTypes | Every building block's settings, by type (kind, range and default of each) |
openEditor({ ring?, tab? }) | Open the Condition Rings editor (GM), on a ring, or on the "mappings" tab |
D&D5e, Pathfinder 2e, Starfinder 2e, Shadowdark and Pendragon have their own maps, which give every status in the system's CONFIG.statusEffects a ring (statuses that mean the same thing share one); any other system uses Foundry's core statuses.
For Pathfinder, forActor reads actor.conditions.active, persistent damage by its damage type; for Shadowdark it also reads the actor's condition effects ("Condition: Afraid").
A ring is a recipe of building blocks, [{ type, ...settings }], drawn first to last; stepTypes describes each type's settings.
Distances are measured from the ring's outer edge and angles are in degrees, with -90 at the top.
A GM's edited and new rings are kept in the world setting condition-library, and their own status mappings, per game system, in condition-maps; both are edited in the Condition Rings editor.
A condition effect in theme data can carry its own steps ({ type: "condition", name, steps }), so a theme made with an edited or custom ring works anywhere.
On the map, a token's rings follow Foundry's own redraws of its status icons, so they change whenever its statuses do. The images map tokens use are made at 1024px by the active GM's client the first time a token needs one, and listed in a world setting so players' clients find them without browsing for files.
const conditions = Tokenizer2.conditions.forActor(actor).map((c) => c.overlay);
const src = await Tokenizer2.conditions.overlay(conditions[0]);
Tokenizer2.conditions.registerMap("my-system", { dazed: "stunned", hasted: "quickened", flying: null });
Number badges
Added in API 1.1; designs, placement and the editor in 1.2.
With Number copies of the same creature on the map on (Settings, Number Badges tab), the active GM numbers unlinked tokens of the same actor on a scene once there are two or more, giving each copy without a number the lowest free one.
The number, its badge image and its design are stored on the token as flags.tokenizer-2.badge ({ number, src, theme }), and every client draws the badge on the token's frame, above its bars, where the world's placement says.
A badge image is just the badge (a disc is about 110px), made once per number, prefix, design and style.
A pasted or duplicated token starts without a number.
See Number Badges.
| Member | Description |
|---|---|
numberCopies(tokenDocument, options?) | Number the copies of a token's actor on its scene; resolves to the numbers given, [{ id, number }]. Only the active GM numbers, and only with the setting on, unless { force: true } |
numberScene(scene, options?) | Number every actor's copies on a scene |
clear(scene) | Take the numbers off every token on a scene; resolves to how many were cleared |
refresh(scene, options?) | Redraw a scene's badges in the look their tokens get now, keeping their numbers; resolves to how many changed. Only the active GM, unless { force: true } |
themes() | The badge design ids: built-in, then modules', then the world's |
themeLabel(id) | A design's label |
themeFor(actor, options?) | The design an actor's badge gets: the default, or with rules turned on, the first matching rule (the default when none matches, or when its design is gone). Options are token, setting and systemId |
registerTheme(id, design) | Add a badge design, or replace one |
designs() | Every design: [{ id, label, source, design }], where source is "built-in", "module" or "world" |
placement() | Where badges sit on their tokens: { position, seat, size } |
openEditor(options?) | Open the Badge Designs editor (GM), on a design if given: { design } |
Badge designs
The API calls a design a theme, as designs began as themes. A design is a badge's look: its shape, lettering, number style, prefix and three materials.
Tokenizer2.numbers.registerTheme("my-module-neon", {
label: "Neon",
rim: "SILVER", // a material preset name...
field: { type: "enamel", color: [0, 0.9, 0.5], rough: 0.1 }, // ...or material data: the background
numeral: { type: "flat", color: [0.2, 1, 0.6] }, // the label (optional: the rim's material)
font: "segment", // "sans", "serif", "mono", "stencil", "segment" or "blackletter"
shape: "hexagon", // "disc", "hexagon", "diamond", "shield", "dogtag", "ribbon", "medal" or "label"
format: "padded", // "arabic", "padded", "roman" or "alpha"
pad: 2,
prefix: { mode: "initial" }, // "none", "fixed" (with `text`) or "initial" (the actor's)
separator: "dash", // "none", "dash", "dot" or "space"
plain: false, // flat colours, no lighting or ring style (no glow)
glow: true,
glowColour: [0.2, 1, 0.6],
unlit: true, // a display's unlit bars
stretch: false, // widen a dog tag or ribbon for a long label
ribbon: null, // a medal's ribbon and its stripes (optional: red and cream)
stripe: null,
});
Only rim and field are required; the rest default to a sans number on a disc.
Register designs in the tokenizer-2.ready hook, or earlier with the module's api once it exists.
A GM can then pick the design in the settings, name it in a rule, or duplicate it in the editor.
The world's own designs are kept in the world setting badge-designs ({ designs: { [id]: design } }).
The world setting badge-themes holds the default design, each system's rules and where badges sit:
{
default: "steel",
enabled: true, // rules pick designs; off (the default) gives every badge the default design
rules: {
dnd5e: [{ fact: "creatureType", op: "is", value: "undead", theme: "bone" }],
},
// position: "top", "topRight", "right", "bottomRight", "bottom", "bottomLeft", "left" or "topLeft";
// seat: "edge", "inside" or "outside"; size: 15-50, per cent of the token frame's width
placement: { position: "bottomRight", seat: "edge", size: 30 },
}
A system's rules are stored only once a GM changes them; until then it uses the built-in ones.
Rules use the same facts as ornament rules, so a system registered there can choose badge designs too.
List facts (Pathfinder and Starfinder traits) use the has and hasNot tests.
Dynamic rings
Added in API 1.2.
Tokenizer2.dynamicRings manages the world's Tokenizer dynamic rings: Ring Generator designs registered as Foundry dynamic token rings.
Foundry reads its rings when the world loads, so changes to its list or to the world's ring show after a reload.
Everything but list, export and openManager is GM only.
See Dynamic Rings.
| Member | Description |
|---|---|
list() | The world's rings: [{ id, label, theme, image, colour, background, injected, sheet }], where a ring has a theme or, made from a picture, an image. injected is whether the ring is in Foundry's list; sheet is its sprite sheet in this world, or null |
create(theme, options?) | Make a ring from a built-in theme id or theme data (with its finish). Options: label, colour: { mode: "full" | "tint", band: { inner, outer }, ringColour }, background: { mode: "dark" | "grey" | "theme" | "custom" | "picture" | "none", colour, src } (src is the picture for "picture") |
fromPicture(src, options?) | Make a ring from an existing ring picture. A frame made in the Ring Generator keeps its design; any other picture is measured for where its ring starts and ends (image: { src, inner, outer, colour }). Options: label |
update(id, changes) | Change a ring's label, theme, image, colour or background; a ring in Foundry's list gets a new sprite sheet |
inject(id, injected = true) | Put a ring in Foundry's list (drawing its sprite sheet if need be), or take it out |
activate(id) | Make a ring the world's dynamic ring, putting it in Foundry's list first if need be |
delete(id) | Delete a ring; the world goes back to Foundry's steel ring if it was the world's |
export(ids?) | The rings (all of them when no ids are given) as an export file's data; a picture ring carries its picture as a data URL |
import(json, options?) | Add the rings in an export file's JSON. onConflict(def, existing) answers "replace", "copy" or "skip" for a ring the world has (default "copy") |
openManager(options?) | Open the Dynamic Rings window, on a ring if given: { ring } |
Foundry knows each ring as tokenizer-2.<id>, the value its core dynamicTokenRing setting stores.
const ring = await Tokenizer2.dynamicRings.create("fantasy-etched-gold", {
label: "Etched Gold",
colour: { mode: "tint" },
background: { mode: "grey" },
});
await Tokenizer2.dynamicRings.activate(ring.id);
Settings backup
Added in API 1.1.
A GM can export Tokenizer 2's settings to module storage or a JSON file and import them into another world (Settings, Backup tab).
| Member | Description |
|---|---|
openExport(options?) | Open the export window. { categories } preselects what to include |
openImport(options?) | Open the import window. { categories } preselects what to import |
list() | The backups in module storage, newest version of each name first: [{ name, slug, version, exported, from, categories, path, backup }] |
The categories are general, conditions, themes, ornaments and frames.
A backup is plain JSON:
{
format: 1,
module: "tokenizer-2",
name: "My Table",
slug: "my-table", // the file name in module storage
version: 2, // goes up each time the same name is saved to module storage
exported: "2026-10-03T13:18:41.000Z",
from: { world, title, system },
categories: ["general", "conditions"],
settings: { "export-size": 4096, "condition-library": { ... } } // setting key: value
}
Backups in module storage are kept in modules/tokenizer-2/storage/backups/<slug>.json.
When persistent module storage is not available they go to tokenizer/backups in the user data folder instead.
Settings that only make sense in one world are never included, such as the list of images made for its tokens or which tours have been seen.
Player requests
Added in API 1.3.
Players can send something to the GM for approval, such as a coat of arms; the GM approves it in the Player Requests window and it is applied on the GM's client. Request types are added by modules (see Player Requests in the plugin guide).
| Member | Description |
|---|---|
register(type, def) | Add a request type, "<moduleId>.<name>", with label, sanitize and apply (and optionally describe, preview, maxBytes) |
send(type, payload, { actor }) | Send a request (an owner of the actor). Resolves to { record, replaced }; rejects with a GmRequestError whose code says why |
pending({ actor, type }?) | The waiting requests, oldest first: [{ actor, record, def, label }] |
withdraw(actor, type) | Take back a waiting request (an owner of the actor) |
approve(actorUuid, recordId) | Approve a request (GM): checked again, applied, cleared, and the player told |
decline(actorUuid, recordId) | Decline a request (GM): cleared, and the player told |
open({ actorUuid, recordId }?) | Open the Player Requests window (GM), on a request if given |
GmRequestError | The error requests fail with: code is unknown-type, not-owner, invalid, too-large, old-version, gone, gm-only or apply-failed |
A request is kept on its actor as flags["tokenizer-2"].requests:
{ v: 1, id, type, userId, userName, createdAt, payload }