The Magic of Automation
Imagine a tireless assistant who remembers every rule, does the sums, and handles the bookkeeping while you focus on storytelling. That's what macros and automation bring to Foundry VTT.
Think of macros as spells you write once and cast whenever you need them. Want to roll stealth for every hidden goblin? One click. Want the lights to drop and a message to appear when the villain arrives? One click. Want a trap to spring when a token steps on it? A Region behavior can do that without any click at all. You don't have to be a programmer: many useful macros are a few lines long, and chat macros need no code.
The Automation Ecosystem
Your First Macro: Baby Steps to Power
Foundry has two kinds of macro:
- Chat macros post whatever you type as if you'd typed it in chat, including chat commands such as
/roll. They're the safe, no-code starting point. - Script macros run JavaScript against Foundry's API. They can do almost anything the GM can do by hand. Players can only create them if the GM grants that permission, so check your permission settings before handing them out.
To make one, open the Macros tab of the sidebar and create a new macro, or click an empty slot on the macro hotbar at the bottom of the screen. Give it a name, choose Chat or Script, pick an image, type the command, and save. Drag macros onto the hotbar: it has 10 slots per page and 5 pages, and the number keys 1 to 9 and 0 run the macros on the current page. You can also run any macro by typing /macro followed by its name in chat. Many game systems also let you drag an item or ability from a character sheet onto the hotbar to make a macro for it.
Here is the same macro as text you can copy. It rolls initiative, using your game system's formula, for the selected tokens that are already in the current combat:
// Roll initiative for the selected tokens in the current combat
const combat = game.combat;
if (!combat) return ui.notifications.warn("Start an encounter first.");
const ids = canvas.tokens.controlled
.map(t => t.combatant?.id)
.filter(id => id);
if (!ids.length) return ui.notifications.warn("Select tokens in combat.");
await combat.rollInitiative(ids);
ui.notifications.info(`Rolled for ${ids.length} combatant(s).`);
Script macros run inside an async function, so you can use await and an early return at the top level.
Essential Macros Every GM Needs
Here's a macro starter kit: simple but powerful tools for common tasks.
Chat Commands: Your Quick Access Toolbar
Everything here works typed straight into chat, and anything you can type in chat can be saved as a chat macro.
Roll Formula Cheat Sheet
These formulas work in chat, in chat macros, and in new Roll(...) inside scripts. Try them first in the Dice Formula Tester.
| Formula | What it does |
|---|---|
2d20kh / 2d20kl | Roll two d20s, keep the highest / lowest |
4d6kh3 or 4d6dl | Roll four d6, keep the best three (drop the lowest) |
5d10x10 / 6d10xo10 | Explode on a 10 / explode only once |
1d10r1 / 1d20rr<3 | Reroll a 1 once / keep rerolling results under 3 |
10d20cs>=15 | Count successes: dice showing 15 or more |
4d10min2 / 4d10max8 | Treat lower results as 2 / higher results as 8 |
4df | Four Fate dice (each -1, 0 or +1) |
1d100 | Percentile roll |
1d20 + @abilities.cha.mod | Pull a value from the character (paths depend on the game system; this one is dnd5e) |
Building Complex Macros: From Simple to Spectacular
Let's grow from one-line macros to ones that ask questions and make decisions.
Example: Weather Control Macro
// Weather Control: pick the sky, set the scene's darkness, tell the table
const choice = await foundry.applications.api.DialogV2.wait({
window: { title: "Weather Control" },
content: "<p>What is the sky doing?</p>",
buttons: [
{ action: "clear", label: "Clear skies", default: true },
{ action: "dusk", label: "Dusk" },
{ action: "storm", label: "Thunderstorm" }
]
});
const darkness = { clear: 0, dusk: 0.5, storm: 0.8 }[choice];
if (darkness === undefined) return; // dialog closed without a choice
await canvas.scene.update({ "environment.darknessLevel": darkness });
await ChatMessage.create({ content: `<p>The weather turns: ${choice}.</p>` });
// Rain or snow particles: set them in the scene's configuration, or use a module
Active Effects: Living Buffs and Debuffs
Active Effects are like enchantments that know their own rules. V14 rebuilt them as Active Effects V2:
Example: Bless Spell Effect (dnd5e)
- Effect: +1d4 to attack rolls and saving throws, added through the system's bonus fields
- Duration: 10 rounds (1 minute), set to expire on its own
- Visual: a status icon on each blessed token
- Automation: the system adds the bonus to the relevant rolls while the effect is active
Practical Macro Library
Copy these into script macros. Test them on a spare world before game night.
The "Oh No!" Emergency Kit
1. Heal All PCs (dnd5e)
// Emergency healing for all player-owned actors.
// The HP path below is for the dnd5e system; other systems use different paths.
const pcs = game.actors.filter(a => a.hasPlayerOwner);
for (const pc of pcs) {
const hp = pc.system.attributes?.hp;
if (hp) await pc.update({ "system.attributes.hp.value": hp.max });
}
ui.notifications.info("All PCs healed to full!");
2. Dramatic Lighting Change
// Sudden darkness falls!
await canvas.scene.update({ "environment.darknessLevel": 0.9 });
await ChatMessage.create({
content: "<h2>Darkness Falls!</h2><p>The lights suddenly dim...</p>",
speaker: { alias: "GM" }
});
3. Roll Stealth for All Enemies (dnd5e)
// Mass stealth check, whispered to you.
// @skills.ste.total is a dnd5e path; other systems name skills differently.
const hostiles = canvas.tokens.placeables.filter(t =>
t.actor && t.document.disposition === CONST.TOKEN_DISPOSITIONS.HOSTILE
);
for (const token of hostiles) {
const roll = new Roll("1d20 + @skills.ste.total", token.actor.getRollData());
await roll.evaluate();
await roll.toMessage({
speaker: { alias: token.name },
flavor: "Stealth Check",
whisper: [game.user.id]
});
}
4. Toggle Visibility of Selected Tokens
// Hide or reveal the selected tokens in one click
const tokens = canvas.tokens.controlled;
if (!tokens.length) return ui.notifications.warn("Select tokens first!");
for (const t of tokens) {
await t.document.update({ hidden: !t.document.hidden });
}
Want to move tokens somewhere when they step on a spot? You don't need a macro: give a Region the Teleport Token behavior (see below).
Hooks: Macros That React
A macro normally runs when you click it. Hooks let code run when something happens in the game. Hooks.on(name, fn) runs fn every time the event fires; Hooks.once runs it only the first time. Modules use the lifecycle hooks init, setup and ready, and canvasReady fires when a scene finishes drawing.
// Announce each new round in chat.
// A hook registered by a macro lasts until the page is reloaded.
Hooks.on("updateCombat", (combat, changed) => {
if (!game.user.isGM || !("round" in changed)) return;
ChatMessage.create({ content: `<h3>Round ${combat.round} begins!</h3>` });
});
ui.notifications.info("Round announcer is on until you reload.");
Where hooks belong: run from a macro, a hook disappears when anyone reloads, and running the macro twice registers it twice. For something you want every session, put it in a small module of your own, or use a Region behavior or Monk's Active Tile Triggers instead.
Region Behaviors: Automation on the Map
Scene Regions (V12 and later) are areas you draw with the Regions tool. Each Region can carry behaviors that fire on events, such as a token entering or leaving the area, or a turn starting while a token is inside. Two behaviors run your own code:
- Execute Macro runs a macro from your Macros directory when the event happens.
- Execute Script holds the script inside the behavior itself, so the trap and its code travel together.
Many jobs need no code: Teleport Token moves a token to another Region (even on another scene), Display Scrolling Text floats a message over the map, Pause Game stops play, Adjust Darkness Level darkens an area, Modify Movement Cost makes difficult terrain, and Toggle Behavior switches other behaviors on or off. V14 adds Change Level and Define Surface for Scene Levels.
Example: A Pit Trap with Execute Script
- Select the Regions tool and draw a rectangle over the trap's squares.
- Open the Region's configuration, add an Execute Script behavior, and choose the event for a token entering the Region.
- Paste a script like this one and save:
// Execute Script behavior. The script receives the event that fired it;
// for token events, event.data.token is the token that triggered it.
const token = event.data.token;
if (!token) return;
const roll = await new Roll("2d6").evaluate();
await roll.toMessage({ flavor: `${token.name} falls into a pit!` });
Test it with a player account as well as your own, because players and the GM can see different things.
Monk's Active Tile Triggers
Before Regions existed, the module Monk's Active Tile Triggers was the usual way to build traps and teleporters. It is verified for V14 and still worth a look if you want a long menu of ready-made actions (open doors, play sounds, change lighting, move tokens, run macros) triggered by tiles, without writing code. For simple teleporters and triggers, core Regions now cover a lot of what it used to do, with one fewer module to keep updated.
Module Automation: Supercharge Your Game
Modules add more automation. These are verified for V14 as of this writing; check each package page before you update.
Your game system may have its own automation add-ons too. For dnd5e, for example, Midi-QOL (verified for Version 14 in October 2026, usually paired with Dynamic Active Effects) automates attack, damage and save workflows, and Token Action HUD gives each token a clickable action bar. Automated Animations can then play an effect for each attack or spell. Check that any system add-on supports both your Foundry version and your system version before you install it.
Try It in Foundry
Starter Set
- Welcome Message: A chat macro that posts a dramatic welcome when the session starts
- Secret Check: A chat macro with
/gmroll 1d20and a flavor label - Torch Toggle: A script macro that gives the selected token light, or removes it
- Initiative Cleaner: Remove all NPCs from the combat tracker
- Inspiration Giver: Award inspiration with a fanfare message in chat
Scene Automation
- Day/Night Cycle: A macro that switches the darkness level between day and night
- Weather Picker: Extend the weather macro above with more choices
- Pit Trap: A Region with Execute Script that rolls damage when a token enters
- Secret Passage: Two Regions with Teleport Token that link a hidden door to the next room
- Round Announcer: The
updateCombathook from this page, with your own message
Bigger Projects
- Smart Healing: Heal the most injured PC first and announce it in chat
- Condition Manager: Keep a compendium of Active Effects with durations and drag them onto tokens
- Loot Roller: A macro that draws from a Roll Table and posts the result
- Scene Narrator: Post a description from a journal page when a token enters a Region
Debugging Your Macros
When macros go wrong (and they will!), here's how to fix them:
Common Issues and Solutions
- Nothing happens: Open the browser console (F12 in most browsers) and read the red error messages
- Wrong target: Make sure tokens are selected (
canvas.tokens.controlled) or targeted (game.user.targets), whichever the macro expects - Permissions: Some actions need GM privileges, and players need permission to use script macros
- Timing issues: Use
awaiton rolls, updates and dialogs - Old code: Macros copied from V11-era posts may use removed or deprecated calls such as
Dialogor the oldscene.darknessfield - Module conflicts: If a macro broke after an update, launch in Safe Configuration or use Find the Culprit! to see whether a module is involved
Debug Helper Macro
// Debug helper: shows useful info in the console
console.log("=== DEBUG INFO ===");
console.log("Selected Tokens:", canvas.tokens.controlled);
console.log("Targeted Tokens:", game.user.targets);
console.log("Active Scene:", canvas.scene?.name);
console.log("Current User:", game.user.name);
console.log("Combat Active:", game.combat?.active);
ui.notifications.info("Check the console for debug info (F12)");
Ideas from the Table
The One-Button Boss Fight
One macro can turn a throne room into a battle arena: it reveals hidden walls of guards, drops the darkness level, starts the boss music from a playlist, and posts a dramatic line to chat. Say "You shouldn't have insulted the king" and press the button.
The Living Street
Give each shop a Region. A macro that you run at nightfall hides the shopkeeper tokens, turns on the street lamps, and reveals the guard patrol. Your players will start planning heists around it.
The Personalized Hotbar
Give each player their own macros. The barbarian gets a "RAGE!" button that applies the rage effect and posts a battle cry; the wizard gets a chat macro that whispers her spell list reminder. Players love their own buttons.
Macro Best Practices
Writing Good Macros
- Comment your code: Future you will thank present you
- Check first: Make sure tokens, actors and a combat exist before using them
- User feedback: Use
ui.notificationsto confirm what happened - Keep it simple: Many simple macros beat one huge one
- Test thoroughly: Try edge cases, and a player account, before game time
Organizing Your Macros
- Naming convention: "GM - Combat - Roll Initiative"
- Images: Pick a consistent icon style per category so the hotbar is easy to scan
- Folders: Group related macros in the Macros directory
- Documentation: Keep a journal entry describing what each macro does
- Backup: Keep your best macros in a compendium pack so other worlds can use them, and take regular backups
Where to Go Next
Foundry can now do the busywork for you: chat and script macros on the hotbar, hooks that react to the game, and Regions that spring traps on their own. Every click saved is more time for epic moments.
- Audio & Music: soundscapes your macros can trigger.
- Modules & Add-ons: install and manage the modules mentioned here.
- Dice Formula Tester: check a roll formula before you put it in a macro.