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- TokenLayer - •
canvas.tiles- TilesLayer - •
canvas.lighting- LightingLayer - •
canvas.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 data - •
NumberField- Numeric values (integers or floats) - •
BooleanField- True/false values - •
ObjectField- Nested objects - •
ArrayField- Arrays of values - •
SchemaField- Nested DataModel schema - •
HTMLField- Sanitized HTML content - •
FilePathField- File paths (images, sounds, etc.) - •
ColorField- Color values (hex strings) - •
AngleField- Angles in degrees - •
AlphaField- 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