Claude
Skills
Sign in
Back

fvtt-version-compat

Included with Lifetime
$97 forever

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.

Web Dev

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);
  }

  t

Related in Web Dev