Skip to main content

Plugin API

Overview

Tokenizer 2 exposes a hook-based plugin system that allows other Foundry VTT modules to:

  • Register toolbar tools with custom icons, panels, and consent dialogs
  • Listen to lifecycle hooks fired during editor use
  • Inject license information into the Settings → Licenses tab

Plugins are standard Foundry VTT modules that declare a dependency on tokenizer-2 and register themselves via the tokenizer-2.registerPlugins hook.

Quick Start

1. Create a Foundry module

my-plugin/
├── module.json
├── scripts/
│ └── index.js
└── lang/
└── en.json

module.json - declare a dependency on tokenizer-2:

{
"id": "my-tokenizer-plugin",
"title": "My Tokenizer Plugin",
"version": "1.0.0",
"compatibility": { "minimum": "13", "verified": "13" },
"relationships": {
"requires": [{ "id": "tokenizer-2", "type": "module" }]
},
"esmodules": ["scripts/index.js"],
"languages": [{ "lang": "en", "name": "English", "path": "lang/en.json" }]
}

2. Register your plugin

In scripts/index.js:

import { MyTool } from "./MyTool.js";

Hooks.once("tokenizer-2.registerPlugins", (registry) => {
registry.register({
id: "my-tool",
moduleId: "my-tokenizer-plugin",
name: "My Tool",
description: "Does something useful",
version: "1.0.0",
author: "Your Name",
tools: [{
id: "my-tool",
icon: "fa-solid fa-wand-magic-sparkles",
tooltip: "My Tool Tooltip",
toolClass: MyTool,
panel: renderMyPanel,
}],
});
});

function renderMyPanel(container, { app, tool }) {
const p = document.createElement("p");
p.textContent = "My tool is active!";
container.appendChild(p);
}

Plugin Descriptor

The object passed to registry.register():

FieldTypeRequiredDescription
idstringyesUnique plugin identifier
moduleIdstringyesThe Foundry module ID (must match module.json)
namestringyesDisplay name
descriptionstringnoShort description
versionstringnoPlugin version
authorstringnoAuthor name
licenseobjectnoLicense info shown in Settings → Licenses tab
license.namestringyes (if license)Library/license name
license.textstringnoDescription text
license.urlstringnoLink to library/license
license.copyrightstringnoCopyright notice
toolsToolDescriptor[]noArray of tools to add to the toolbar
hooksobjectnoMap of hook names to callback functions

Tool Descriptor

Each entry in the tools array:

FieldTypeRequiredDescription
idstringyesUnique tool identifier (used as data-tool attribute)
iconstringyesFontAwesome class (e.g. "fa-solid fa-scissors")
tooltipstringyesTooltip text or localization key (keys containing . are passed through game.i18n.localize())
toolClassclassyesES class instantiated when the tool is activated
panelfunctionno(container: HTMLElement, ctx: { app, tool }) => void - builds the left-panel UI
consentobjectnoConsent dialog shown before first use

If your tool requires user consent (e.g. for AI/ML features):

FieldTypeRequiredDescription
moduleIdstringnoModule ID for the setting (defaults to plugin's moduleId)
settingKeystringyesSetting key storing consent state ("" = not asked, "yes", "no")
titlestringyesDialog title (or localization key)
contentstringyesDialog body HTML (or localization key)
yesLabelstringyesAccept button label (or localization key)
noLabelstringyesDecline button label (or localization key)

You must register the consent setting in a Hooks.once("init", ...) handler under your module's ID:

Hooks.once("init", () => {
game.settings.register("my-tokenizer-plugin", "my-consent", {
scope: "world",
config: false,
type: String,
default: "",
});
});

Tool Class Interface

Your tool class is instantiated when the user clicks the toolbar button and destroyed when they switch away. Implement any of these methods:

export class MyTool {
/**
* Called when the tool becomes active.
* @param {object} ctx
* @param {TokenImageEditor} ctx.app - The editor instance
* @param {LayerManager} ctx.layerManager
* @param {CanvasEngine} ctx.canvasEngine
* @param {Function} ctx.scheduleRender - Call to trigger a canvas re-render
* @param {Function} ctx.pushUndoSnapshot - Call before making changes for undo support
*/
activate(ctx) {}

/** Called when the tool is deactivated (user switches to another tool). */
deactivate() {}

/**
* Called when the user selects a different layer while this tool is active.
* @param {string} newLayerId
*/
onActiveLayerChange(newLayerId) {}

// Pointer event handlers - called by ToolManager with logical canvas coordinates
onPointerDown(e, logicalX, logicalY) {}
onPointerMove(e, logicalX, logicalY) {}
onPointerUp(e, logicalX, logicalY) {}
onWheel(e) {}
onKeyDown(e) {}
}

Panel Function

The panel function is called on every render cycle when your tool is the active tool. It receives:

  • container - an empty <div id="tie-plugin-panel"> element. Build your UI by appending DOM nodes.
  • ctx.app - the TokenImageEditor instance (access layerManager, canvasEngine, etc.)
  • ctx.tool - your tool class instance (access tool state like slider values)

The container is cleared (innerHTML = "") before each call, so build the full panel every time. Attach event listeners directly to your elements.

function renderMyPanel(container, { app, tool }) {
const heading = document.createElement("h3");
heading.className = "tie-panel__heading";
heading.textContent = "My Tool";
container.appendChild(heading);

// Add sliders, buttons, etc.
}

Lifecycle Hooks

Subscribe to these hooks via the standard Foundry Hooks.on() API, or via the hooks field in your plugin descriptor.

Hook NameFired WhenData
tokenizer-2.registerPluginsOnce at startup, before tokenizer-2.readyregistry - call registry.register({...})
tokenizer-2.readyOnce after all plugins registeredapi - the Tokenizer 2 public API object
tokenizer-2.editorOpenEditor finishes first render{ editor, actor, token }
tokenizer-2.editorCloseEditor begins closing{ editor, actor }
tokenizer-2.preSaveBefore token image is exported{ editor, actor, layers }
tokenizer-2.postSaveAfter token image is saved{ editor, actor, path }
tokenizer-2.toolActivatedA tool becomes active{ toolName, tool }
tokenizer-2.toolDeactivatedA tool is deactivated{ toolName }
tokenizer-2.layerAddedA layer is added to the stack{ layer }
tokenizer-2.layerRemovedA layer is removed from the stack{ layerId }

Using the hooks field

registry.register({
id: "my-plugin",
moduleId: "my-tokenizer-plugin",
name: "My Plugin",
hooks: {
"tokenizer-2.editorOpen": ({ editor, actor }) => {
console.log(`Editor opened for ${actor.name}`);
},
"tokenizer-2.layerAdded": ({ layer }) => {
console.log(`Layer added: ${layer.name}`);
},
},
});

Accessing Tokenizer 2 Internals

The Tokenizer 2 API is available after the tokenizer-2.ready hook:

// Via the module API
const api = game.modules.get("tokenizer-2").api;

// Via the global
const api = window.Tokenizer2;

The API includes pluginRegistry for inspecting registered plugins at runtime.

Within a tool's activate() method and the panel function, you receive the app (editor) instance which provides access to:

  • app.layerManager - layer CRUD, active layer, serialization
  • app.canvasEngine - rendering, image cache, export
  • app.toolManager - active tool state
  • app._scheduleRender() - trigger a canvas re-render

Tokenizer Lib

Tokenizer exposes it's libray through globalThis.Tokenizer2.lib

Example: Auto Subject Cutout

This example will be available on GitHub soon!