xiaohongshu
xiaohongshu
fvtt-version-compat
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.
Full skill instructions
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() wrapperxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience