Claude-skill-registry fvtt-v13-migration
This skill should be used when the user asks to "migrate to V13", "upgrade to Foundry V13", "update module for V13", "fix V13 compatibility", "convert to ESM", "use DataModel", or mentions V13-specific patterns like hook signature changes, actor.system vs actor.data.data, or Application V2. Provides comprehensive Foundry VTT V13 development patterns and migration guidance.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/fvtt-v13-migration" ~/.claude/skills/majiayu000-claude-skill-registry-fvtt-v13-migration && rm -rf "$T"
skills/data/fvtt-v13-migration/SKILL.mdFoundry VTT V13 Development Guide
Domain: Foundry VTT Module/System Development Status: Production-Ready Last Updated: 2026-01-05
Overview
This guide covers V13-specific patterns and migration from earlier versions. For modules targeting V13, follow these guidelines.
When to Use This Skill
- Migrating a module/system from V12 or earlier to V13
- Converting CommonJS (
) to ESM (require
/import
)export - Implementing DataModel for structured data
- Updating deprecated patterns (
toactor.data.data
)actor.system - Fixing hook signature changes after V13 upgrade
- Setting up V13-compatible manifest configuration
Core Architecture Principles
Use DataModel for Structured Data
ALWAYS use
foundry.abstract.DataModel for defining data structures instead of plain JavaScript objects. DataModels provide:
- Built-in validation and type coercion
- Schema definition with
DataSchema - Automatic data preparation lifecycle
- Integration with Foundry's document system
Example:
class MyModuleData extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; return { name: new fields.StringField({ required: true, blank: false }), value: new fields.NumberField({ initial: 0, min: 0 }), enabled: new fields.BooleanField({ initial: true }) }; } }
ESM Modules Only
Foundry V13 uses ECMAScript Modules (ESM) exclusively. Your code must:
- Use
andimport
statements (NOexport
)require() - Declare
in your manifest (NOT"esmodules"
)"scripts" - Use
extensions in import paths when importing relative files.js
Example manifest entry:
{ "esmodules": ["scripts/module.js"], "scripts": [] }
Internationalization (i18n)
ALWAYS use
game.i18n.localize() or game.i18n.format() for user-facing text. Never hardcode English strings.
// BAD ui.notifications.info("Item created successfully"); // GOOD ui.notifications.info(game.i18n.localize("MYMODULE.Notifications.ItemCreated")); // For dynamic values const message = game.i18n.format("MYMODULE.Notifications.ItemCreatedWithName", { name: itemName });
Localization files go in
lang/en.json:
{ "MYMODULE.Notifications.ItemCreated": "Item created successfully", "MYMODULE.Notifications.ItemCreatedWithName": "Item {name} created successfully" }
Common V13 Pitfalls
Hook Arguments Have Changed
Many hooks in V13 have different argument signatures than V12. Always check the current API documentation.
Example:
HookcreateToken
// V12 (OLD - WRONG in V13) Hooks.on("createToken", (scene, tokenData, options, userId) => { }); // V13 (CORRECT) Hooks.on("createToken", (tokenDocument, options, userId) => { });
Document vs Data
V13 distinguishes between Document classes (e.g.,
Actor, Item) and their data models:
- Access document properties directly:
,actor.nameitem.system - Use
to access system-specific data defined in your DataModelactor.system - Avoid accessing
(deprecated pattern from V10)actor.data.data
// BAD (V10 pattern) const hp = actor.data.data.attributes.hp.value; // GOOD (V13 pattern) const hp = actor.system.attributes.hp.value;
Active Effects Structure
Active Effects in V13 use a cleaner structure:
- Effects are stored in
(EffectCollection)document.effects - Use
await actor.createEmbeddedDocuments("ActiveEffect", [effectData]) - Effect changes use
(path to property) andkey
(add, multiply, override, etc.)mode
const effectData = { name: game.i18n.localize("MYMODULE.Effects.Blessed"), icon: "icons/magic/light/beam-rays-yellow.webp", changes: [{ key: "system.attributes.ac.bonus", mode: CONST.ACTIVE_EFFECT_MODES.ADD, value: "2" }], duration: { rounds: 10 } }; await actor.createEmbeddedDocuments("ActiveEffect", [effectData]);
Canvas Rendering Layers
V13 has reorganized canvas layers. Use the correct layer references:
- TokenLayercanvas.tokens
- TilesLayercanvas.tiles
- LightingLayercanvas.lighting
- GridLayercanvas.grid
Dialog API Updates
Dialog construction now prefers Application V2 patterns in some contexts, but classic Dialogs still work:
new Dialog({ title: game.i18n.localize("MYMODULE.Dialog.Title"), content: `<p>${game.i18n.localize("MYMODULE.Dialog.Content")}</p>`, buttons: { yes: { icon: '<i class="fas fa-check"></i>', label: game.i18n.localize("MYMODULE.Dialog.Confirm"), callback: () => { /* action */ } }, no: { icon: '<i class="fas fa-times"></i>', label: game.i18n.localize("MYMODULE.Dialog.Cancel") } }, default: "yes" }).render(true);
Data Field Types Reference
When defining DataModel schemas, use these field types from
foundry.data.fields:
- Text dataStringField
- Numeric values (integers or floats)NumberField
- True/false valuesBooleanField
- Nested objectsObjectField
- Arrays of valuesArrayField
- Nested DataModel schemaSchemaField
- Sanitized HTML contentHTMLField
- File paths (images, sounds, etc.)FilePathField
- Color values (hex strings)ColorField
- Angles in degreesAngleField
- Alpha transparency (0-1)AlphaField
Best Practices
- Always await async operations - Most Foundry operations are async
- Check for user permissions - Use
or document permission checksgame.user.isGM - Use
orfromUuidSync()
- For reliable document referencesfromUuid() - Leverage Hooks - Don't override core behavior, extend it via hooks
- Handle errors gracefully - Wrap operations in try/catch and show user-friendly messages
- Test with multiple users - Permission and rendering issues often appear in multi-user scenarios
- Follow Foundry's module conventions - Use proper manifest structure, versioning, and compatibility flags
Manifest Requirements
Your
module.json or system.json must declare V13 compatibility:
{ "id": "my-module", "title": "My Module", "version": "1.0.0", "compatibility": { "minimum": "13", "verified": "13", "maximum": "13" }, "esmodules": ["scripts/init.js"], "languages": [ { "lang": "en", "name": "English", "path": "lang/en.json" } ] }
Development Workflow
- Enable "Developer Mode" in Foundry settings for better error messages
- Use browser DevTools Console for debugging
- Use
liberally, or set up proper logging withconsole.log()CONFIG.debug - Test in a fresh world to avoid conflicts with other modules
- Use
to see all hook executionsCONFIG.debug.hooks = true
Remember: When in doubt, check the official Foundry VTT V13 API documentation at https://foundryvtt.com/api/
Implementation Checklist
Module Structure
- Manifest declares
(not"esmodules"
)"scripts" - All imports use ESM syntax with
extensions.js - Compatibility set to minimum V13
Data Patterns
- DataModel classes define schemas with
defineSchema() - Access system data via
(notdocument.system
)data.data - Active Effects use correct change structure
Code Quality
- All user-facing strings use
game.i18n.localize() - Hook callbacks use V13 argument signatures
- Async operations are properly awaited
- Permissions checked before privileged operations
Testing
- Module loads without console errors
- DataModel validation works correctly
- Hooks fire with expected arguments
- Multi-user scenarios tested
References
Last Updated: 2026-01-05 Status: Production-Ready Maintainer: ImproperSubset