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.
What this skill does
# Foundry VTT Version Compatibility (V12/V13/V15+)
Use compatibility wrappers to avoid deprecation warnings when APIs move from globals to namespaces across Foundry versions.
## When to Use This Skill
Invoke this skill when:
### ✅ Use Compat Wrappers For:
- **Importing Foundry classes** - ActorSheet, ItemSheet, TextEditor
- **Registering sheets** - Actor sheets, item sheets, document sheets
- **Loading templates** - Handlebars template loading
- **Enriching HTML** - TextEditor.enrichHTML for journal content
- **Generating IDs** - randomID() for unique identifiers
- **Any Foundry API** - That has moved or will move to namespaces
### ❌ Don't Use Compat Wrappers For:
- **Stable globals** - `game`, `ui`, `CONFIG`, `Hooks`
- **Project-specific code** - Your own classes and functions
- **One-off migrations** - If only targeting a single Foundry version
- **Styling/layout** - CSS and templates (not API drift)
## The Problem: API Migration Across Versions
### What Changed Across Foundry Versions
**Foundry V11/V12 (Legacy):**
```javascript
// APIs available as globals
ActorSheet
ItemSheet
TextEditor.enrichHTML()
loadTemplates()
renderTemplate()
randomID()
Actors.registerSheet()
```
**Foundry V13+ (Namespaced):**
```javascript
// 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
```
### Why This Breaks Code
```javascript
// ❌ 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 });
```
## Solution: Compatibility Wrappers
### Three Core Patterns
1. **Try modern first, fallback to legacy** - Nullish coalescing (`??`)
2. **Cache resolved classes** - Avoid repeated lookups
3. **Throw clear errors** - Don't silently fail if neither exists
## Step-by-Step Implementation
### Step 1: Create Compatibility Module
**File:** `scripts/compat.js`
```javascript
/**
* 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:**
```javascript
// 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");
}
```
### Step 2: Sheet Registration Compatibility
**File:** `scripts/compat-helpers.js`
```javascript
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);
}
```
### Step 3: Template Loading Compatibility
```javascript
/**
* 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);
}
tRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.