Foundry 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 (
require) to ESM (import/export) - Implementing DataModel for structured data
- Updating deprecated patterns (
actor.data.datatoactor.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
importandexportstatements (NOrequire()) - Declare
"esmodules"in your manifest (NOT"scripts") - Use
.jsextensions in import paths when importing relative files
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: createToken Hook
// 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.name,item.system - Use
actor.systemto access system-specific data defined in your DataModel - Avoid accessing
actor.data.data(deprecated pattern from V10)
// 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
document.effects(EffectCollection) - Use
await actor.createEmbeddedDocuments("ActiveEffect", [effectData]) - Effect changes use
key(path to property) andmode(add, multiply, override, etc.)
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:
canvas.tokens- TokenLayercanvas.tiles- TilesLayercanvas.lighting- LightingLayercanvas.grid- GridLayer
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:
StringField- Text dataNumberField- Numeric values (integers or floats)BooleanField- True/false valuesObjectField- Nested objectsArrayField- Arrays of valuesSchemaField- Nested DataModel schemaHTMLField- Sanitized HTML contentFilePathField- File paths (images, sounds, etc.)ColorField- Color values (hex strings)AngleField- Angles in degreesAlphaField- Alpha transparency (0-1)
Best Practices
- Always await async operations - Most Foundry operations are async
- Check for user permissions - Use
game.user.isGMor document permission checks - Use
fromUuidSync()orfromUuid()- For reliable document references - 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
console.log()liberally, or set up proper logging withCONFIG.debug - Test in a fresh world to avoid conflicts with other modules
- Use
CONFIG.debug.hooks = trueto see all hook executions
Remember: When in doubt, check the official Foundry VTT V13 API documentation at https://foundryvtt.com/api/
Implementation Checklist
Module Structure
- Manifest declares
"esmodules"(not"scripts") - All imports use ESM syntax with
.jsextensions - Compatibility set to minimum V13
Data Patterns
- DataModel classes define schemas with
defineSchema() - Access system data via
document.system(notdata.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
