This skill should be used when importing Foundry classes, registering sheets, loading templates, enriching HTML, or using any Foundry API that has moved to namespaces. Covers compat wrappers, deferred sheet registration, and the modern-first fallback pattern.
Use compatibility wrappers when importing Foundry classes, registering sheets, loading templates, or enriching HTML. This skill provides modern-first fallback patterns that prevent deprecation warnings and future breaks as APIs move from globals to namespaces across V12-V15+.
/plugin marketplace add ImproperSubset/hh-agentics/plugin install fvtt-dev@hh-agenticsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Use compatibility wrappers to avoid deprecation warnings when APIs move from globals to namespaces across Foundry versions.
Invoke this skill when:
game, ui, CONFIG, HooksFoundry V11/V12 (Legacy):
// APIs available as globals
ActorSheet
ItemSheet
TextEditor.enrichHTML()
loadTemplates()
renderTemplate()
randomID()
Actors.registerSheet()
Foundry V13+ (Namespaced):
// APIs moved to namespaces
foundry.appv1.sheets.ActorSheet
foundry.appv1.sheets.ItemSheet
foundry.applications.ux.TextEditor.implementation.enrichHTML()
foundry.applications.handlebars.loadTemplates()
foundry.applications.handlebars.renderTemplate()
foundry.utils.randomID()
foundry.applications.api.DocumentSheetConfig.registerSheet()
Foundry V15+ (Legacy Removed):
Globals will be REMOVED entirely
↓
Direct global access will break
↓
Must use namespaced APIs only
// ❌ This worked in V11/V12, will BREAK in V15+
import { ActorSheet } from "somewhere"; // No longer a global!
class MySheet extends ActorSheet {
// ...
}
// ❌ This throws deprecation warnings in V13
TextEditor.enrichHTML(content, options);
// ❌ This will stop working in V15
Actors.registerSheet("my-module", MySheet, { makeDefault: true });
??)File: scripts/compat.js
/**
* Compatibility helpers for Foundry V12/V13/V15+
* Prefer modern namespaced APIs, fallback to legacy globals
*/
/**
* Get ActorSheet class (modern or legacy)
* @returns {class} ActorSheet constructor
*/
export function getActorSheetClass() {
// Try V13+ namespace first
const modern = foundry?.appv1?.sheets?.ActorSheet;
if (modern) return modern;
// Fallback to V11/V12 global
if (typeof ActorSheet !== 'undefined') return ActorSheet;
throw new Error("Unable to resolve ActorSheet class");
}
/**
* Get ItemSheet class (modern or legacy)
* @returns {class} ItemSheet constructor
*/
export function getItemSheetClass() {
return foundry?.appv1?.sheets?.ItemSheet ?? ItemSheet;
}
/**
* Enrich HTML content (journal entries, descriptions)
* @param {string} content - Raw HTML/markdown content
* @param {object} options - Enrichment options
* @returns {Promise<string>} Enriched HTML
*/
export function enrichHTML(content, options = {}) {
// Try V13+ namespace
const textEditor = foundry?.applications?.ux?.TextEditor?.implementation;
if (textEditor?.enrichHTML) {
return textEditor.enrichHTML(content, options);
}
// Fallback to V11/V12 global
if (typeof TextEditor !== 'undefined' && TextEditor.enrichHTML) {
return TextEditor.enrichHTML(content, options);
}
throw new Error("Unable to resolve TextEditor.enrichHTML");
}
/**
* Generate random ID
* @returns {string} Random ID
*/
export function generateRandomId() {
const randomIdFn = foundry?.utils?.randomID ?? randomID;
if (!randomIdFn) {
throw new Error("Unable to resolve randomID generator");
}
return randomIdFn();
}
Pattern:
// Template for adding new compat functions
export function getAPIClass() {
// 1. Try modern namespace
const modern = foundry?.path?.to?.API;
if (modern) return modern;
// 2. Fallback to legacy global
if (typeof LegacyGlobal !== 'undefined') return LegacyGlobal;
// 3. Throw clear error
throw new Error("Unable to resolve API");
}
File: scripts/compat-helpers.js
import { getActorSheetClass, getItemSheetClass } from "./compat.js";
// Cache DocumentSheetConfig to avoid repeated lookups
let cachedSheetConfig;
/**
* Get DocumentSheetConfig (modern V13+) or null
* @returns {object|null}
*/
function getSheetConfig() {
if (cachedSheetConfig) return cachedSheetConfig;
// Try multiple V13+ namespace locations
const apiConfig =
foundry?.applications?.apps?.DocumentSheetConfig ??
foundry?.applications?.config?.DocumentSheetConfig ??
foundry?.applications?.api?.DocumentSheetConfig;
cachedSheetConfig = apiConfig ?? null;
return cachedSheetConfig;
}
/**
* Get legacy Actors collection (V11/V12)
* @returns {object}
*/
function getActorsCollectionLegacy() {
return foundry?.documents?.collections?.Actors ?? Actors;
}
/**
* Get legacy Items collection (V11/V12)
* @returns {object}
*/
function getItemsCollectionLegacy() {
return foundry?.documents?.collections?.Items ?? Items;
}
/**
* Register actor sheet (compatible across versions)
* @param {string} namespace - Module ID
* @param {class} sheetClass - Sheet constructor
* @param {object} options - Registration options
*/
export function registerActorSheet(namespace, sheetClass, options = {}) {
const sheetConfig = getSheetConfig();
// Try V13+ API
if (sheetConfig?.registerSheet) {
return sheetConfig.registerSheet(
CONFIG.Actor.documentClass,
namespace,
sheetClass,
options
);
}
// Fallback to V11/V12 API
return getActorsCollectionLegacy()?.registerSheet?.(
namespace,
sheetClass,
options
);
}
/**
* Unregister actor sheet (compatible across versions)
*/
export function unregisterActorSheet(namespace, sheetClass) {
const sheetConfig = getSheetConfig();
if (sheetConfig?.unregisterSheet) {
return sheetConfig.unregisterSheet(
CONFIG.Actor.documentClass,
namespace,
sheetClass
);
}
return getActorsCollectionLegacy()?.unregisterSheet?.(namespace, sheetClass);
}
/**
* Register item sheet (compatible across versions)
*/
export function registerItemSheet(namespace, sheetClass, options = {}) {
const sheetConfig = getSheetConfig();
if (sheetConfig?.registerSheet) {
return sheetConfig.registerSheet(
CONFIG.Item.documentClass,
namespace,
sheetClass,
options
);
}
return getItemsCollectionLegacy()?.registerSheet?.(
namespace,
sheetClass,
options
);
}
/**
* Unregister item sheet (compatible across versions)
*/
export function unregisterItemSheet(namespace, sheetClass) {
const sheetConfig = getSheetConfig();
if (sheetConfig?.unregisterSheet) {
return sheetConfig.unregisterSheet(
CONFIG.Item.documentClass,
namespace,
sheetClass
);
}
return getItemsCollectionLegacy()?.unregisterSheet?.(namespace, sheetClass);
}
/**
* Load Handlebars templates (compatible across versions)
* @param {Array<string>} paths - Template paths
* @returns {Promise}
*/
export function loadHandlebarsTemplates(paths) {
// Try V13+ namespace
const loader = foundry?.applications?.handlebars?.loadTemplates;
if (loader) {
return loader(paths);
}
// Fallback to V11/V12 global
if (typeof loadTemplates !== 'undefined') {
return loadTemplates(paths);
}
throw new Error("Unable to resolve Handlebars template loader");
}
/**
* Render Handlebars template (compatible across versions)
* @param {string} path - Template path
* @param {object} data - Template data
* @returns {Promise<string>}
*/
export function renderHandlebarsTemplate(path, data) {
// Try V13+ namespace
const renderer = foundry?.applications?.handlebars?.renderTemplate;
if (renderer) {
return renderer(path, data);
}
// Fallback to V11/V12 global
if (typeof renderTemplate !== 'undefined') {
return renderTemplate(path, data);
}
throw new Error("Unable to resolve Handlebars template renderer");
}
File: scripts/module.js (Entry point)
import { registerActorSheet, registerItemSheet } from "./compat-helpers.js";
import { loadHandlebarsTemplates } from "./compat-helpers.js";
import { BladesAlternateActorSheet } from "./blades-alternate-actor-sheet.js";
import { BladesAlternateItemSheet } from "./blades-alternate-item-sheet.js";
const MODULE_ID = "my-module";
Hooks.once("init", async function() {
console.log("My Module | Initializing");
// Load templates (compat)
await loadHandlebarsTemplates([
"modules/my-module/templates/actor-sheet.html",
"modules/my-module/templates/item-sheet.html",
]);
});
Hooks.once("ready", async function() {
// Register sheets (compat)
// Why ready hook? V13+ requires DocumentSheetConfig to be available
// which isn't ready during init
registerActorSheet(
MODULE_ID,
BladesAlternateActorSheet,
{
types: ["character"],
makeDefault: true,
label: "Alternate Character Sheet"
}
);
registerItemSheet(
MODULE_ID,
BladesAlternateItemSheet,
{
types: ["item"],
makeDefault: false,
label: "Alternate Item Sheet"
}
);
});
File: scripts/blades-alternate-actor-sheet.js
import { getActorSheetClass } from "./compat.js";
import { enrichHTML } from "./compat.js";
// Get base class via compat wrapper
const ActorSheet = getActorSheetClass();
export class BladesAlternateActorSheet extends ActorSheet {
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["bitd-alt", "sheet", "actor"],
template: "modules/my-module/templates/actor-sheet.html",
width: 900,
height: 800,
});
}
async getData() {
const data = await super.getData();
// Enrich description (compat)
if (data.actor.system.description) {
data.enrichedDescription = await enrichHTML(
data.actor.system.description,
{ secrets: data.editable }
);
}
return data;
}
}
// Get class, throw if not found
export function getAPIClass() {
const api = foundry?.new?.path?.API ?? LegacyGlobalAPI;
if (!api) {
throw new Error("Unable to resolve API");
}
return api;
}
// Cache expensive lookups
let cachedConfig;
function getConfig() {
if (cachedConfig) return cachedConfig;
const config =
foundry?.new?.path?.Config ??
foundry?.another?.path?.Config ??
LegacyConfig;
cachedConfig = config ?? null;
return cachedConfig;
}
// Wrap methods that moved
export function someMethod(...args) {
const api = foundry?.new?.path?.API?.implementation;
if (api?.someMethod) {
return api.someMethod(...args);
}
if (typeof LegacyAPI !== 'undefined' && LegacyAPI.someMethod) {
return LegacyAPI.someMethod(...args);
}
throw new Error("Unable to resolve someMethod");
}
// Use ?. to safely traverse nested paths
export function getDeepAPI() {
return (
foundry?.level1?.level2?.level3?.API ??
OldGlobal?.level1?.API ??
LegacyAPI
);
}
ready Hook?// ❌ BAD: Registering in init hook
Hooks.once("init", function() {
registerActorSheet(MODULE_ID, MySheet, { ... });
});
// Result in V13+:
// Error: DocumentSheetConfig is not available yet!
// ✅ GOOD: Register in ready hook
Hooks.once("ready", function() {
registerActorSheet(MODULE_ID, MySheet, { ... });
});
Why?
init hook (globals available)DocumentSheetConfig isn't initialized until after initDocumentSheetConfigSolution: Always register sheets in ready hook for V13+ compatibility
import { getActorSheetClass } from "./compat.js";
const ActorSheet = getActorSheetClass();
export class MyActorSheet extends ActorSheet {
// No deprecation warnings!
}
import { enrichHTML } from "./compat.js";
async function displayJournalEntry(content) {
const enriched = await enrichHTML(content, {
secrets: game.user.isGM,
documents: true,
links: true,
});
return enriched;
}
import { loadHandlebarsTemplates } from "./compat-helpers.js";
Hooks.once("init", async function() {
await loadHandlebarsTemplates([
"modules/my-module/templates/actor-sheet.html",
"modules/my-module/templates/parts/abilities.html",
"modules/my-module/templates/parts/items.html",
]);
});
import { generateRandomId } from "./compat.js";
function createNewItem() {
const item = {
_id: generateRandomId(),
name: "New Item",
type: "item",
};
return item;
}
Identify the move
OldAPIfoundry.new.path.NewAPIAdd compat function
export function getNewAPI() {
return foundry?.new?.path?.NewAPI ?? OldAPI;
}
Update imports
// Before
import { OldAPI } from "somewhere";
// After
import { getNewAPI } from "./compat.js";
const OldAPI = getNewAPI();
Test across versions
Common APIs that have moved or will move:
ActorSheet → foundry.appv1.sheets.ActorSheetItemSheet → foundry.appv1.sheets.ItemSheetTextEditor.enrichHTML → foundry.applications.ux.TextEditor.implementation.enrichHTMLloadTemplates → foundry.applications.handlebars.loadTemplatesrenderTemplate → foundry.applications.handlebars.renderTemplaterandomID → foundry.utils.randomIDActors.registerSheet → DocumentSheetConfig.registerSheetItems.registerSheet → DocumentSheetConfig.registerSheetDialog → DialogV2 (see dialog-compat skill)1. Test in Foundry V11 (if supporting)
- Check console for errors
- Verify sheets register correctly
- Confirm templates load
2. Test in Foundry V12
- Same checks as V11
- Look for deprecation warnings
3. Test in Foundry V13+
- No deprecation warnings
- All features work
- Modern APIs used (check network/console)
// In browser console, verify which API is being used
// V11/V12 - Should use globals
console.log(typeof ActorSheet !== 'undefined'); // true
// V13+ - Should use namespaces
console.log(foundry?.appv1?.sheets?.ActorSheet); // class ActorSheet
// BAD: Will throw deprecation warnings in V13, break in V15
class MySheet extends ActorSheet {
// ...
}
Fix: Use compat wrapper
// GOOD
import { getActorSheetClass } from "./compat.js";
const ActorSheet = getActorSheetClass();
class MySheet extends ActorSheet {
// ...
}
// BAD: Breaks in V13+
Hooks.once("init", function() {
Actors.registerSheet(MODULE_ID, MySheet, { ... });
});
Fix: Use ready hook + compat wrapper
// GOOD
import { registerActorSheet } from "./compat-helpers.js";
Hooks.once("ready", function() {
registerActorSheet(MODULE_ID, MySheet, { ... });
});
// BAD: Assumes single namespace location
export function getConfig() {
return foundry?.applications?.api?.Config ?? LegacyConfig;
}
// Problem: Config might be in different namespace!
Fix: Check multiple locations
// GOOD
export function getConfig() {
return (
foundry?.applications?.api?.Config ??
foundry?.applications?.apps?.Config ??
foundry?.applications?.config?.Config ??
LegacyConfig
);
}
// BAD: Returns undefined if both fail
export function getAPI() {
return foundry?.new?.API ?? OldAPI;
}
// Calling code will crash with cryptic error later
Fix: Throw clear error
// GOOD
export function getAPI() {
const api = foundry?.new?.API ?? OldAPI;
if (!api) {
throw new Error("Unable to resolve API - unsupported Foundry version?");
}
return api;
}
Before using Foundry APIs:
ready hookscripts/compat.js - Core compatibility wrappersscripts/compat-helpers.js - Sheet registration, template loadingdocs/compat-helpers-guide.md - Usage examplesFor BitD Alternate Sheets:
getActorSheetClass() / getItemSheetClass()ready hook via registerActorSheet()loadHandlebarsTemplates() in init hookenrichHTML() wrapperThis skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.