Skip to content
Alex edited this page Oct 30, 2026 · 23 revisions

WebSocket API

The module connects to your server as a WebSocket client. Your server sends commands, Foundry executes them and returns results.

Connection

The module is the WebSocket client. It opens two outgoing connections, authenticated with the API key from the module settings, and keeps them open for as long as the world is open on the GM's client:

Channel Default URL Serves
MCP wss://foundry-mcp.com/ws AI assistants over MCP (https://foundry-mcp.com/mcp and the code-mode endpoint https://foundry-mcp.com/mcp/code)
API wss://api.foundry-mcp.com/v1/connect The public REST API at api.foundry-mcp.com
  • Protocol: JSON text frames, one message per frame.
  • Reconnect: the delay doubles from the configured base (5 s) up to a 60 s ceiling with up to 1 s of jitter. By default the module keeps trying while the world is open (Max Reconnect Attempts = 0).
  • Close codes (since 8.15.0): two application close codes from the server change that schedule. 4001 (reason Missing API key / Invalid API key) means the key was rejected after the upgrade: the module stops reconnecting, shows a permanent red notification and waits for the GM to fix the key and reload the world. 4002 (reason replaced by a newer connection with the same API key) means another Foundry client took over the key: the module waits 120 s (plus jitter) before reconnecting, without counting the wait as a backoff attempt, and shows a yellow notification. Every other code (1000, 1005, 1006, …) keeps the exponential backoff above. A server that closes without a code is treated like a network drop.
  • API key format: the module normalizes the key from the settings before connecting (all whitespace and invisible characters removed, anywhere in the string) and refuses to connect when the result does not look like a key: no pk_ prefix, characters outside A-Za-z0-9_-+/=, or a length outside 20..64. The server's 4001 remains the final check.
  • Heartbeat: the module sends { "type": "ping", "ts": <ms> } every 25 s (the first one a second after connecting) and expects { "type": "pong" }. Once a server has answered a ping, an unanswered one (10 s) closes the socket and the module reconnects at once. Ping and pong frames never reach the command router.
  • Legacy aliases: roll-ability, roll-skill, roll-save, roll-attack, roll-damage, roll-perception, use-item and activate-item are aliases of the dnd5e/* commands and require the D&D 5e system.

Message Format

Command (Server → Foundry)

interface Command {
  id: string;      // Unique identifier for correlation
  type: CommandType;
  params: object;
}

Response (Foundry → Server)

interface CommandResponse {
  id: string;      // Same as command ID
  success: boolean;
  data?: object;   // Result on success
  error?: string;  // Error message on failure
}

Commands Reference

Dice Rolling

roll-dice

Execute arbitrary dice formula. Formulas longer than 200 characters or rolling more than 1000 dice are rejected before evaluation (Roll formula asks for too many dice (N > 1000)).

Params:

interface RollDiceParams {
  formula: string;      // "1d20+5", "2d6", "4d6kh3"
  showInChat?: boolean; // Display in Foundry chat (default: false)
  flavor?: string;      // Chat message flavor text
}

Result:

interface RollResult {
  total: number;
  formula: string;
  dice: DiceResult[];
  isCritical?: boolean;
  isFumble?: boolean;
  mode?: 'advantage' | 'normal' | 'disadvantage';  // What dnd5e applied to a d20 roll (module 8.14+)
  kept?: number;                                    // The d20 result that stayed after kh/kl
}

interface DiceResult {
  type: string;     // "d20", "d6"
  count: number;
  results: number[];
}

Example:

// Command
{ "id": "1", "type": "roll-dice", "params": { "formula": "1d20+5", "showInChat": true } }

// Response
{ "id": "1", "success": true, "data": { "total": 18, "formula": "1d20+5", "dice": [{ "type": "d20", "count": 1, "results": [13] }] } }

dnd5e/roll-skill

Roll a D&D 5e skill check.

Naming (v8.6.0+): This is the canonical, system-namespaced command (<system>/<command>). The legacy roll-skill name still works as a deprecated alias that routes to the same handler, so existing integrations are unaffected — new integrations should prefer dnd5e/roll-skill.

Params:

interface RollSkillParams {
  actorId: string;
  skill: SkillKey;       // See skill keys table
  advantage?: boolean;     // Roll 2d20kh (module 8.13+)
  disadvantage?: boolean;  // Roll 2d20kl; both flags at once → validation error
  showInChat?: boolean;
}

Skill Keys:

Key Skill Key Skill
acr Acrobatics med Medicine
ani Animal Handling nat Nature
arc Arcana prc Perception
ath Athletics prf Performance
dec Deception per Persuasion
his History rel Religion
ins Insight slt Sleight of Hand
itm Intimidation ste Stealth
inv Investigation sur Survival

Example:

// Canonical name (v8.6.0+); the legacy "roll-skill" is still accepted as a deprecated alias
{ "id": "2", "type": "dnd5e/roll-skill", "params": { "actorId": "abc123", "skill": "ste" } }
{ "id": "3", "type": "dnd5e/roll-skill", "params": { "actorId": "abc123", "skill": "ste", "disadvantage": true } }

dnd5e/roll-save

Roll a D&D 5e saving throw.

Naming (v8.7.0+): Canonical, system-namespaced name. The legacy roll-save still works as a deprecated alias routing to the same handler.

Params:

interface RollSaveParams {
  actorId: string;
  ability: AbilityKey;   // "str", "dex", "con", "int", "wis", "cha"
  advantage?: boolean;     // Roll 2d20kh (module 8.13+)
  disadvantage?: boolean;  // Roll 2d20kl; both flags at once → validation error
  showInChat?: boolean;
}

dnd5e/roll-ability

Roll a D&D 5e ability check.

Naming (v8.7.0+): Canonical, system-namespaced name. The legacy roll-ability still works as a deprecated alias routing to the same handler.

Params:

interface RollAbilityParams {
  actorId: string;
  ability: AbilityKey;
  advantage?: boolean;     // Roll 2d20kh (module 8.13+)
  disadvantage?: boolean;  // Roll 2d20kl; both flags at once → validation error
  showInChat?: boolean;
}

dnd5e/roll-attack

Roll an attack with a weapon/spell.

Naming (v8.7.0+): Canonical, system-namespaced name. The legacy roll-attack still works as a deprecated alias routing to the same handler.

Params:

interface RollAttackParams {
  actorId: string;
  itemId: string;        // Weapon/spell item ID
  advantage?: boolean;
  disadvantage?: boolean;
  showInChat?: boolean;
}

With Midi-QOL active the flags are also passed as midiOptions.advantage/disadvantage and midiOptions.workflowOptions.advantage/disadvantage (module 8.14+): Midi rebuilds advantage from its own tracker, which only reads those. The result's mode and kept show what was applied.


dnd5e/roll-damage

Roll damage for a weapon/spell.

Naming (v8.7.0+): Canonical, system-namespaced name. The legacy roll-damage still works as a deprecated alias routing to the same handler.

Params:

interface RollDamageParams {
  actorId: string;
  itemId: string;
  critical?: boolean;    // Double dice
  showInChat?: boolean;
}

dnd5e/roll-perception

Roll the actor's Perception (prc) skill check. Returns the standard RollResult. The bare roll-perception is a legacy alias.

Params:

interface RollPerceptionParams {
  actorId: string;
  advantage?: boolean;     // Roll 2d20kh (module 8.13+)
  disadvantage?: boolean;  // Roll 2d20kl; both flags at once → validation error
  showInChat?: boolean;
}

Example:

{ "id": "1", "type": "dnd5e/roll-perception", "params": { "actorId": "abc123", "showInChat": true } }

World Info

dnd5e/filter-compendium-actors

Specification-based search over Actor compendium packs (bestiaries) with the filter-actors filter surface. D&D 5e worlds only (Operation … is not supported by game system '<world>' otherwise). World-only filters (folder, hasPlayerOwner, currentHp) are intentionally absent.

Params:

interface FilterCompendiumActorsParams {
  packIds?: string[];          // Explicit Actor packs (unknown/wrong-type pack → error); omitted → all Actor packs
  name?: string;               // Substring, case-insensitive
  type?: string[];             // "character" | "npc" | "vehicle" | "group"
  creatureType?: string[];     // 14 D&D creature types, e.g. ["undead"]
  size?: string[];             // "tiny" | "sm" | "med" | "lg" | "huge" | "grg"
  disposition?: string[];      // "hostile" | "neutral" | "friendly" | "secret"
  cr?: { min?: number; max?: number };       // Bounds must be legal CRs (0, 0.125, 0.25, 0.5, 1..30)
  level?: { min?: number; max?: number };    // Character levels
  maxHp?: { min?: number; max?: number };
  ac?: { min?: number; max?: number };
  abilities?: { str?: {min?: number; max?: number}; /* dex, con, int, wis, cha */ };
  limit?: number;              // 1..200, default 50
  offset?: number;
}

Result:

interface FilterCompendiumActorsResult {
  results: { id: string; name: string; packId: string; uuid: string }[];
  total: number;
  hasMore: boolean;
}

Example:

{ "id": "1", "type": "dnd5e/filter-compendium-actors",
  "params": { "creatureType": ["undead"], "cr": { "min": 5, "max": 10 } } }

dnd5e/filter-compendium-items

The filter-items surface over Item compendium packs: type (13 D&D item types), rarity, spellSchool, requiresAttunement, identified, hasActivities, isContainer, weight/price/spellLevel ranges, plus packIds/name/limit/offset as above (the world-only folder filter is absent). Result shape matches dnd5e/filter-compendium-actors — entries carry packId and uuid for follow-up get-compendium-document / import calls.

{ "id": "1", "type": "dnd5e/filter-compendium-items",
  "params": { "type": ["spell"], "spellLevel": { "min": 1, "max": 3 }, "spellSchool": ["evocation"] } }

get-world-info

Get information about the current Foundry world, system, version, entity counts, and available compendiums.

Params:

// No parameters required
interface GetWorldInfoParams {}

Result:

interface WorldInfoResult {
  world: WorldInfoData;
  counts: WorldCounts;
  compendiumMeta: CompendiumMetaSummary[];
}

interface WorldInfoData {
  id: string;
  title: string;
  system: string;            // e.g., "dnd5e"
  systemVersion: string;     // e.g., "4.3.0"
  foundryVersion: string;    // e.g., "13.331"
}

interface WorldCounts {
  journals: number;
  actors: number;
  items: number;
  scenes: number;
}

interface CompendiumMetaSummary {
  id: string;                // e.g., "dnd5e.monsters"
  label: string;             // e.g., "Monsters (SRD)"
  type: string;              // "Actor", "Item", "JournalEntry", etc.
  system: string;
  count: number;             // Number of entries in the compendium
}

Example:

// Command
{ "id": "1", "type": "get-world-info", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "world": {
      "id": "my-campaign",
      "title": "Curse of Strahd",
      "system": "dnd5e",
      "systemVersion": "4.3.0",
      "foundryVersion": "13.331"
    },
    "counts": { "journals": 42, "actors": 85, "items": 120, "scenes": 15 },
    "compendiumMeta": [
      { "id": "dnd5e.monsters", "label": "Monsters (SRD)", "type": "Actor", "system": "dnd5e", "count": 325 },
      { "id": "dnd5e.items", "label": "Items (SRD)", "type": "Item", "system": "dnd5e", "count": 680 }
    ]
  }
}

Actor Management

get-actors

Get a summary list of all actors in the world.

Params:

// No parameters required
interface GetActorsParams {}

Result:

type GetActorsResult = ActorSummary[];

interface ActorSummary {
  id: string;
  name: string;
  type: string;    // "character", "npc", "vehicle", etc.
  img: string;
}

Example:

// Command
{ "id": "1", "type": "get-actors", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    { "id": "actor-1", "name": "Aragorn", "type": "character", "img": "portraits/aragorn.png" },
    { "id": "actor-2", "name": "Goblin", "type": "npc", "img": "tokens/goblin.png" }
  ]
}

get-actor

Get detailed information about a single actor, including system data and items.

Params:

interface GetActorParams {
  actorId: string;
}

Result:

interface ActorDetailResult {
  id: string;
  name: string;
  type: string;
  img: string;
  system: Record<string, unknown>;  // Full system data (abilities, HP, AC, etc.)
  items: ItemSummary[];
}

interface ItemSummary {
  id: string;
  name: string;
  type: string;
  img: string;
  system: Record<string, unknown>;
}

Example:

// Command
{ "id": "1", "type": "get-actor", "params": { "actorId": "actor-1" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "actor-1",
    "name": "Aragorn",
    "type": "character",
    "img": "portraits/aragorn.png",
    "system": {
      "abilities": { "str": { "value": 18 }, "dex": { "value": 14 } },
      "attributes": { "hp": { "value": 85, "max": 85 }, "ac": { "value": 18 } }
    },
    "items": [
      { "id": "item-1", "name": "Longsword", "type": "weapon", "img": "icons/sword.png", "system": {} }
    ]
  }
}

filter-actors

Search for actors with structured D&D 5e-aware filters. All filters combined with AND, multiple values within array filters with OR. Returns minimal { id, name } summaries — use get-actor for details.

Params:

interface FilterActorsParams {
  // String filters
  name?: string;                          // substring, case-insensitive, trimmed
  type?: string[];                        // strict enum: 'character' | 'npc' | 'vehicle' | 'group'
  creatureType?: string[];                // 14 SRD types: 'aberration', 'beast', 'celestial', 'construct',
                                          // 'dragon', 'elemental', 'fey', 'fiend', 'giant', 'humanoid',
                                          // 'monstrosity', 'ooze', 'plant', 'undead'
  size?: string[];                        // strict codes: 'tiny' | 'sm' | 'med' | 'lg' | 'huge' | 'grg'
  disposition?: string[];                 // 'hostile' | 'neutral' | 'friendly' | 'secret'

  // Boolean
  hasPlayerOwner?: boolean;

  // Numeric range filters (inclusive bounds, both fields optional)
  cr?: { min?: number; max?: number };    // strict D&D values: 0, 0.125, 0.25, 0.5, 1, 2, ..., 30
  level?: { min?: number; max?: number }; // integer ≥ 0 (PC level)
  maxHp?: { min?: number; max?: number };
  currentHp?: { min?: number; max?: number };
  ac?: { min?: number; max?: number };
  abilities?: {
    str?: { min?: number; max?: number };
    dex?: { min?: number; max?: number };
    con?: { min?: number; max?: number };
    int?: { min?: number; max?: number };
    wis?: { min?: number; max?: number };
    cha?: { min?: number; max?: number };
  };

  // Folder
  folder?: {
    id?: string;
    name?: string;
    recursive?: boolean;     // default false
  };

  // Pagination
  limit?: number;            // 1-200, default 50
  offset?: number;           // ≥ 0, default 0
}

Result:

interface FilterActorsResult {
  results: Array<{ id: string; name: string }>;
  total: number;             // total count after filtering, before pagination
  hasMore: boolean;          // total > offset + results.length
}

Behavior notes:

  • All filters combine via AND. Within array filters, values combine via OR.
  • "Silent exclude": if an actor lacks the field being filtered (e.g. PC has no CR, vehicle has no abilities), it's excluded from results without error.
  • Sorting: name ASC, id as tiebreaker. Stable across pages.
  • No filters → returns full list paginated (use get-actors for the simpler full-list contract).
  • Strict validation: invalid CR values, out-of-range limits, unknown enum values produce errors with descriptive messages.
  • World actors only (compendium search is a separate concern).

Examples:

// Find CR 1/4 monsters in the "Goblins" folder
{
  "id": "1",
  "type": "filter-actors",
  "params": {
    "type": ["npc"],
    "cr": { "min": 0.25, "max": 0.25 },
    "folder": { "name": "Goblins", "recursive": true }
  }
}

// Find tough hostile dragons (CR 15+, AC 18+)
{
  "id": "2",
  "type": "filter-actors",
  "params": {
    "creatureType": ["dragon"],
    "disposition": ["hostile"],
    "cr": { "min": 15 },
    "ac": { "min": 18 }
  }
}

// Find PCs by name fragment
{
  "id": "3",
  "type": "filter-actors",
  "params": {
    "type": ["character"],
    "name": "fro"
  }
}

// Response (minimal entries — call get-actor for details)
{
  "id": "3",
  "success": true,
  "data": {
    "results": [
      { "id": "actor-frodo", "name": "Frodo Baggins" }
    ],
    "total": 1,
    "hasMore": false
  }
}

// Pagination through a large set
{
  "id": "4",
  "type": "filter-actors",
  "params": {
    "type": ["npc"],
    "limit": 50,
    "offset": 100
  }
}

create-actor

Create a new actor in the world.

Params:

interface CreateActorParams {
  name: string;
  type: string;           // "character", "npc", "vehicle", etc.
  folder?: string;        // Folder ID
  img?: string;           // Portrait image path
  system?: object;        // System-specific data (abilities, hp, etc.)
}

Result:

interface ActorResult {
  id: string;
  uuid: string;
  name: string;
  type: string;
  img: string;
  folder: string | null;
}

Example:

// Command - create NPC
{
  "id": "1",
  "type": "create-actor",
  "params": {
    "name": "Goblin Archer",
    "type": "npc",
    "system": {
      "abilities": { "str": { "value": 8 }, "dex": { "value": 14 } },
      "attributes": { "hp": { "value": 7, "max": 7 } }
    }
  }
}

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "actor-xyz",
    "uuid": "Actor.actor-xyz",
    "name": "Goblin Archer",
    "type": "npc",
    "img": "icons/svg/mystery-man.svg",
    "folder": null
  }
}

create-actor-from-compendium

Import actor from a compendium pack.

Params:

interface CreateActorFromCompendiumParams {
  packId: string;         // Compendium ID, e.g., "dnd5e.monsters"
  actorId: string;        // Actor ID within compendium
  name?: string;          // Override name
  folder?: string;        // Target folder ID
}

Result: ActorResult

Example:

// Command - import Goblin from SRD
{
  "id": "1",
  "type": "create-actor-from-compendium",
  "params": {
    "packId": "dnd5e.monsters",
    "actorId": "goblin-id-in-compendium",
    "name": "Sneaky Goblin"
  }
}

update-actor

Update actor properties.

Params:

interface UpdateActorParams {
  actorId?: string;       // World actor. Either this or tokenId is required.
  tokenId?: string;       // One token's actor instead (module 8.14+): the synthetic actor of an unlinked token
  sceneId?: string;       // Scene the token is on; defaults to the active scene
  name?: string;
  img?: string;
  folder?: string;
  system?: object;        // Partial update of system data
}

Result: ActorResult, with tokenId echoed when a token was addressed.

Example:

// Command - update HP
{
  "id": "1",
  "type": "update-actor",
  "params": {
    "actorId": "abc123",
    "system": {
      "attributes": { "hp": { "value": 25 } }
    }
  }
}

dnd5e/apply-damage

Apply damage through dnd5e's Actor5e#applyDamage (module 8.14+): temp HP absorbs it first, and resistances, immunities and vulnerabilities apply when a damage type is given. The bare apply-damage is an alias.

Params:

interface ApplyDamageParams {
  actorId?: string;       // World actor. Either this or tokenId is required.
  tokenId?: string;       // One token's actor: an unlinked token takes the hit alone
  sceneId?: string;       // Defaults to the active scene
  amount: number;         // Before resistances
  type?: string;          // dnd5e damage type: "slashing", "fire", …
}

Result:

interface HitPointsChangeResult {
  actorId: string;
  tokenId?: string;
  hpBefore: number;
  hpAfter: number;
  tempBefore: number;
  tempAfter: number;
  maxHp: number;
}

Example:

{ "id": "1", "type": "apply-damage", "params": { "tokenId": "goblin-3", "amount": 7, "type": "slashing" } }

dnd5e/apply-healing

Heal through Actor5e#applyDamage with the healing type (module 8.14+). The bare apply-healing is an alias.

Params: { actorId?: string, tokenId?: string, sceneId?: string, amount: number }

Result: HitPointsChangeResult (see dnd5e/apply-damage).


delete-actor

Delete actor from the world.

Params:

interface DeleteActorParams {
  actorId: string;
}

Result: DeleteResult


Chat Messages

send-chat-message

Send a message to Foundry VTT chat. Supports in-character, out-of-character, and emote styles. Can optionally whisper to specific users.

Params:

interface SendChatMessageParams {
  content: string;          // Message text (HTML supported)
  speaker?: string;         // Speaker alias override
  actorId?: string;         // Actor ID — sets speaker to actor's name
  flavor?: string;          // Flavor text shown above the message
  whisperTo?: string[];     // User IDs to whisper to
  type?: 'ic' | 'ooc' | 'emote';  // Message style (default: standard)
}

Result:

interface SendChatMessageResult {
  messageId: string;
  sent: boolean;
}

Example:

// Command - in-character speech
{
  "id": "1",
  "type": "send-chat-message",
  "params": {
    "content": "You shall not pass!",
    "actorId": "gandalf-actor-id",
    "type": "ic"
  }
}

// Response
{ "id": "1", "success": true, "data": { "messageId": "msg-001", "sent": true } }

// Command - whisper to player
{
  "id": "2",
  "type": "send-chat-message",
  "params": {
    "content": "You notice a hidden trap in the floor.",
    "whisperTo": ["player-user-id"],
    "flavor": "Perception DC 15"
  }
}

get-chat-messages

Retrieve chat messages with filtering and pagination. Returns messages from most recent, with configurable limit.

Params:

interface GetChatMessagesParams {
  limit?: number;           // Number of messages to return (default: 20, max: 100)
  since?: string;           // Message ID — return messages after this one
  before?: string;          // Message ID — return messages before this one
  includeRolls?: boolean;   // Include roll formula/total summaries (default: false)
  authorId?: string;        // Filter by author user ID
  actorId?: string;         // Filter by speaker actor ID
  type?: 'ic' | 'ooc' | 'emote' | 'roll';  // Filter by message style
  search?: string;          // Full-text search in message content
}

Result:

interface GetChatMessagesResult {
  messages: ChatMessageData[];
  total: number;            // Total messages in chat log
  hasMore: boolean;         // More messages available beyond limit
}

interface ChatMessageData {
  id: string;
  timestamp: number;
  author: {
    userId: string;
    name: string;
  };
  speaker: ChatMessageSpeaker;
  content: string;          // HTML stripped to plain text
  flavor: string | null;
  style: 'other' | 'ooc' | 'ic' | 'emote';
  isRoll: boolean;
  rolls?: ChatMessageRollSummary[];   // Only when includeRolls: true
  whisper: string[];
  isWhisper: boolean;
}

interface ChatMessageSpeaker {
  alias: string;
  actorId: string | null;
  tokenId: string | null;
}

interface ChatMessageRollSummary {
  formula: string;
  total: number;
}

Example:

// Command - get last 10 messages with rolls
{
  "id": "1",
  "type": "get-chat-messages",
  "params": { "limit": 10, "includeRolls": true }
}

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "messages": [
      {
        "id": "msg-100",
        "timestamp": 1713200000,
        "author": { "userId": "gm-user", "name": "Game Master" },
        "speaker": { "alias": "GM", "actorId": null, "tokenId": null },
        "content": "Roll for initiative!",
        "flavor": null,
        "style": "ooc",
        "isRoll": false,
        "whisper": [],
        "isWhisper": false
      }
    ],
    "total": 250,
    "hasMore": true
  }
}

// Command - search for messages mentioning "dragon"
{ "id": "2", "type": "get-chat-messages", "params": { "search": "dragon", "limit": 5 } }

update-chat-message

Update the content or flavor of an existing chat message.

Params:

interface UpdateChatMessageParams {
  messageId: string;
  content?: string;         // New message content
  flavor?: string;          // New flavor text
}

Result:

interface SendChatMessageResult {
  messageId: string;
  sent: boolean;
}

Example:

// Command
{ "id": "1", "type": "update-chat-message", "params": { "messageId": "msg-100", "content": "Updated text" } }

// Response
{ "id": "1", "success": true, "data": { "messageId": "msg-100", "sent": true } }

delete-chat-message

Delete a single chat message by ID.

Params:

interface DeleteChatMessageParams {
  messageId: string;
}

Result: DeleteResult

Example:

{ "id": "1", "type": "delete-chat-message", "params": { "messageId": "msg-100" } }
{ "id": "1", "success": true, "data": { "deleted": true } }

clear-chat

Delete all messages from the chat log.

Params:

// No parameters required

Result:

interface ClearChatResult {
  deletedCount: number;
}

Example:

// Command
{ "id": "1", "type": "clear-chat", "params": {} }

// Response
{ "id": "1", "success": true, "data": { "deletedCount": 142 } }

export-chat

Export the entire chat log as text or JSON.

Params:

interface ExportChatParams {
  format?: 'text' | 'json';  // Default: 'text'
}

Result:

interface ExportChatResult {
  content: string;           // Exported content (plain text or JSON string)
  messageCount: number;
}

Example:

// Command - export as text
{ "id": "1", "type": "export-chat", "params": { "format": "text" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "content": "[Game Master] Roll for initiative!\n[Aragorn] I attack the goblin!",
    "messageCount": 2
  }
}

// Command - export as JSON
{ "id": "2", "type": "export-chat", "params": { "format": "json" } }

Journal Management

get-journals

Get a list of all journal entries in the world. With light: true the result is an index without page text, which is what the server uses for lists and search.

Params:

interface GetJournalsParams {
  light?: boolean;   // Index only: pages carry id/name/type/src but no text, markdown or enrichedText,
                     // and enrichment is skipped — fast on large worlds (module 8.12+)
}

Result:

type GetJournalsResult = JournalData[];

interface JournalData {
  id: string;
  uuid: string;
  name: string;
  folder: string | null;
  pages: JournalPageData[];
}

interface JournalPageData {
  id: string;
  name: string;
  type: string;           // "text", "image", "video", "pdf"
  text: string | null;
  markdown: string | null;
  enrichedText: string | null;  // Foundry-enriched HTML (@UUID links resolved, inline rolls, etc.)
  src: string | null;           // Source URL for image/video/pdf pages
}

Example:

// Command
{ "id": "1", "type": "get-journals", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    {
      "id": "journal-1",
      "uuid": "JournalEntry.journal-1",
      "name": "Session Notes",
      "folder": null,
      "pages": [
        { "id": "page-1", "name": "Session 1", "type": "text", "text": "<p>The party met at the tavern...</p>", "markdown": null }
      ]
    }
  ]
}

get-journal

Get a single journal entry with full page content.

Params:

interface GetJournalParams {
  journalId: string;
}

Result: JournalData

Example:

{ "id": "1", "type": "get-journal", "params": { "journalId": "journal-1" } }

create-journal

Create new journal entry.

Params:

interface CreateJournalParams {
  name: string;
  folder?: string;              // Folder ID
  content?: string;             // HTML content for first page
  pageType?: "text" | "image" | "video" | "pdf";
}

Result:

interface JournalResult {
  id: string;
  name: string;
  folder: string | null;
  pages: JournalPageResult[];
}

update-journal

Update journal properties.

Params:

interface UpdateJournalParams {
  journalId: string;
  name?: string;
  folder?: string;
}

delete-journal

Delete journal entry.

Params:

interface DeleteJournalParams {
  journalId: string;
}

Result:

interface DeleteResult {
  deleted: boolean;
}

create-journal-page

Add page to existing journal.

Params:

interface CreateJournalPageParams {
  journalId: string;
  name: string;
  type?: "text" | "image" | "video" | "pdf";
  content?: string;              // HTML for text pages
  src?: string;                  // Source URL for image/video/pdf pages
}

update-journal-page

Update page content.

Params:

interface UpdateJournalPageParams {
  journalId: string;
  pageId: string;
  name?: string;
  content?: string;
  src?: string;                  // Source URL for image/video/pdf pages
}

delete-journal-page

Remove page from journal.

Params:

interface DeleteJournalPageParams {
  journalId: string;
  pageId: string;
}

show-journal

Show a journal entry or specific page to players. Pushes the journal to their screens.

Params:

interface ShowJournalParams {
  journalId: string;
  pageId?: string;         // Show specific page (default: entire journal)
  force?: boolean;         // Override permissions (default: false)
  users?: string[];        // Specific user IDs (default: all players)
}

Result:

interface ShowJournalResult {
  shown: boolean;
  journalId: string;
  journalName: string;
  pageId?: string;
}

Example:

// Command - show journal to all players
{ "id": "1", "type": "show-journal", "params": { "journalId": "journal-quest" } }

// Response
{ "id": "1", "success": true, "data": { "shown": true, "journalId": "journal-quest", "journalName": "Quest Log" } }

// Command - show specific page, force override permissions
{
  "id": "2",
  "type": "show-journal",
  "params": {
    "journalId": "journal-quest",
    "pageId": "page-handout",
    "force": true
  }
}

// Command - show to specific player
{
  "id": "3",
  "type": "show-journal",
  "params": {
    "journalId": "journal-secret",
    "users": ["player-rogue-id"]
  }
}

Combat Tracker

create-combat

Create new combat encounter.

Params:

interface CreateCombatParams {
  sceneId?: string;    // Defaults to active scene
  activate?: boolean;  // Make active combat
}

Result:

interface CombatResult {
  id: string;
  round: number;
  turn: number;
  started: boolean;
  combatants: CombatantResult[];
  current: CombatantResult | null;
}

interface CombatantResult {
  id: string;
  actorId: string;
  tokenId: string | null;
  name: string;
  img: string;
  initiative: number | null;
  defeated: boolean;
  hidden: boolean;
}

add-combatant

Add actor to combat.

Params:

interface AddCombatantParams {
  actorId: string;
  combatId?: string;      // Defaults to active combat
  tokenId?: string;
  initiative?: number;    // Pre-set initiative
  hidden?: boolean;
}

remove-combatant

Remove from combat.

Params:

interface RemoveCombatantParams {
  combatantId: string;
  combatId?: string;
}

start-combat

Begin combat (sets round to 1).

Params:

interface CombatIdParams {
  combatId?: string;
}

end-combat

End and delete combat. Shows confirmation dialog in Foundry VTT v13.

Params: CombatIdParams


delete-combat

Delete combat directly without confirmation dialog. Useful for programmatic combat management, especially in Foundry VTT v13 where end-combat shows a confirmation dialog.

Params: CombatIdParams

Result:

interface DeleteResult {
  deleted: boolean;
}

Example:

// Command
{ "id": "1", "type": "delete-combat", "params": {} }

// Response
{ "id": "1", "success": true, "data": { "deleted": true } }

// With specific combat ID
{ "id": "2", "type": "delete-combat", "params": { "combatId": "abc123" } }

next-turn / previous-turn

Navigate turn order.

Params: CombatIdParams


set-turn

Set the current turn to a specific combatant.

Params:

interface SetTurnParams {
  combatantId: string;    // Combatant to set as current turn
  combatId?: string;
}

Result: CombatResult

Example:

// Command
{ "id": "1", "type": "set-turn", "params": { "combatantId": "combatant-xyz" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "combat-1",
    "round": 2,
    "turn": 3,
    "started": true,
    "combatants": [],
    "current": { "id": "combatant-xyz", "actorId": "actor-1", "tokenId": "token-1", "name": "Goblin", "img": "tokens/goblin.png", "initiative": 15, "defeated": false, "hidden": false }
  }
}

get-combat-state

Get current combat state.

Params: CombatIdParams

Result: CombatResult


get-combat-turn-context

Get rich contextual information about the current combat turn, including the active combatant's stats, nearby tokens with distances, line-of-sight data, and an ASCII map of the battlefield zoomed around the current combatant.

Params:

interface GetCombatTurnContextParams {
  combatId?: string;       // Defaults to active combat
}

Result:

interface CombatTurnContext {
  round: number;
  turn: number;
  currentCombatant: TurnCombatantInfo;
  nearbyTokens: NearbyTokenInfo[];
  asciiMap: string;        // ASCII art map of the battlefield (zoomed to 12-grid radius)
}

interface TurnCombatantInfo {
  id: string;              // Combatant ID
  actorId: string;
  tokenId: string;
  name: string;
  gridX: number;           // Grid coordinates
  gridY: number;
  hp?: { value: number; max: number };
  ac?: number;
  conditions: string[];    // Active status effect IDs
}

interface NearbyTokenInfo {
  tokenId: string;
  actorId: string | null;
  name: string;
  gridX: number;
  gridY: number;
  distanceFt: number;      // Distance in feet (Chebyshev / grid distance)
  disposition: string;     // "hostile", "neutral", "friendly"
  hp?: { value: number; max: number };
  ac?: number;
  conditions: string[];
  lineOfSight: boolean;    // Whether the current combatant can see this token
}

Example:

// Command
{ "id": "1", "type": "get-combat-turn-context", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "round": 2,
    "turn": 0,
    "currentCombatant": {
      "id": "combatant-1",
      "actorId": "actor-fighter",
      "tokenId": "token-fighter",
      "name": "Fighter",
      "gridX": 5,
      "gridY": 5,
      "hp": { "value": 45, "max": 52 },
      "ac": 18,
      "conditions": []
    },
    "nearbyTokens": [
      {
        "tokenId": "token-goblin-1",
        "actorId": "actor-goblin",
        "name": "Goblin",
        "gridX": 6,
        "gridY": 5,
        "distanceFt": 5,
        "disposition": "hostile",
        "hp": { "value": 5, "max": 7 },
        "ac": 15,
        "conditions": [],
        "lineOfSight": true
      }
    ],
    "asciiMap": "... (ASCII representation of battlefield) ..."
  }
}

Initiative

roll-initiative

Roll initiative for specific combatants.

Params:

interface RollInitiativeParams {
  combatantIds: string[];
  combatId?: string;
  formula?: string;      // Custom formula, uses system default if omitted
}

Result:

interface InitiativeRollResult {
  results: Array<{
    combatantId: string;
    name: string;
    initiative: number;
  }>;
}

set-initiative

Manually set initiative value.

Params:

interface SetInitiativeParams {
  combatantId: string;
  initiative: number;
  combatId?: string;
}

roll-all-initiative

Roll initiative for all combatants.

Params:

interface RollAllInitiativeParams {
  combatId?: string;
  formula?: string;
  npcsOnly?: boolean;   // Roll only for NPCs
}

Combatant Updates

update-combatant

Update combatant properties.

Params:

interface UpdateCombatantParams {
  combatantId: string;
  combatId?: string;
  initiative?: number;
  defeated?: boolean;
  hidden?: boolean;
}

set-combatant-defeated

Mark combatant as defeated.

Params:

interface SetCombatantDefeatedParams {
  combatantId: string;
  defeated: boolean;
  combatId?: string;
}

toggle-combatant-visibility

Toggle hidden status.

Params:

interface ToggleCombatantVisibilityParams {
  combatantId: string;
  combatId?: string;
}

Token Management

create-token

Place token on scene.

Params:

interface CreateTokenParams {
  actorId: string;
  x: number;
  y: number;
  sceneId?: string;      // Defaults to active scene
  hidden?: boolean;
  elevation?: number;
  rotation?: number;     // Degrees
  scale?: number;
  actorLink?: boolean;   // Linked to the actor (shared HP/effects) or an independent copy (module 8.14+)
}

Result:

interface TokenResult {
  id: string;
  name: string;
  actorId: string | null;
  x: number;
  y: number;
  elevation: number;
  rotation: number;
  hidden: boolean;
  img: string;
  disposition: number;   // -1: hostile, 0: neutral, 1: friendly
  actorLink: boolean;    // Shares its actor's data (linked) or is an independent copy (module 8.14+)
  hp?: { value: number; max: number };
  ac?: number;
  conditions: string[];
  warning?: string;      // "unlinked_character_token": a player character placed as an unlinked copy
}

delete-token

Remove token from scene.

Params:

interface DeleteTokenParams {
  tokenId: string;
  sceneId?: string;
}

move-token

Move token to new position. Uses A* pathfinding to navigate around walls and optionally through doors.

Params:

interface MoveTokenParams {
  tokenId: string;
  x: number;
  y: number;
  sceneId?: string;
  elevation?: number;
  rotation?: number;
  animate?: boolean;       // Default: true
  canOpenDoors?: boolean;  // Allow opening doors along path
}

Result:

interface TokenResult {
  id: string;
  name: string;
  actorId: string | null;
  x: number;
  y: number;
  width: number;
  height: number;
  elevation: number;
  rotation: number;
  hidden: boolean;
  img: string;
  disposition: number;
  actorLink: boolean;       // Linked to its actor or an independent copy (module 8.14+)
  hp?: { value: number; max: number };
  ac?: number;
  conditions: string[];
  pathCost?: number;        // Movement cost in grid units (when pathfinding used)
  doorsOpened?: string[];   // Wall IDs of doors opened during movement
  warning?: string;         // "unlinked_character_token"
}

update-token

Update token properties.

Params:

interface UpdateTokenParams {
  tokenId: string;
  sceneId?: string;
  hidden?: boolean;
  elevation?: number;
  rotation?: number;
  scale?: number;
  name?: string;
  displayName?: number;
  disposition?: number;  // -1, 0, 1
  lockRotation?: boolean;
  actorLink?: boolean;   // Link or unlink the token from its actor (module 8.14+)
}

get-scene-tokens

Get all tokens from scene.

Params:

interface GetSceneTokensParams {
  sceneId?: string;      // Defaults to active scene
}

Result:

interface SceneTokensResult {
  sceneId: string;
  sceneName: string;
  tokens: TokenResult[];
}

get-token

Get one token on a scene with its actor link, position, HP and AC.

Params:

interface GetTokenParams {
  sceneId?: string;   // Defaults to the active scene
  tokenId: string;
}

Result:

interface TokenDetail {
  id: string;
  sceneId: string;
  name: string;
  x: number;
  y: number;
  width: number;
  height: number;
  elevation: number;
  rotation: number;
  hidden: boolean;
  disposition: 'hostile' | 'neutral' | 'friendly' | 'secret';
  actorId: string | null;
  actorLink: boolean;
  textureSrc: string;
  hp: { current: number; max: number } | null;
  ac: number | null;
}

get-token-by-actor

Find the token that represents an actor on a scene.

Params:

interface GetTokenByActorParams {
  sceneId?: string;   // Defaults to the active scene
  actorId: string;
}

Result: TokenDetail (see get-token).


get-tokens-in-range

List the tokens within a distance of a point, nearest first. The origin is in pixels; range and distance are in the scene's grid units. Distance uses Chebyshev geometry (a diagonal step counts as one grid space), measured from the origin to the nearest edge of each token.

Params:

interface GetTokensInRangeParams {
  sceneId?: string;        // Defaults to the active scene
  originX: number;         // Pixels
  originY: number;
  range: number;           // Grid units, e.g. feet
  excludeTokenId?: string; // Leave out the origin token itself
}

Result:

interface GetTokensInRangeResult {
  sceneId: string;
  origin: { x: number; y: number };
  range: number;
  units: string;           // Scene grid units, e.g. "ft"
  tokens: Array<{
    id: string;
    name: string;
    x: number;
    y: number;
    distance: number;
    actorId: string | null;
    disposition: 'hostile' | 'neutral' | 'friendly' | 'secret';
  }>;
}

set-token-target

Target or untarget a token on the active scene for the GM user.

Params:

interface SetTokenTargetParams {
  tokenId: string;
  targeted: boolean;
  releaseOthers?: boolean; // Drop the existing targets first (default true)
}

Result:

interface SetTokenTargetResult {
  tokenId: string;
  targeted: boolean;
}

clear-targets

Release every token the GM user is targeting.

Params: none ({}).

Result:

interface ClearTargetsResult {
  cleared: true;
  count: number;   // Targets released
}

Scene Management

get-scene

Get detailed information about a scene, including grid, darkness, notes, walls, lights, tiles, drawings, regions, and tokens with grid coordinates.

Params:

interface GetSceneParams {
  sceneId?: string;          // Defaults to active scene
  includeScreenshot?: boolean; // Include base64 screenshot of the scene
}

Result:

interface SceneDetailResult {
  id: string;
  name: string;
  active: boolean;
  img: string;
  width: number;
  height: number;
  grid: {
    size: number;        // Grid size in pixels (default: 100)
    type: number;        // 1 = square, 2 = hex rows, 3 = hex columns
    units: string;       // "ft", "m", etc.
    distance: number;    // Distance per grid square (e.g., 5)
  };
  darkness: number;      // 0 (bright) to 1 (fully dark)
  notes: SceneNote[];
  walls: SceneWall[];
  lights: SceneLight[];
  tiles: SceneTile[];
  drawings: SceneDrawing[];
  regions: SceneRegion[];
  tokens: SceneTokenSummary[];
  asciiMap: string;      // ASCII art representation of the scene
  screenshot?: SceneScreenshot;  // Only when includeScreenshot: true
}

interface SceneScreenshot {
  image: string;         // Base64-encoded image data
  mimeType: string;      // e.g., "image/webp"
  width: number;
  height: number;
}

interface SceneNote {
  x: number;
  y: number;
  text: string;
  label: string;
  entryId: string | null;
}

interface SceneWall {
  id: string;
  c: number[];           // [x1, y1, x2, y2]
  move: number;          // 0 = none, 20 = blocks movement
  sense: number;         // 0 = none, 20 = blocks vision
  door: number;          // 0 = wall, 1 = door, 2 = secret door
  ds: number;            // Door state: 0 = closed, 1 = open, 2 = locked
}

interface SceneLight {
  x: number;
  y: number;
  bright: number;        // Bright light radius in grid units
  dim: number;           // Dim light radius in grid units
  color: string | null;  // e.g., "#ff9900", null = white
  angle: number;         // Emission angle (360 = omnidirectional)
  walls: boolean;        // Blocked by walls
  hidden: boolean;
}

interface SceneTile {
  x: number;
  y: number;
  width: number;
  height: number;
  img: string;           // Image file path
  hidden: boolean;
  elevation: number;
  rotation: number;      // Degrees
}

interface SceneDrawing {
  x: number;
  y: number;
  shape: {
    type: string;        // "r" = rectangle, "e" = ellipse, "p" = polygon, "f" = freehand
    width: number;
    height: number;
    points: number[];    // Vertex coordinates [x1,y1,x2,y2,...] for polygon/freehand
  };
  text: string;
  hidden: boolean;
  fillColor: string | null;
  strokeColor: string | null;
}

interface SceneRegion {
  id: string;
  name: string;          // e.g., "Throne Room", "Trap Zone"
  color: string | null;
  shapes: Array<{ type: string }>;
}

interface SceneTokenSummary {
  id: string;
  name: string;
  actorId: string | null;
  gridX: number;         // Grid position (Math.floor(x / gridSize))
  gridY: number;
  x: number;             // Original pixel position
  y: number;
  elevation: number;
  hidden: boolean;
  disposition: number;   // -1: hostile, 0: neutral, 1: friendly
  hp?: { value: number; max: number };
  ac?: number;
  conditions: string[];
}

Example:

// Command - get active scene
{ "id": "1", "type": "get-scene", "params": {} }

// Command - get specific scene
{ "id": "2", "type": "get-scene", "params": { "sceneId": "scene-abc" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "scene-abc",
    "name": "Tavern - Ground Floor",
    "active": true,
    "img": "scenes/tavern.jpg",
    "width": 4000,
    "height": 3000,
    "grid": { "size": 100, "type": 1, "units": "ft", "distance": 5 },
    "darkness": 0.3,
    "notes": [
      { "x": 500, "y": 300, "text": "Bar counter", "label": "Bar", "entryId": null },
      { "x": 1200, "y": 800, "text": "Secret trapdoor", "label": "Trapdoor", "entryId": "journal-cellar" }
    ],
    "walls": [
      { "id": "wall-1", "c": [0, 0, 4000, 0], "move": 20, "sense": 20, "door": 0, "ds": 0 },
      { "id": "wall-2", "c": [800, 500, 800, 700], "move": 20, "sense": 20, "door": 1, "ds": 0 }
    ],
    "lights": [
      { "x": 500, "y": 500, "bright": 20, "dim": 40, "color": "#ff9900", "angle": 360, "walls": true, "hidden": false }
    ],
    "tiles": [
      { "x": 200, "y": 300, "width": 100, "height": 100, "img": "tiles/table.png", "hidden": false, "elevation": 0, "rotation": 0 }
    ],
    "drawings": [
      { "x": 400, "y": 400, "shape": { "type": "r", "width": 200, "height": 100, "points": [] }, "text": "", "hidden": false, "fillColor": "#00ff00", "strokeColor": "#000000" }
    ],
    "regions": [
      { "id": "region1", "name": "Trap Zone", "color": "#ff0000", "shapes": [{ "type": "rectangle" }] }
    ],
    "tokens": [
      { "id": "token-1", "name": "Goblin", "actorId": "actor-1", "gridX": 2, "gridY": 3, "x": 250, "y": 350, "elevation": 0, "hidden": false, "disposition": -1 },
      { "id": "token-2", "name": "Fighter", "actorId": "actor-2", "gridX": 5, "gridY": 5, "x": 500, "y": 500, "elevation": 0, "hidden": false, "disposition": 1 }
    ],
    "asciiMap": "... (ASCII map) ..."
  }
}

get-scenes-list

Get a summary list of all scenes in the world.

Params:

interface GetScenesListParams {
  // No parameters
}

Result:

interface SceneListResult {
  scenes: SceneSummary[];
}

interface SceneSummary {
  id: string;
  name: string;
  active: boolean;
  img: string;
}

Example:

// Command
{ "id": "1", "type": "get-scenes-list", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "scenes": [
      { "id": "scene-1", "name": "Tavern - Ground Floor", "active": true, "img": "scenes/tavern.jpg" },
      { "id": "scene-2", "name": "Dark Forest", "active": false, "img": "scenes/forest.jpg" },
      { "id": "scene-3", "name": "Dragon's Lair", "active": false, "img": "scenes/lair.jpg" }
    ]
  }
}

activate-scene

Switch the active scene. All players will be moved to this scene.

Params:

interface ActivateSceneParams {
  sceneId: string;       // Required - scene ID to activate
}

Result:

interface ActivateSceneResult {
  id: string;
  name: string;
  active: boolean;       // Always true after activation
}

Example:

// Command
{ "id": "1", "type": "activate-scene", "params": { "sceneId": "scene-2" } }

// Response
{ "id": "1", "success": true, "data": { "id": "scene-2", "name": "Dark Forest", "active": true } }

capture-scene

Capture a screenshot of the current active scene canvas, including a grid overlay. Returns a base64-encoded WebP image.

Params:

// No parameters required
interface CaptureSceneParams {}

Result:

interface CaptureSceneResult {
  sceneId: string;
  sceneName: string;
  image: string;         // Base64-encoded image data (no data URI prefix)
  mimeType: string;      // "image/webp"
  width: number;         // Canvas width in pixels
  height: number;        // Canvas height in pixels
}

Example:

// Command
{ "id": "1", "type": "capture-scene", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "sceneId": "scene-abc",
    "sceneName": "Tavern - Ground Floor",
    "image": "UklGRh4AAABXRUJQVlA4IBIAAAAwAQCdASoBAAEAAQ...",
    "mimeType": "image/webp",
    "width": 1920,
    "height": 1080
  }
}

create-scene

Create a scene.

Params:

interface CreateSceneParams {
  name: string;
  width?: number;
  height?: number;
  grid?: {
    type?: 'gridless' | 'square' | 'hexPointyOdd' | 'hexPointyEven' | 'hexFlatOdd' | 'hexFlatEven';
    size?: number;       // Pixels per grid space
    distance?: number;   // Units per grid space
    units?: string;      // e.g. "ft"
  };
  background?: string;   // Image path or URL
  foreground?: string;
  padding?: number;
  navigation?: boolean;  // Show in the scene navigation bar
  navName?: string;
  navOrder?: number;
  fogExploration?: boolean;
  darkness?: number;     // 0-1
  folder?: string;       // Folder ID
}

Result:

interface SceneSummary {
  id: string;
  uuid: string;
  name: string;
  active: boolean;
  width: number;
  height: number;
  background: string | null;
  navigation: boolean;
  navName: string | null;
  navOrder: number;
  folder: string | null;
  grid: { type: SceneGridType; size: number; distance: number; units: string };
}

update-scene

Update a scene. Same fields as create-scene, all optional, plus sceneId. navName: null clears the navigation name and folder: null moves the scene to the root.

Params:

interface UpdateSceneParams {
  sceneId: string;
  name?: string;
  width?: number;
  height?: number;
  grid?: SceneGridConfig;
  background?: string;
  foreground?: string;
  padding?: number;
  navigation?: boolean;
  navName?: string | null;
  navOrder?: number;
  fogExploration?: boolean;
  darkness?: number;
  folder?: string | null;
}

Result: SceneSummary (see create-scene).


delete-scene

Params: { sceneId: string }

Result: { deleted: true, sceneId: string }


clone-scene

Duplicate a scene with everything placed on it.

Params:

interface CloneSceneParams {
  sourceId: string;
  name?: string;     // Name of the copy
  folder?: string;   // Folder ID
}

Result: SceneSummary of the new scene.


view-scene

Show a scene on the GM's canvas without activating it for players (compare activate-scene).

Params: { sceneId: string }

Result: { viewed: true, sceneId: string }


Item Management

get-items

Get a list of all world-level items (not in actors' inventories).

Params:

// No parameters required
interface GetItemsParams {}

Result:

type GetItemsResult = ItemData[];

interface ItemData {
  id: string;
  uuid?: string;
  name: string;
  type: string;
  img: string;
  folder?: string | null;
  system: Record<string, unknown>;
}

Example:

// Command
{ "id": "1", "type": "get-items", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    { "id": "item-1", "name": "Healing Potion", "type": "consumable", "img": "icons/potions/healing.png", "system": { "quantity": 5 } },
    { "id": "item-2", "name": "Longsword +1", "type": "weapon", "img": "icons/weapons/sword.png", "system": {} }
  ]
}

get-item

Get detailed information about a single world-level item.

Params:

interface GetItemParams {
  itemId: string;
}

Result: ItemData

Example:

{ "id": "1", "type": "get-item", "params": { "itemId": "item-1" } }

filter-items

Search world-level items with structured D&D 5e-aware filters. All filters combine with AND; values inside an array filter combine with OR. Returns minimal { id, name } summaries — use get-item for details. An item that lacks a filtered field is silently excluded.

Params:

interface FilterItemsParams {
  name?: string;                 // substring, case-insensitive
  type?: string[];               // 'weapon' | 'equipment' | 'consumable' | 'tool' | 'container' | 'loot' | 'spell' |
                                 // 'feat' | 'background' | 'race' | 'class' | 'subclass' | 'feature'
  rarity?: string[];             // 'common' | 'uncommon' | 'rare' | 'veryRare' | 'legendary' | 'artifact'
  spellSchool?: string[];        // 'abjuration' | 'conjuration' | 'divination' | 'enchantment' | 'evocation' |
                                 // 'illusion' | 'necromancy' | 'transmutation'
  requiresAttunement?: boolean;
  identified?: boolean;
  hasActivities?: boolean;
  isContainer?: boolean;
  weight?: { min?: number; max?: number };
  price?: { min?: number; max?: number };
  spellLevel?: { min?: number; max?: number };
  folder?: { id?: string; name?: string; recursive?: boolean };
  limit?: number;                // 1-200, default 50
  offset?: number;               // ≥ 0, default 0
}

Result:

interface FilterItemsResult {
  results: Array<{ id: string; name: string }>;
  total: number;             // count after filtering, before pagination
  hasMore: boolean;
}

Example:

{ "id": "1", "type": "filter-items", "params": { "type": ["spell"], "spellLevel": { "min": 1, "max": 3 }, "spellSchool": ["evocation"] } }

create-item

Create a world-level item (not in an actor's inventory).

Params:

interface CreateItemParams {
  name: string;
  type: string;                     // System item type, e.g. "weapon", "spell"
  folder?: string;                  // Folder ID
  img?: string;
  system?: Record<string, unknown>; // System-specific data
}

Result:

interface WorldItemResult {
  id: string;
  uuid: string;
  name: string;
  type: string;
  img: string;
  folder: string | null;
}

create-item-from-compendium

Import an item from a compendium pack into the world (not onto an actor; see add-item-from-compendium for that).

Params:

interface CreateItemFromCompendiumParams {
  packId: string;     // e.g. "dnd5e.items"
  itemId: string;     // Document ID inside the pack
  name?: string;      // Rename the imported copy
  folder?: string;    // Folder ID
}

Result: WorldItemResult (see create-item). Errors: Compendium pack not found: <packId>, Item not found in compendium: <itemId>.


update-item

Update a world-level item.

Params:

interface UpdateItemParams {
  itemId: string;
  name?: string;
  img?: string;
  folder?: string | null;           // null = move to root
  system?: Record<string, unknown>;
}

Result: WorldItemResult (see create-item).


delete-item

Delete a world-level item.

Params:

interface DeleteItemParams {
  itemId: string;
}

Result:

interface DeleteItemResult {
  deleted: true;
  itemId: string;
}

get-actor-items

Get items from actor's inventory with optional filtering.

Params:

interface GetActorItemsParams {
  actorId: string;
  type?: string;           // Filter by item type (weapon, spell, consumable, etc.)
  equipped?: boolean;      // Filter by equipped status
  hasActivities?: boolean; // Filter items with activities (D&D 5e 2024)
}

Result:

interface ActorItemsResult {
  actorId: string;
  actorName: string;
  items: ItemDetailSummary[];
}

interface ItemDetailSummary {
  id: string;
  name: string;
  type: string;
  img: string;
  equipped: boolean;
  quantity: number;
  hasActivities: boolean;
  activityTypes: string[];  // e.g., ["attack", "damage", "save"]
  description: string;
  damage: object | null;
  range: object | null;
}

Example:

// Command - get all weapons
{ "id": "1", "type": "get-actor-items", "params": { "actorId": "abc123", "type": "weapon" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "actorId": "abc123",
    "actorName": "Aragorn",
    "items": [
      {
        "id": "item-456",
        "name": "Longsword",
        "type": "weapon",
        "img": "icons/weapons/swords/sword.png",
        "equipped": true,
        "quantity": 1,
        "hasActivities": true,
        "activityTypes": ["attack", "damage"],
        "description": "A fine longsword",
        "damage": {},
        "range": {}
      }
    ]
  }
}

dnd5e/use-item

Use/activate an item (cast spell, use consumable, attack with weapon).

Naming (v8.7.0+): Canonical, system-namespaced name. The legacy use-item still works as a deprecated alias routing to the same handler.

Params:

interface UseItemParams {
  actorId: string;
  itemId: string;
  activityId?: string;     // Specific activity ID (D&D 5e 2024)
  activityType?: string;   // Activity type filter (attack, damage, save, heal)
  consume?: boolean;       // Consume resources (default: true)
  scaling?: number;        // Spell slot level for scaling
  showInChat?: boolean;    // Show in chat (default: true)
}

Result:

interface UseItemResult {
  itemId: string;
  itemName: string;
  itemType: string;
  activityUsed?: ActivityInfo;
  rolls: RollResult[];
  chatMessageId?: string;
  status?: ActivationStatus;            // Only for a self-targeted heal (see below)
  dialog?: UserDialogKind;
  warning?: string;                     // "no_roll_performed": the card was posted but nothing was rolled
  appliedHealing?: AppliedHitPoints;    // HP before/after per token, when the module or Midi-QOL applied it
}

interface ActivityInfo {
  id: string;
  name: string;
  type: string;
}

A self-targeted heal activity (Second Wind, Lay on Hands on yourself) runs through the dnd5e/activate-item pipeline instead (module 8.14+): the healing roll is made and applied — by Midi-QOL, or by the module without it — and the answer carries status, appliedHealing and, when nothing was rolled, warning: "no_roll_performed" rather than a silent success.

Example:

// Command - cast Fireball at 4th level
{
  "id": "1",
  "type": "use-item",
  "params": {
    "actorId": "abc123",
    "itemId": "spell-fireball",
    "scaling": 4,
    "showInChat": true
  }
}

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "itemId": "spell-fireball",
    "itemName": "Fireball",
    "itemType": "spell",
    "activityUsed": { "id": "act-1", "name": "Cast", "type": "save" },
    "rolls": [{ "total": 32, "formula": "9d6", "dice": [{ "type": "d6", "count": 9, "results": [4,5,3,6,2,4,3,3,2] }] }],
    "chatMessageId": "msg-xyz"
  }
}

dnd5e/activate-item

Naming (v8.7.0+): Canonical, system-namespaced name. The legacy activate-item still works as a deprecated alias routing to the same handler.

Activate an item natively, triggering the full Foundry automation pipeline. Unlike use-item, this command does not suppress hooks or dialogs, allowing automation modules like Midi-QOL to intercept the call and execute the full combat sequence (attack roll -> AC check -> damage -> HP reduction -> effects).

Supports programmatic targeting -- set targetTokenIds so automation modules know which tokens to resolve attacks against.

Params:

interface ActivateItemParams {
  actorId: string;           // ID of the actor using the item
  itemId: string;            // ID of the item in the actor's inventory
  activityId?: string;       // Specific activity ID (D&D 5e 2024 activities system)
  activityType?: string;     // Activity type filter: "attack", "damage", "save", "heal", "check", "utility"
  targetTokenIds?: string[]; // Token IDs on the active scene to set as targets before activation
  templatePosition?: { x: number; y: number; direction?: number };  // Position for spell template
  spellLevel?: number;       // Cast at a specific spell level

  // module 8.14+
  attackerTokenId?: string;  // Act as this token of the actor (its synthetic actor when unlinked); controlled first
  attackMode?: string;       // "oneHanded" | "twoHanded" | "offhand" | "thrown" | "thrown-offhand"
  ammunition?: string | false; // Ammunition item id, or false to attack without consuming any
  consume?: { spellSlot?: boolean; itemUses?: boolean; ammunition?: boolean };
  fastForward?: boolean;     // Default true: skip every dialog, auto-roll attack and damage
  advantage?: boolean;       // One-off, this use only — no effect document is created
  disadvantage?: boolean;
  attackBonus?: number | string;  // Added to this attack roll: 2, "1d4"
  damageBonus?: string;      // Added to the first damage part: "1d6" for Sneak Attack
  targetAcBonus?: number;    // Added to every target's AC for this hit check (Midi-QOL only)
}

Both flags at once are a validation error (Cannot have both advantage and disadvantage).

Result:

interface ActivateItemResult {
  itemId: string;            // ID of the used item
  itemName: string;          // Display name of the item (e.g., "Longsword", "Fireball")
  itemType: string;          // Item type: "weapon", "spell", "consumable", "equipment", "feat", etc.
  activityUsed?: ActivityInfo; // Present when item has D&D 5e activities. Absent when item has no activities.
  activated: boolean;        // false only when a dialog opened before the use went through
  status: ActivationStatus;  // module 8.14+, see below
  dialog?: UserDialogKind;   // The dialog that opened, when status is "awaiting_user_dialog"
  targetsSet: number;        // Number of targets set before activation (0 if targetTokenIds not provided)
  rolls: RollResult[];       // Rolls from use() plus, without Midi-QOL, the attack or damage roll dnd5e made afterwards
  chatMessageId?: string;    // ID of the chat message created in Foundry. Present when Foundry creates a chat card.
  workflow?: MidiWorkflowResult; // Midi-QOL automation results. ONLY present when Midi-QOL module is installed and active.
  appliedDamage?: AppliedHitPoints;  // HP before/after per token id (Midi-QOL damage list, checked against the actor)
  appliedHealing?: AppliedHitPoints; // Same for healing; without Midi-QOL the module applies heal activities itself
  warning?: string;          // "no_roll_performed" | "midi_workflow_timeout" | "midi_workflow_aborted"
}

type ActivationStatus = 'completed' | 'awaiting_user_dialog' | 'workflow_timeout' | 'workflow_aborted';
type UserDialogKind = 'attack-roll' | 'consume' | 'damage' | 'other';

type AppliedHitPoints = Record<string, {
  applied: boolean;          // HP on the actor now matches what the workflow computed
  hpBefore: number;
  hpAfter: number;
  tempBefore?: number;
  tempAfter?: number;
}>;

interface ActivityInfo {
  id: string;                // Activity ID within the item
  name: string;              // Activity display name (e.g., "Attack", "Cast", "Heal")
  type: string;              // Activity type: "attack", "damage", "save", "heal", "check", "utility"
}

interface RollResult {
  total: number;             // Final roll total (e.g., 18 for "1d20+5" roll of 13)
  formula: string;           // Roll formula as string (e.g., "1d20+5", "2d6+3")
  dice: DiceResult[];        // Individual dice breakdowns
  isCritical?: boolean;      // true if natural 20 on d20 attack roll
  isFumble?: boolean;        // true if natural 1 on d20 attack roll
  mode?: 'advantage' | 'normal' | 'disadvantage';  // What dnd5e applied to the d20
  kept?: number;             // The d20 result that stayed after kh/kl
}

interface DiceResult {
  type: string;              // Dice type: "d20", "d6", "d8", "d10", "d12", etc.
  count: number;             // Number of dice rolled (e.g., 2 for "2d6")
  results: number[];         // Individual die results (e.g., [4, 6] for 2d6)
}

interface MidiWorkflowResult {
  attackTotal: number | undefined;      // Total attack roll value. undefined for non-attack items (potions, spells with saves only).
  damageTotal: number | undefined;      // Total damage dealt. undefined if attack missed or no damage component.
  isCritical: boolean;                  // true if the attack was a critical hit (natural 20)
  isFumble: boolean;                    // true if the attack was a fumble (natural 1)
  hitTargetIds: string[];               // Token IDs that were HIT by the attack. Empty array if attack missed or spell with saves.
  saveTargetIds: string[];              // Token IDs that SUCCEEDED their saving throw. For spells like Fireball.
  failedSaveTargetIds: string[];        // Token IDs that FAILED their saving throw. These take full damage/effects.
}

Presence rules for optional fields:

Field When present When absent
activityUsed Item has D&D 5e activities Item has no activities (falls back to item.use())
chatMessageId Foundry created a chat card No chat message was created
rolls[] Always present Empty array [] if no rolls (Midi-QOL handles rolling)
workflow Midi-QOL module is installed, active, and completed Midi-QOL not installed, inactive, timed out (25 s → status: "workflow_timeout") or aborted ("workflow_aborted")
appliedDamage / appliedHealing A workflow or the module changed HP Nothing landed
dialog status is awaiting_user_dialog Otherwise
workflow.attackTotal Attack item (weapon, spell attack) Non-attack items (potion, spell with save only)
workflow.damageTotal Attack hit or AoE damage applied Attack missed with no damage

Difference from use-item:

use-item activate-item
Foundry hooks Suppressed (configure: false, shiftKey: true) Native (not suppressed)
Midi-QOL Does not trigger Full automation pipeline
Targeting Not supported targetTokenIds sets targets before activation
Returns rolls Always (from suppressed Foundry call) From native call + Midi-QOL workflow data
Dialogs Suppressed Skipped and auto-rolled by default (fastForward: true); a dialog that still opens is reported at once as status: "awaiting_user_dialog" and the use finishes when the GM clicks
Use case Silent/programmatic operations Combat actions, spellcasting, item usage with full visualization

Midi-QOL detection: The module checks game.modules.get('midi-qol')?.active before activation. If Midi-QOL is active, it arms a midi-qol.RollComplete listener before calling activity.use() and resolves when the workflow completes, when Midi deletes the usage card (abort), or after 25 s (timeout) — below the platform's 30 s command gate. attackMode, advantage / disadvantage and the fast-forward flags reach Midi through usage.midiOptions.workflowOptions; targetAcBonus through workflow.targetACModifiers in midi-qol.preCheckHits. Without Midi-QOL the module hooks dnd5e's own preRollAttackV2 / preRollDamageV2 for the same choices, waits up to 5 s for the roll dnd5e triggers after use(), and applies heal activities itself. A consumable destroyed by its last use is kept at quantity 0 until the rolls are over, then deleted.

Examples:

// === Melee attack with Midi-QOL active ===

// Command
{
  "id": "1",
  "type": "activate-item",
  "params": {
    "actorId": "goblin-actor",
    "itemId": "scimitar-id",
    "targetTokenIds": ["fighter-token"]
  }
}

// Response -- attack HIT
{
  "id": "1",
  "success": true,
  "data": {
    "itemId": "scimitar-id",
    "itemName": "Scimitar",
    "itemType": "weapon",
    "activityUsed": { "id": "act-1", "name": "Attack", "type": "attack" },
    "activated": true,
    "targetsSet": 1,
    "rolls": [
      {
        "total": 17,
        "formula": "1d20+4",
        "dice": [{ "type": "d20", "count": 1, "results": [13] }]
      }
    ],
    "chatMessageId": "msg-abc",
    "workflow": {
      "attackTotal": 17,
      "damageTotal": 8,
      "isCritical": false,
      "isFumble": false,
      "hitTargetIds": ["fighter-token"],
      "saveTargetIds": [],
      "failedSaveTargetIds": []
    }
  }
}

// === Critical hit ===
{
  "id": "2",
  "success": true,
  "data": {
    "itemId": "scimitar-id",
    "itemName": "Scimitar",
    "itemType": "weapon",
    "activityUsed": { "id": "act-1", "name": "Attack", "type": "attack" },
    "activated": true,
    "targetsSet": 1,
    "rolls": [
      { "total": 24, "formula": "1d20+4", "dice": [{ "type": "d20", "count": 1, "results": [20] }], "isCritical": true }
    ],
    "chatMessageId": "msg-def",
    "workflow": {
      "attackTotal": 24,
      "damageTotal": 14,
      "isCritical": true,
      "isFumble": false,
      "hitTargetIds": ["fighter-token"],
      "saveTargetIds": [],
      "failedSaveTargetIds": []
    }
  }
}

// === Attack MISSED ===
{
  "id": "3",
  "success": true,
  "data": {
    "itemId": "scimitar-id",
    "itemName": "Scimitar",
    "itemType": "weapon",
    "activityUsed": { "id": "act-1", "name": "Attack", "type": "attack" },
    "activated": true,
    "targetsSet": 1,
    "rolls": [
      { "total": 8, "formula": "1d20+4", "dice": [{ "type": "d20", "count": 1, "results": [4] }] }
    ],
    "chatMessageId": "msg-ghi",
    "workflow": {
      "attackTotal": 8,
      "isCritical": false,
      "isFumble": false,
      "hitTargetIds": [],
      "saveTargetIds": [],
      "failedSaveTargetIds": []
    }
  }
}

// === Fireball (save-based AoE) with Midi-QOL ===
{
  "id": "4",
  "type": "activate-item",
  "params": {
    "actorId": "wizard-actor",
    "itemId": "fireball-id",
    "targetTokenIds": ["goblin-1", "goblin-2", "goblin-3"]
  }
}

// Response -- 2 failed saves, 1 saved
{
  "id": "4",
  "success": true,
  "data": {
    "itemId": "fireball-id",
    "itemName": "Fireball",
    "itemType": "spell",
    "activityUsed": { "id": "act-1", "name": "Cast", "type": "save" },
    "activated": true,
    "targetsSet": 3,
    "rolls": [],
    "chatMessageId": "msg-jkl",
    "workflow": {
      "damageTotal": 28,
      "isCritical": false,
      "isFumble": false,
      "hitTargetIds": [],
      "saveTargetIds": ["goblin-2"],
      "failedSaveTargetIds": ["goblin-1", "goblin-3"]
    }
  }
}

// === Healing Potion (no target, no Midi-QOL workflow) ===
{
  "id": "5",
  "type": "activate-item",
  "params": {
    "actorId": "fighter-actor",
    "itemId": "healing-potion-id"
  }
}

// Response -- no targets, basic rolls only
{
  "id": "5",
  "success": true,
  "data": {
    "itemId": "healing-potion-id",
    "itemName": "Potion of Healing",
    "itemType": "consumable",
    "activated": true,
    "targetsSet": 0,
    "rolls": [
      { "total": 7, "formula": "2d4+2", "dice": [{ "type": "d4", "count": 2, "results": [3, 2] }] }
    ],
    "chatMessageId": "msg-mno"
  }
}

// === Without Midi-QOL installed (vanilla Foundry) ===
{
  "id": "6",
  "success": true,
  "data": {
    "itemId": "scimitar-id",
    "itemName": "Scimitar",
    "itemType": "weapon",
    "activityUsed": { "id": "act-1", "name": "Attack", "type": "attack" },
    "activated": true,
    "targetsSet": 1,
    "rolls": [
      { "total": 15, "formula": "1d20+4", "dice": [{ "type": "d20", "count": 1, "results": [11] }] }
    ],
    "chatMessageId": "msg-pqr"
  }
}

add-item-to-actor

Create a new item directly in an actor's inventory.

Params:

interface AddItemToActorParams {
  actorId: string;
  name: string;
  type: string;              // "weapon", "equipment", "consumable", "loot", "spell", etc.
  img?: string;              // Item icon path
  system?: ItemSystemData;   // System-specific data
}

interface ItemSystemData {
  description?: { value: string };
  quantity?: number;
  weight?: { value: number; units?: string };
  price?: { value: number; denomination?: string };
  rarity?: string;           // "common", "uncommon", "rare", "veryRare", "legendary"
  identified?: boolean;
  equipped?: boolean;
  attunement?: number;
}

Result:

interface ItemResult {
  id: string;
  name: string;
  type: string;
  img: string;
  actorId: string;
  actorName: string;
}

Example:

// Command - give 50 gold pieces
{
  "id": "1",
  "type": "add-item-to-actor",
  "params": {
    "actorId": "abc123",
    "name": "Gold Coins",
    "type": "loot",
    "system": {
      "quantity": 50,
      "price": { "value": 1, "denomination": "gp" }
    }
  }
}

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "item-new-001",
    "name": "Gold Coins",
    "type": "loot",
    "img": "icons/commodities/currency/coins-gold.png",
    "actorId": "abc123",
    "actorName": "Aragorn"
  }
}

add-item-from-compendium

Add an item from a compendium pack to an actor's inventory. Useful for giving standard SRD equipment, weapons, potions, etc.

Params:

interface AddItemFromCompendiumParams {
  actorId: string;
  packId: string;      // Compendium ID, e.g., "dnd5e.items"
  itemId: string;      // Item ID within compendium
  name?: string;       // Override item name
  quantity?: number;   // Override quantity
}

Result: ItemResult

Example:

// Command - give Healing Potion from SRD
{
  "id": "1",
  "type": "add-item-from-compendium",
  "params": {
    "actorId": "abc123",
    "packId": "dnd5e.items",
    "itemId": "potion-of-healing-id",
    "quantity": 3
  }
}

// Command - give custom-named weapon
{
  "id": "2",
  "type": "add-item-from-compendium",
  "params": {
    "actorId": "abc123",
    "packId": "dnd5e.items",
    "itemId": "longsword-id",
    "name": "Flame Tongue Longsword"
  }
}

update-actor-item

Update an existing item in an actor's inventory.

Params:

interface UpdateActorItemParams {
  actorId: string;
  itemId: string;
  name?: string;
  img?: string;
  system?: Partial<ItemSystemData>;  // Partial update
}

Result: ItemResult

Example:

// Command - update quantity (consume 2 potions)
{
  "id": "1",
  "type": "update-actor-item",
  "params": {
    "actorId": "abc123",
    "itemId": "potion-item-id",
    "system": { "quantity": 1 }
  }
}

// Command - equip item
{
  "id": "2",
  "type": "update-actor-item",
  "params": {
    "actorId": "abc123",
    "itemId": "armor-item-id",
    "system": { "equipped": true }
  }
}

delete-actor-item

Remove an item from an actor's inventory.

Params:

interface DeleteActorItemParams {
  actorId: string;
  itemId: string;
}

Result: DeleteResult

Example:

{ "id": "1", "type": "delete-actor-item", "params": { "actorId": "abc123", "itemId": "broken-sword-id" } }
{ "id": "1", "success": true, "data": { "deleted": true } }

Effect Management

get-actor-effects

Get all active effects on an actor.

Params:

interface GetActorEffectsParams {
  actorId?: string;      // World actor. Either this or tokenId is required.
  tokenId?: string;      // One token's actor instead (module 8.14+): unlinked tokens keep their own effects
  sceneId?: string;      // Defaults to the active scene
  includeDisabled?: boolean;  // Include disabled effects (default: true)
}

Result:

interface ActorEffectsResult {
  actorId: string;
  actorName: string;
  effects: EffectSummary[];
  activeStatuses: string[];   // Currently active status IDs
}

interface EffectSummary {
  id: string;
  name: string;
  img: string;
  disabled: boolean;
  isTemporary: boolean;
  statuses: string[];
  origin: string | null;
  changes?: EffectChangeData[];
  duration?: EffectDurationData;
}

interface EffectChangeData {
  key: string;    // e.g., "system.attributes.ac.bonus"
  value: string;
  mode: number;   // See change modes below
}

interface EffectDurationData {
  seconds?: number;
  rounds?: number;
  turns?: number;
}

Change Modes:

Mode Name Description
0 CUSTOM Custom handling
1 MULTIPLY Multiply value
2 ADD Add to value
3 DOWNGRADE Use lower value
4 UPGRADE Use higher value
5 OVERRIDE Replace value

Example:

// Command
{ "id": "1", "type": "get-actor-effects", "params": { "actorId": "abc123" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "actorId": "abc123",
    "actorName": "Aragorn",
    "effects": [
      {
        "id": "effect-001",
        "name": "Bless",
        "img": "icons/magic/holy/bless.png",
        "disabled": false,
        "isTemporary": true,
        "statuses": [],
        "origin": "Item.spell-123",
        "changes": [{ "key": "system.bonuses.abilities.save", "value": "1d4", "mode": 2 }],
        "duration": { "rounds": 10 }
      },
      {
        "id": "effect-002",
        "name": "Prone",
        "img": "icons/conditions/prone.png",
        "disabled": false,
        "isTemporary": false,
        "statuses": ["prone"],
        "origin": null
      }
    ],
    "activeStatuses": ["prone"]
  }
}

toggle-actor-status

Toggle a D&D 5e status condition on/off.

Params:

interface ToggleActorStatusParams {
  actorId?: string;      // World actor. Either this or tokenId is required.
  tokenId?: string;      // One token's actor instead (module 8.14+): unlinked tokens keep their own effects
  sceneId?: string;      // Defaults to the active scene
  statusId: string;      // See available statuses below
  active?: boolean;      // Force active/inactive state
  overlay?: boolean;     // Show as large overlay on token
}

Available Status IDs:

Status Status Status
blinded frightened petrified
charmed grappled poisoned
concentrating incapacitated prone
dead invisible restrained
deafened paralyzed stunned
exhaustion unconscious

Result:

interface ToggleStatusResult {
  actorId: string;
  statusId: string;
  active: boolean;       // New state
  effectId?: string;     // Effect ID if created
}

Example:

// Command - apply stunned condition
{ "id": "1", "type": "toggle-actor-status", "params": { "actorId": "abc123", "statusId": "stunned", "active": true } }

// Response
{ "id": "1", "success": true, "data": { "actorId": "abc123", "statusId": "stunned", "active": true, "effectId": "effect-new-001" } }

// Command - remove stunned condition
{ "id": "2", "type": "toggle-actor-status", "params": { "actorId": "abc123", "statusId": "stunned", "active": false } }

// Response
{ "id": "2", "success": true, "data": { "actorId": "abc123", "statusId": "stunned", "active": false } }

add-actor-effect

Create a custom ActiveEffect on an actor.

Params:

interface AddActorEffectParams {
  actorId?: string;      // World actor. Either this or tokenId is required.
  tokenId?: string;      // One token's actor instead (module 8.14+): unlinked tokens keep their own effects
  sceneId?: string;      // Defaults to the active scene
  name: string;
  img?: string;
  disabled?: boolean;
  origin?: string;              // Source reference, e.g., "Item.spell-id"
  statuses?: string[];          // Associated status IDs
  changes?: EffectChangeData[];
  duration?: EffectDurationData;
}

Result:

interface AddEffectResult {
  actorId: string;
  effectId: string;
  name: string;
}

Example:

// Command - add Shield of Faith effect (+2 AC for 10 minutes)
{
  "id": "1",
  "type": "add-actor-effect",
  "params": {
    "actorId": "abc123",
    "name": "Shield of Faith",
    "img": "icons/magic/defensive/shield.png",
    "changes": [{ "key": "system.attributes.ac.bonus", "value": "2", "mode": 2 }],
    "duration": { "seconds": 600 }
  }
}

// Response
{ "id": "1", "success": true, "data": { "actorId": "abc123", "effectId": "effect-new-002", "name": "Shield of Faith" } }

remove-actor-effect

Remove an effect from an actor.

Params:

interface RemoveActorEffectParams {
  actorId?: string;      // World actor. Either this or tokenId is required.
  tokenId?: string;      // One token's actor instead (module 8.14+): unlinked tokens keep their own effects
  sceneId?: string;      // Defaults to the active scene
  effectId: string;
}

Result:

interface RemoveEffectResult {
  actorId: string;
  effectId: string;
  removed: boolean;
}

Example:

{ "id": "1", "type": "remove-actor-effect", "params": { "actorId": "abc123", "effectId": "effect-001" } }
{ "id": "1", "success": true, "data": { "actorId": "abc123", "effectId": "effect-001", "removed": true } }

update-actor-effect

Update an existing effect's properties.

Params:

interface UpdateActorEffectParams {
  actorId?: string;      // World actor. Either this or tokenId is required.
  tokenId?: string;      // One token's actor instead (module 8.14+): unlinked tokens keep their own effects
  sceneId?: string;      // Defaults to the active scene
  effectId: string;
  name?: string;
  img?: string;
  disabled?: boolean;
  changes?: EffectChangeData[];
  duration?: EffectDurationData;
}

Result:

interface UpdateEffectResult {
  actorId: string;
  effectId: string;
  name: string;
}

Example:

// Command - disable an effect
{ "id": "1", "type": "update-actor-effect", "params": { "actorId": "abc123", "effectId": "effect-001", "disabled": true } }

// Response
{ "id": "1", "success": true, "data": { "actorId": "abc123", "effectId": "effect-001", "name": "Bless" } }

Compendium Access

get-compendiums

Get a list of all available compendium packs.

Params:

// No parameters required
interface GetCompendiumsParams {}

Result:

type GetCompendiumsResult = CompendiumMetadata[];

interface CompendiumMetadata {
  id: string;              // e.g., "dnd5e.monsters"
  label: string;           // e.g., "Monsters (SRD)"
  type: string;            // "Actor", "Item", "JournalEntry", "Scene", etc.
  system: string;          // e.g., "dnd5e"
  packageName: string;     // e.g., "dnd5e"
  documentCount: number;   // Number of entries
}

Example:

// Command
{ "id": "1", "type": "get-compendiums", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    { "id": "dnd5e.monsters", "label": "Monsters (SRD)", "type": "Actor", "system": "dnd5e", "packageName": "dnd5e", "documentCount": 325 },
    { "id": "dnd5e.items", "label": "Items (SRD)", "type": "Item", "system": "dnd5e", "packageName": "dnd5e", "documentCount": 680 },
    { "id": "dnd5e.spells", "label": "Spells (SRD)", "type": "Item", "system": "dnd5e", "packageName": "dnd5e", "documentCount": 319 }
  ]
}

get-compendium

Get contents of a compendium pack, including documents with their data. Optional types / ids select a subset server-side (Foundry type__in / _id__in database queries on v13+; on v11/v12 the module transparently falls back to a full load with client-side filtering — the result is identical). Batch-fetching several known documents is one get-compendium call with ids instead of N get-compendium-document calls.

Params:

interface GetCompendiumParams {
  packId: string;          // Compendium ID, e.g., "dnd5e.monsters"
  types?: string[];        // Only documents of these sub-types, e.g. ["spell"] (empty array matches nothing)
  ids?: string[];          // Only documents with these _ids (empty array matches nothing)
}

documentCount in the result reflects the returned (filtered) set. Omitting both params keeps the classic full-pack behavior.

Result:

interface CompendiumData {
  id: string;
  label: string;
  type: string;
  system: string;
  documentCount: number;
  documents: CompendiumDocument[];
}

interface CompendiumDocument {
  id: string;
  uuid: string;
  name: string;
  type: string;
  img: string;
  system?: Record<string, unknown>;
  items?: ItemData[];          // For Actor documents
  pages?: JournalPageData[];   // For JournalEntry documents
}

Example:

// Command
{ "id": "1", "type": "get-compendium", "params": { "packId": "dnd5e.monsters" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "dnd5e.monsters",
    "label": "Monsters (SRD)",
    "type": "Actor",
    "system": "dnd5e",
    "documentCount": 325,
    "documents": [
      {
        "id": "goblin-001",
        "uuid": "Compendium.dnd5e.monsters.goblin-001",
        "name": "Goblin",
        "type": "npc",
        "img": "tokens/goblin.png",
        "system": { "attributes": { "hp": { "value": 7, "max": 7 } } }
      }
    ]
  }
}

get-compendium-index

Get the lightweight index of a pack — id/name/img/type per entry — without loading documents. Optional dot-path fields are re-indexed server-side and returned per entry (e.g. system.level, system.details.cr).

Params:

interface GetCompendiumIndexParams {
  packId: string;
  fields?: string[];       // Extra index fields as dot-paths
}

Result:

interface GetCompendiumIndexResult {
  packId: string;
  packType: string;        // Pack document type, e.g. "Actor"
  packLabel: string;
  total: number;
  entries: CompendiumIndexEntry[];
}

interface CompendiumIndexEntry {
  id: string;
  name: string;
  img: string | null;
  type: string | null;                // Document sub-type, e.g. "npc", "spell"
  fields?: Record<string, unknown>;   // Present when `fields` was requested
}

search-compendium

Foundry's native index search within one pack: full-text query over searchable fields plus structured field filters with operators. Wire operator names are translated to Foundry's internal values by the module.

Params:

interface SearchCompendiumParams {
  packId: string;
  query?: string;              // Full-text query
  filters?: CompendiumFilter[];
  exclude?: string[];          // Document _ids to exclude
  fields?: string[];           // Dot-paths to index before filtering and return per entry
  limit?: number;              // Default 50, max 500
  offset?: number;             // Default 0
}

interface CompendiumFilter {
  field: string;               // Dot-path, e.g. "system.level"
  operator?: 'EQUALS' | 'CONTAINS' | 'STARTS_WITH' | 'ENDS_WITH' | 'LESS_THAN'
    | 'LESS_THAN_EQUAL' | 'GREATER_THAN' | 'GREATER_THAN_EQUAL' | 'BETWEEN' | 'IS_EMPTY';
  value?: unknown;             // BETWEEN expects [min, max]
  negate?: boolean;            // Default false
}

Note: CONTAINS follows Foundry semantics — membership of the document value in an array value, not substring matching.

Result:

interface SearchCompendiumResult {
  packId: string;
  results: CompendiumIndexEntry[];
  total: number;
  hasMore: boolean;
}

get-compendium-document

Get a single document from a pack by id, with its full raw data (toObject()), without loading the whole pack.

Params:

interface GetCompendiumDocumentParams {
  packId: string;
  documentId: string;
}

Result:

interface CompendiumDocumentResult {
  id: string;
  uuid: string;
  name: string;
  type: string;                    // Document sub-type ("" when absent)
  img: string | null;
  documentType: string;            // Pack document type, e.g. "Item"
  data: Record<string, unknown>;   // Full document source
}

import-from-compendium

Import a document from a pack into the world. Works for any pack type except Adventure (use the Foundry UI for adventures). Optional name / folder overrides.

Params:

interface ImportFromCompendiumParams {
  packId: string;
  documentId: string;
  name?: string;           // Override the imported document's name
  folder?: string;         // World folder id
}

Result:

interface ImportFromCompendiumResult {
  imported: true;
  worldId: string;         // Id of the created world document
  uuid: string;
  name: string;
  documentType: string;
}

search-compendiums

Search the index of every compendium pack by document name (case-insensitive substring). Reads only pack indexes — never full documents — and returns lightweight matches, so it stays fast across large packs. Use this to find a document by name across all packs (e.g. "goblin"); use get-compendium / get-compendium-index when you already know the pack.

Params:

interface SearchCompendiumsParams {
  query: string;           // Name substring, case-insensitive (required)
  type?: string;           // Filter by pack document type: "Actor" | "Item" | "JournalEntry" | "RollTable" | "Scene" | "ActiveEffect"
  system?: string;         // Filter by system, e.g. "dnd5e"
  limit?: number;          // Max matches, default 100, capped at 500
}

Result:

type SearchCompendiumsResult = CompendiumSearchMatch[];

interface CompendiumSearchMatch {
  packId: string;          // pack collection, e.g. "dnd5e.monsters"
  packLabel: string;       // pack label
  packType: string;        // pack document type, e.g. "Actor"
  system: string;          // pack system, "" if none
  id: string;              // document _id within the pack
  name: string;
  documentType?: string;   // index sub-type if present, e.g. "npc", "weapon", "spell"
}

An empty result is [], never an error.

Example:

// Command
{ "id": "1", "type": "search-compendiums", "params": { "query": "goblin", "type": "Actor" } }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    { "packId": "dnd5e.monsters", "packLabel": "Monsters (SRD)", "packType": "Actor", "system": "dnd5e", "id": "goblin-001", "name": "Goblin", "documentType": "npc" },
    { "packId": "dnd5e.monsters", "packLabel": "Monsters (SRD)", "packType": "Actor", "system": "dnd5e", "id": "hobgoblin-001", "name": "Hobgoblin", "documentType": "npc" }
  ]
}

search-compendium-pages

Full-text search inside JournalEntry compendium packs — rules text and spell lists live in journal pages, which name-based index search cannot see. Matches the query against page names and (unless searchContent: false) page HTML content; content matches return a plain-text snippet (±100 chars around the first hit). Packs are scanned lazily — the search stops loading further packs once limit is reached.

Params:

interface SearchCompendiumPagesParams {
  query: string;           // Substring, case-insensitive; blank → []
  packIds?: string[];      // Explicit JournalEntry packs (unknown/non-journal pack → error); omitted → all journal packs
  pageTypes?: string[];    // Page sub-types, e.g. ["rule", "spells"] (empty array matches nothing)
  searchContent?: boolean; // Default true; false = names only (cheaper)
  limit?: number;          // Default 25, capped at 200
}

Result:

type SearchCompendiumPagesResult = CompendiumPageMatch[];

interface CompendiumPageMatch {
  packId: string;
  packLabel: string;
  journalId: string;
  journalName: string;
  pageId: string;
  pageName: string;
  pageType: string;               // e.g. "rule", "spells", "class", "text"
  uuid: string;                   // Page uuid — feed it to resolve-uuid for the full text
  matchedIn: 'name' | 'content';
  snippet: string | null;         // Context around the content match; null for name matches
}

Example:

// Command
{ "id": "1", "type": "search-compendium-pages", "params": { "query": "grappled", "pageTypes": ["rule"] } }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    {
      "packId": "dnd5e.rules", "packLabel": "Rules (SRD)",
      "journalId": "j1", "journalName": "Appendix A: Conditions",
      "pageId": "p2", "pageName": "Grappled", "pageType": "rule",
      "uuid": "Compendium.dnd5e.rules.JournalEntry.j1.JournalEntryPage.p2",
      "matchedIn": "name", "snippet": null
    }
  ]
}

resolve-uuid

Resolve any absolute Foundry UUID to its document: world documents (Actor.x), compendium documents (Compendium.scope.pack.Item.x), and embedded documents (Actor.x.Item.y, journal pages). This makes reference graphs walkable — class → ItemGrant advancement → features → effects, spell lists → spells — one call per hop.

Params:

interface ResolveUuidParams {
  uuid: string;
}

Result:

interface ResolveUuidResult {
  uuid: string;                    // Normalized uuid
  documentName: string;            // Primary type, e.g. "Item", "Actor", "JournalEntryPage"
  id: string;
  name: string | null;
  type: string | null;             // Sub-type, e.g. "spell"; null when the document has none
  img: string | null;
  pack: string | null;             // Pack collection for compendium documents, null for world
  parentUuid: string | null;       // Parent uuid for embedded documents
  data: Record<string, unknown>;   // Full document source (toObject())
}

An unresolvable or malformed UUID returns Document not found for UUID: <uuid> as the error.


Roll Tables

list-roll-tables

Get a summary list of all roll tables in the world.

Params:

// No parameters required
interface ListRollTablesParams {}

Result:

type ListRollTablesResult = RollTableSummary[];

interface RollTableSummary {
  id: string;
  name: string;
  img: string;
  description: string;
  formula: string;           // e.g., "1d20", "1d100"
  replacement: boolean;      // Draw with replacement
  totalResults: number;      // Total number of entries
  drawnResults: number;      // Number already drawn (when replacement: false)
}

Example:

// Command
{ "id": "1", "type": "list-roll-tables", "params": {} }

// Response
{
  "id": "1",
  "success": true,
  "data": [
    {
      "id": "table-1",
      "name": "Random Encounters",
      "img": "icons/svg/d20.svg",
      "description": "Forest encounter table",
      "formula": "1d20",
      "replacement": true,
      "totalResults": 20,
      "drawnResults": 0
    }
  ]
}

get-roll-table

Get full details of a roll table including all result entries.

Params:

interface GetRollTableParams {
  tableId: string;
}

Result:

interface RollTableResult {
  id: string;
  name: string;
  img: string;
  description: string;
  formula: string;
  replacement: boolean;
  displayRoll: boolean;      // Show roll in chat
  results: TableResultData[];
}

interface TableResultData {
  id: string;
  type: number;              // 0 = text, 1 = document, 2 = compendium
  text: string;
  img: string;
  range: [number, number];   // [min, max] roll range
  weight: number;
  drawn: boolean;            // Already drawn (for no-replacement tables)
  documentCollection: string | null;  // For document/compendium references
  documentId: string | null;
}

Example:

// Command
{ "id": "1", "type": "get-roll-table", "params": { "tableId": "table-1" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "id": "table-1",
    "name": "Random Encounters",
    "img": "icons/svg/d20.svg",
    "description": "Forest encounter table",
    "formula": "1d20",
    "replacement": true,
    "displayRoll": true,
    "results": [
      { "id": "res-1", "type": 0, "text": "2d4 Wolves", "img": "", "range": [1, 5], "weight": 1, "drawn": false, "documentCollection": null, "documentId": null },
      { "id": "res-2", "type": 0, "text": "1 Owlbear", "img": "", "range": [6, 10], "weight": 1, "drawn": false, "documentCollection": null, "documentId": null }
    ]
  }
}

roll-on-table

Roll on a table and get the result. Optionally displays the roll in Foundry chat.

Params:

interface RollOnTableParams {
  tableId: string;
  displayChat?: boolean;     // Show in chat (default: true)
}

Result:

interface RollOnTableResult {
  tableId: string;
  tableName: string;
  roll: {
    formula: string;
    total: number;
  };
  results: TableResultData[];  // Matched result entries
}

Example:

// Command
{ "id": "1", "type": "roll-on-table", "params": { "tableId": "table-1" } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "tableId": "table-1",
    "tableName": "Random Encounters",
    "roll": { "formula": "1d20", "total": 7 },
    "results": [
      { "id": "res-2", "type": 0, "text": "1 Owlbear", "img": "", "range": [6, 10], "weight": 1, "drawn": false, "documentCollection": null, "documentId": null }
    ]
  }
}

reset-table

Reset a roll table, marking all results as not drawn. Useful for tables with replacement: false.

Params:

interface ResetTableParams {
  tableId: string;
}

Result:

interface ResetTableResult {
  tableId: string;
  tableName: string;
  resetCount: number;        // Number of results reset
}

Example:

// Command
{ "id": "1", "type": "reset-table", "params": { "tableId": "table-1" } }

// Response
{ "id": "1", "success": true, "data": { "tableId": "table-1", "tableName": "Random Encounters", "resetCount": 20 } }

create-roll-table

Create a new roll table with optional pre-defined results.

Params:

interface CreateRollTableParams {
  name: string;
  formula?: string;          // Default: "1d20"
  replacement?: boolean;     // Default: true
  displayRoll?: boolean;     // Default: true
  description?: string;
  img?: string;
  folder?: string;           // Folder ID
  results?: CreateTableResultData[];
}

interface CreateTableResultData {
  text: string;
  range: [number, number];   // [min, max]
  weight?: number;           // Default: 1
  type?: number;             // 0 = text (default), 1 = document, 2 = compendium
  documentCollection?: string;
  documentId?: string;
  img?: string;
}

Result: RollTableResult

Example:

// Command
{
  "id": "1",
  "type": "create-roll-table",
  "params": {
    "name": "Treasure Hoard",
    "formula": "1d6",
    "description": "Loot from dragon's hoard",
    "results": [
      { "text": "100 gold pieces", "range": [1, 2] },
      { "text": "Potion of Healing", "range": [3, 4] },
      { "text": "Magic Sword +1", "range": [5, 5] },
      { "text": "Diamond worth 500gp", "range": [6, 6] }
    ]
  }
}

update-roll-table

Update roll table properties (does not modify individual results).

Params:

interface UpdateRollTableParams {
  tableId: string;
  name?: string;
  formula?: string;
  replacement?: boolean;
  displayRoll?: boolean;
  description?: string;
  img?: string;
}

Result: RollTableResult

Example:

// Command
{ "id": "1", "type": "update-roll-table", "params": { "tableId": "table-1", "name": "Updated Encounters", "formula": "1d100" } }

delete-roll-table

Delete a roll table from the world.

Params:

interface DeleteRollTableParams {
  tableId: string;
}

Result: DeleteResult

Example:

{ "id": "1", "type": "delete-roll-table", "params": { "tableId": "table-1" } }
{ "id": "1", "success": true, "data": { "deleted": true } }

Walls & Doors

set-door-state

Set the state of a door on a scene wall. The wall must be a door (door type 1 or 2).

Params:

interface SetDoorStateParams {
  sceneId?: string;          // Defaults to active scene
  wallId: string;            // Wall ID (must be a door)
  state: number;             // 0 = closed, 1 = open, 2 = locked
}

Result:

interface SetDoorStateResult {
  wallId: string;
  door: number;              // Door type (1 = door, 2 = secret door)
  previousState: number;     // State before the change
  newState: number;          // State after the change
}

Example:

// Command - open a door
{ "id": "1", "type": "set-door-state", "params": { "wallId": "wall-door-1", "state": 1 } }

// Response
{
  "id": "1",
  "success": true,
  "data": {
    "wallId": "wall-door-1",
    "door": 1,
    "previousState": 0,
    "newState": 1
  }
}

// Command - lock a door
{ "id": "2", "type": "set-door-state", "params": { "wallId": "wall-door-1", "state": 2 } }

// Response
{ "id": "2", "success": true, "data": { "wallId": "wall-door-1", "door": 1, "previousState": 1, "newState": 2 } }

get-walls

List the walls of a scene.

Params: { sceneId?: string } — defaults to the active scene.

Result:

interface GetWallsResult {
  sceneId: string;
  walls: WallSummary[];
}

interface WallSummary {
  id: string;
  c: [number, number, number, number];     // x1, y1, x2, y2 in pixels
  door: 'none' | 'door' | 'secret';
  doorState: 'closed' | 'open' | 'locked';
  move: 'none' | 'normal' | 'limited';     // Movement restriction
  sense: 'none' | 'normal' | 'limited';    // Vision restriction
  sound: 'none' | 'normal' | 'limited';
  light: 'none' | 'normal' | 'limited';
  dir: 'both' | 'left' | 'right';          // Side the wall blocks
}

create-wall

Params:

interface CreateWallParams {
  sceneId?: string;                        // Defaults to the active scene
  c: [number, number, number, number];
  door?: 'none' | 'door' | 'secret';
  doorState?: 'closed' | 'open' | 'locked';
  move?: 'none' | 'normal' | 'limited';
  sense?: 'none' | 'normal' | 'limited';
  sound?: 'none' | 'normal' | 'limited';
  light?: 'none' | 'normal' | 'limited';
  dir?: 'both' | 'left' | 'right';
}

Result: WallSummary (see get-walls).


update-wall

Same fields as create-wall, all optional, plus wallId.

Params: { sceneId?: string, wallId: string, c?, door?, doorState?, move?, sense?, sound?, light?, dir? }

Result: WallSummary.


delete-wall

Params: { sceneId?: string, wallId: string }

Result: { deleted: true, wallId: string, sceneId: string }


Notes

Map notes (journal pins) on a scene. sceneId defaults to the active scene everywhere.

get-notes

Params: { sceneId?: string }

Result:

interface GetNotesResult {
  sceneId: string;
  notes: NoteSummary[];
}

interface NoteSummary {
  id: string;
  x: number;
  y: number;
  entryId: string | null;   // Linked JournalEntry
  pageId: string | null;    // Linked page inside the entry
  text: string | null;      // Label override
  iconSrc: string;
  iconTint: string | null;
  iconSize: number;
  fontSize: number;
  textAnchor: 'center' | 'bottom' | 'top' | 'left' | 'right';
  textColor: string | null;
  global: boolean;          // Visible to everyone regardless of vision
}

create-note

Params:

interface CreateNoteParams {
  sceneId?: string;
  x: number;
  y: number;
  entryId?: string;
  pageId?: string;
  text?: string;
  iconSrc?: string;
  iconTint?: string;
  iconSize?: number;
  fontSize?: number;
  textAnchor?: 'center' | 'bottom' | 'top' | 'left' | 'right';
  textColor?: string;
  global?: boolean;
}

Result: NoteSummary (see get-notes).


update-note

Same fields as create-note, all optional, plus noteId. entryId, pageId, text, iconTint and textColor accept null to clear the value.

Result: NoteSummary.


delete-note

Params: { sceneId?: string, noteId: string }

Result: { deleted: true, noteId: string, sceneId: string }


Folders

get-folders

Params:

interface GetFoldersParams {
  type?: 'Actor' | 'Item' | 'Scene' | 'JournalEntry' | 'RollTable' | 'Macro' | 'Cards' | 'Playlist' | 'Compendium';
}

Result: FolderSummary[]

interface FolderSummary {
  id: string;
  name: string;
  type: FolderDocumentType;
  color: string | null;
  description: string | null;
  parentId: string | null;
  sort: number;
}

get-folder

Params:

interface GetFolderParams {
  folderId: string;
  includeSubfolders?: boolean;   // Nest child folders in `subfolders`
  includeContents?: boolean;     // Add `contentIds` of the documents inside
}

Result:

interface FolderTreeEntry extends FolderSummary {
  subfolders: FolderTreeEntry[];
  contentIds?: string[];
}

create-folder

Params:

interface CreateFolderParams {
  name: string;
  type: FolderDocumentType;
  parentId?: string;
  color?: string;        // e.g. "#ff0000"
  description?: string;
  sort?: number;
}

Result: FolderSummary.


update-folder

Params:

interface UpdateFolderParams {
  folderId: string;
  name?: string;
  parentId?: string | null;   // null = move to root
  color?: string;
  description?: string;
  sort?: number;
}

Result: FolderSummary.


delete-folder

Params:

interface DeleteFolderParams {
  folderId: string;
  deleteSubfolders?: boolean;  // Omitted → Foundry's default: subfolders are kept and moved up one level
  deleteContents?: boolean;    // Omitted → Foundry's default: documents are kept and moved up one level
}

Result: { deleted: true, folderId: string }


Macros

Script macros (type: "script") are arbitrary JavaScript with GM rights. Creating, updating or executing one is refused unless Allow Script Macros is on in the module settings, with the error Script macros are disabled. Enable "Allow Script Macros" in module settings to manage script-type macros via API. Chat macros are always allowed.

get-macros

Params: { type?: 'chat' | 'script' }

Result: MacroSummary[]

interface MacroSummary {
  id: string;
  uuid: string;
  name: string;
  type: 'chat' | 'script';
  img: string;
  scope: 'global' | 'actors' | 'actor';
  folder: string | null;
  authorId: string | null;
}

get-macro

Params: { macroId: string }

Result: MacroDetail — MacroSummary plus command: string (the macro body).


create-macro

Params:

interface CreateMacroParams {
  name: string;
  type: 'chat' | 'script';
  command: string;
  scope?: 'global' | 'actors' | 'actor';
  img?: string;
  folder?: string;
}

Result: MacroDetail.


update-macro

Same fields as create-macro, all optional, plus macroId; folder: null moves the macro to the root.

Result: MacroDetail.


delete-macro

Params: { macroId: string }

Result: { deleted: true, macroId: string }


execute-macro

Run a macro. actorId and tokenId set the actor and token the macro sees; the token must be on the active scene. Script macros need Allow Script Macros in the module settings.

Since 8.18.0 the call also takes args (any JSON object) and answers with the macro's return value.

  • args reaches a script macro as the variable args and as scope.args. Its keys are not spread into the macro's own variables, so the one reserved name cannot collide with speaker, actor, token, character or scope. Without args the macro sees exactly the scope earlier versions sent. A chat macro accepts args and ignores it (the core ignores the scope for chat macros). Anything but a JSON object (null, an array, a string, a number) fails with args must be a JSON object before the macro runs.
  • result is the macro's return value made JSON-safe: undefined, functions and symbols are dropped (absent at the root, omitted from objects, null inside arrays); NaN / ±Infinity become null; bigints their decimal string; Date its ISO string; Error { name, message }; a Foundry Document (anything with a string documentName and uuid) is reduced to { documentName, id, uuid, name } and never expanded; Map / Collection / Set become arrays of their values; any other object with toJSON() (a Roll) uses it; a value already on the current path becomes "[Circular]" and nesting deeper than 32 levels "[MaxDepth]". When the serialized JSON exceeds 256 KB (262 144 UTF-8 bytes) result is absent and the answer carries resultTruncated: true and resultBytes instead. A macro that returns nothing gets no result key; a chat macro's result is the created message's reference ({ documentName: 'ChatMessage', id, uuid }) or absent.
  • Before a script macro runs the module compiles its text (without executing it) and answers Macro "<name>" has a syntax error: <message>; a runtime error inside the macro answers Macro "<name>" failed: <message>. Checks run in this order: macro exists → script permission → args shape → syntax → actorId / tokenId → execution, so a bad call never half-executes.
  • A macro that waits for a dialog or a long-running promise does not answer in time: the platform waits 30 s and reports a timeout while the macro keeps running in Foundry. Return from the macro instead of waiting on the user.

Params:

interface ExecuteMacroParams {
  macroId: string;
  actorId?: string;
  tokenId?: string;
  /** Since 8.18.0. Any JSON object; a script macro reads it as `args` / `scope.args`, a chat macro ignores it. */
  args?: Record<string, unknown>;
}

Result:

interface ExecuteMacroResult {
  executed: true;
  macroId: string;
  macroName: string;
  macroType: 'chat' | 'script';
  /** Since 8.18.0. The macro's return value made JSON-safe; absent when it returned undefined or was truncated. */
  result?: unknown;
  /** Since 8.18.0. Present only when the serialized result exceeded 256 KB; `result` is then absent. */
  resultTruncated?: true;
  /** Since 8.18.0. UTF-8 size of the serialized result; present only with resultTruncated. */
  resultBytes?: number;
}

Example:

// → { "type": "execute-macro", "params": { "macroId": "aB3dE5fG7hI9jK1l", "args": { "itemId": "sword1", "targetTokenId": "tok42", "advantage": true } } }
// ← { "success": true, "data": { "executed": true, "macroId": "aB3dE5fG7hI9jK1l", "macroName": "Attack", "macroType": "script",
//      "result": { "hit": true, "roll": 18, "ac": 15, "damage": [{ "type": "slashing", "amount": 7 }], "hpBefore": 22, "hpAfter": 15 } } }

Errors: Macro not found: <id>, Script macros are disabled. Enable "Allow Script Macros" in module settings to manage script-type macros via API., args must be a JSON object, Macro "<name>" has a syntax error: <message>, Actor not found: <id>, Token not found on active scene: <id>, Macro "<name>" failed: <message>.


Playlists & Sounds

get-playlists

Params: none ({}).

Result: PlaylistSummary[]

interface PlaylistSummary {
  id: string;
  uuid: string;
  name: string;
  mode: 'disabled' | 'sequential' | 'shuffle' | 'simultaneous' | 'soundboard';
  channel: 'music' | 'environment' | 'interface' | null;
  fade: number | null;         // Fade duration in ms
  playing: boolean;
  description: string | null;
  folder: string | null;
  soundCount: number;
}

get-playlist

Params: { playlistId: string }

Result: PlaylistDetail — PlaylistSummary plus sounds:

interface PlaylistSoundSummary {
  id: string;
  name: string;
  path: string;
  playing: boolean;
  volume: number;    // 0-1
  repeat: boolean;
}

play-playlist / stop-playlist

Params: { playlistId: string }

Result: { playing: true, playlistId: string, soundCount: number } / { stopped: true, playlistId: string }


play-sound-in-playlist / stop-sound-in-playlist

Params: { playlistId: string, soundId: string }

Result: { playing: true, playlistId, soundId, soundName } / { stopped: true, playlistId, soundId }


play-sound-once

Play an audio file once, outside any playlist.

Params:

interface PlaySoundOnceParams {
  src: string;          // Audio file path or URL
  volume?: number;      // 0-1, default 0.8
  loop?: boolean;       // default false
  broadcast?: boolean;  // Play for every connected client, default true
}

Result: { playing: true, src: string, broadcast: boolean }


add-sound-to-playlist

Params:

interface AddSoundToPlaylistParams {
  playlistId: string;
  name: string;
  path: string;
  volume?: number;
  repeat?: boolean;
  description?: string;
}

Result: PlaylistSoundSummary (see get-playlist).


World Time & Pause

get-world-time

Params: none. Result: { worldTime: number } — game time in seconds.

advance-time

Params: { seconds: number } — seconds to add to the world time. Result: { worldTime: number, advancedBy: number }

set-world-time

Params: { worldTime: number }. Result: { worldTime: number }

pause-game / resume-game / get-pause-state

Params: none. Result: { paused: true } / { paused: false } / { paused: boolean }


UI Helpers

notify

Show a Foundry notification on the GM's client.

Params:

interface NotifyParams {
  message: string;
  type?: 'info' | 'warn' | 'error' | 'success';  // default "info"; "success" falls back to "info" on cores without it
  permanent?: boolean;                           // Stay until dismissed
}

Result: { shown: true, type: NotifyType }


pan-canvas

Pan or zoom the GM's canvas (canvas.animatePan).

Params: { x?: number, y?: number, scale?: number, duration?: number } — target pixels, zoom factor, animation length in ms.

Result: { panned: true }


ping-location

Ping a point on the canvas for every connected user, like Foundry's long-press ping.

Params:

interface PingLocationParams {
  x: number;
  y: number;
  style?: 'pulse' | 'arrow' | 'alert' | 'chevron';
  color?: string;
  duration?: number;   // ms
}

Result: { pinged: true, x: number, y: number }


Pathfinder 2e (PF2e)

PF2e commands run only when the world's game system is pf2e. Each is a fully separate implementation from the D&D 5e commands. Calling a pf2e/* command in a non-PF2e world (or a dnd5e/* / legacy bare command in a PF2e world) returns Operation '<command>' is not supported by game system '<world-system>'. PF2e API verified against pf2e 7.12.2.

Rolls

pf2e/roll-skill, pf2e/roll-save, pf2e/roll-perception — actor checks via the PF2e Statistic API. Each returns the standard RollResult (degree of success → isCritical on a critical success, isFumble on a critical failure). PF2e has no bare ability checks, so there is no pf2e/roll-ability.

interface RollSkillParams {
  actorId: string;
  skill: string;       // one of the 16 PF2e slugs: acrobatics, arcana, athletics, crafting,
                       // deception, diplomacy, intimidation, medicine, nature, occultism,
                       // performance, religion, society, stealth, survival, thievery
  showInChat?: boolean;
}

interface Pf2eRollSaveParams {
  actorId: string;
  save: "fortitude" | "reflex" | "will";
  showInChat?: boolean;
}

interface RollPerceptionParams {
  actorId: string;
  showInChat?: boolean;
}

dnd5e/roll-perception (and the neutral roll-perception) also exist for D&D 5e, rolling the prc skill.

Example:

{ "id": "10", "type": "pf2e/roll-skill", "params": { "actorId": "abc", "skill": "acrobatics" } }
{ "id": "11", "type": "pf2e/roll-save",  "params": { "actorId": "abc", "save": "reflex" } }

Conditions

interface SetConditionParams { actorId: string; slug: string; value?: number; }  // valued → exact value
interface ConditionSlugParams { actorId: string; slug: string; }                  // remove / increase / decrease
interface GetConditionsParams { actorId: string; }
  • pf2e/set-condition — apply or set a condition; for valued conditions value sets the exact value.
  • pf2e/increase-condition / pf2e/decrease-condition — raise/lower a valued condition by 1 (decrease removes at 0).
  • pf2e/remove-condition — remove the condition entirely.
  • pf2e/get-conditions — list the actor's active conditions with their values.

43 supported slugs (e.g. frightened, prone, clumsy, sickened, slowed); persistent-damage and malevolence are not supported. 12 are valued: frightened, clumsy, drained, enfeebled, sickened, stupefied, slowed, stunned, doomed, dying, wounded, cursebound.

Result (set / increase / decrease):

interface ConditionMutationResult {
  actorId: string;
  condition: { slug: string; name: string; value: number | null; active: boolean } | null;
}

Strikes (weapon attacks)

interface ListStrikesParams { actorId: string; }
interface RollStrikeParams { actorId: string; slug: string; mapIncrease?: 0 | 1 | 2; showInChat?: boolean; }
interface RollStrikeDamageParams { actorId: string; slug: string; critical?: boolean; showInChat?: boolean; }
  • pf2e/list-strikes — available strikes (slug, label, ready, variant labels).
  • pf2e/roll-strike — attack roll; mapIncrease selects the multiple-attack-penalty step (0 = none, 1 = −5, 2 = −10). Returns RollResult with degree-of-success flags.
  • pf2e/roll-strike-damage — damage roll; critical: true rolls critical damage.

Item use

PF2e has no generic "use item", so entry points are per item type.

interface UseConsumableParams { actorId: string; itemId: string; quantity?: number; }            // potions, elixirs, scrolls…
interface CastSpellParams { actorId: string; spellId: string; rank?: number; showInChat?: boolean; }  // rank = heightened level 1–10
interface PostItemParams { actorId: string; itemId: string; showInChat?: boolean; }               // post any item card to chat
  • pf2e/use-consumable — consume a consumable (decrements uses/quantity, posts to chat, applies embedded effects). Returns remaining uses/quantity.
  • pf2e/cast-spell — cast a spell via its spellcasting entry, with optional heightened rank.
  • pf2e/post-item — post any item's card to chat (returns the chat message id).

Compendium search

pf2e/filter-compendium-actors, pf2e/filter-compendium-items — specification search over compendium packs with a PF2e-native filter surface. Data paths verified against pf2e 7.12.2.

interface Pf2eFilterCompendiumActorsParams {
  packIds?: string[];      // Explicit Actor packs; omitted → all Actor packs (bestiaries)
  name?: string;
  type?: string[];         // "army" | "character" | "familiar" | "hazard" | "loot" | "npc" | "party" | "vehicle"
  level?: { min?: number; max?: number };   // Negative creature levels are valid (e.g. min: -1)
  traits?: string[];       // ALL listed traits must be present, e.g. ["undead", "incorporeal"]
  rarity?: string[];       // "common" | "uncommon" | "rare" | "unique"
  size?: string[];         // "tiny" | "sm" | "med" | "lg" | "huge" | "grg"
  maxHp?: { min?: number; max?: number };
  ac?: { min?: number; max?: number };
  limit?: number;          // 1..200, default 50
  offset?: number;
}

interface Pf2eFilterCompendiumItemsParams {
  packIds?: string[];
  name?: string;
  type?: string[];         // All 25 PF2e item types: "feat", "spell", "equipment", "weapon", "ancestry", …
  level?: { min?: number; max?: number };   // Item/feat level; spell rank for spells
  traits?: string[];       // ALL-of
  rarity?: string[];
  category?: string[];     // Feat category: "class", "skill", "general", "ancestry", …
  traditions?: string[];   // ANY-of, spells: "arcane" | "divine" | "occult" | "primal"
  priceGold?: { min?: number; max?: number };  // Price normalized to gp from the {pp,gp,sp,cp} coins object
  limit?: number;
  offset?: number;
}

Both return { results: { id, name, level, packId, uuid }[], total, hasMore } — entries carry level (the primary PF2e content-selection axis) and are sorted by level → name → id.

{ "id": "1", "type": "pf2e/filter-compendium-items",
  "params": { "type": ["feat"], "level": { "min": 1, "max": 4 }, "traits": ["fighter"], "category": ["class"] } }

Savage Worlds Adventure Edition (SWADE)

SWADE commands run only when the world's game system is swade (since 8.16.0, verified against swade 6.0.4 on Foundry v14). Each is a separate implementation under src/systems/swade/; calling a swade/* command in another world returns Operation '<command>' is not supported by game system '<world-system>'. Every actor-bound command addresses a world actor by actorId or one token's actor by tokenId (+ optional sceneId, default the active scene), so an unlinked token takes the hit alone; results echo actorId and, when given, tokenId. Errors: Actor not found: <id>, Token not found on active scene: <id>, Scene not found: <id>, Skill not found: <name or id>, Item not found: <id>, Item is not a weapon: <id>, Either actorId or tokenId is required.

Trait rolls

swade/roll-attribute and swade/roll-skill build the roll through the system (actor.rollAttribute / rollSkill / makeUnskilledAttempt with suppressChat), so the RollDialog never opens; the system still adds its own modifiers (wounds, fatigue, Distracted, conviction, global modifiers).

interface SwadeActorTargetParams {
  actorId?: string;   // either this …
  tokenId?: string;   // … or this (one token's actor)
  sceneId?: string;   // default: active scene
}

interface SwadeRollAttributeParams extends SwadeActorTargetParams {
  attribute: "agility" | "smarts" | "spirit" | "strength" | "vigor";
  modifier?: number;
  modifierLabel?: string;
  targetNumber?: number;   // default 4
  ignoreWounds?: boolean;
  showInChat?: boolean;    // default false; true posts the system's own card
}

interface SwadeRollSkillParams extends SwadeActorTargetParams {
  skill: string;           // skill item id OR name (case-insensitive)
  unskilled?: boolean;     // roll d4−2 when the actor lacks the skill (else "Skill not found")
  modifier?: number; modifierLabel?: string; targetNumber?: number; ignoreWounds?: boolean; showInChat?: boolean;
}

interface SwadeTraitRollResult {
  actorId: string; tokenId?: string;
  trait: string;                 // "vigor", the skill name, or "Unskilled"
  total: number;                 // kept die + modifiers
  formula: string;
  dice: { type: string; results: number[]; total: number; wild: boolean; kept: boolean }[];  // every die incl. explosions
  modifiers: { label: string; value: number | string }[];
  modifierTotal: number;         // total − kept die
  targetNumber: number;
  successes: number;             // −1 critical failure, 0 failure, 1 success, 2+ raises
  outcome: "critical-failure" | "failure" | "success" | "raise";
  raises: number;
  isCriticalFailure: boolean;
  wildDie: boolean;              // a wild die was in the pool (Wild Cards)
  isCritical: boolean;           // raises > 0
  isFumble: boolean;             // isCriticalFailure
  messageId?: string;            // when showInChat
}

Example:

{ "id": "1", "type": "swade/roll-attribute", "params": { "actorId": "abc", "attribute": "vigor", "targetNumber": 6 } }
{ "id": "2", "type": "swade/roll-skill", "params": { "tokenId": "tok1", "skill": "Notice", "modifier": -2, "showInChat": true } }

Attack and damage

swade/roll-attack rolls the weapon's skill (system.actions.trait; unskilled when the actor lacks it) with the weapon passed to the system, which adds its default attack modifiers (range, cover, illumination, prone) for the GM's current targets — target with set-token-target first. A melee attack on targetTokenId uses the target's Parry as the target number. No ammo is consumed.

interface SwadeRollAttackParams extends SwadeActorTargetParams {
  itemId: string;            // weapon item id
  targetTokenId?: string;
  targetNumber?: number;     // explicit > target's Parry (melee) > 4
  modifier?: number; modifierLabel?: string; showInChat?: boolean;
}

interface SwadeAttackRollResult extends SwadeTraitRollResult {
  weapon: { id: string; name: string; damage: string; ap: number; range: string; isMelee: boolean };
  target?: { tokenId: string; actorId: string; name: string; parry: number };
  hit: boolean;              // successes >= 1
}

interface SwadeRollDamageParams extends SwadeActorTargetParams {
  itemId: string;
  raise?: boolean;           // adds the weapon's bonus damage die (default +1d6x)
  modifier?: number | string;
  modifierLabel?: string;
  ap?: number;               // overrides the weapon's AP
  targetTokenId?: string;    // preview (never apply) the damage rule against that token
  showInChat?: boolean;
}

interface SwadeDamageRollResult {
  actorId: string; tokenId?: string;
  weapon: { id: string; name: string };
  total: number; formula: string;
  dice: { type: string; results: number[]; total: number; wild: false; kept: true }[];
  ap: number; isHeavyWeapon: boolean; raiseDamageIncluded: boolean;
  versusTarget?: SwadeDamagePreview;
  isCritical: false; isFumble: false;
  messageId?: string;
}

Damage and soak

swade/apply-damage applies the Toughness rule (as the system's damage applicator computes it): AP eats armor, a hit without a raise Shakes, every raise is a Wound, a second Shaken becomes a Wound unless the actor has the hardy flag, the Wound Cap setting limits Wounds to 4, Wounds past the maximum Incapacitate; vehicles are never Shaken. apply: false previews without touching the actor.

interface SwadeApplyDamageParams extends SwadeActorTargetParams {
  damage: number;            // >= 0
  ap?: number;               // default 0
  isHeavyWeapon?: boolean;
  apply?: boolean;           // default true
}

interface SwadeDamagePreview {
  toughness: number; armor: number; apApplied: number; effectiveToughness: number;
  excess: number;                     // damage − effectiveToughness (may be negative)
  outcome: "none" | "shaken" | "wounded";
  woundsInflicted: number;            // after the wound cap
  doubleShaken: boolean;
  wouldIncapacitate: boolean;
}

interface SwadeApplyDamageResult extends SwadeDamagePreview {
  actorId: string; tokenId?: string; applied: boolean;
  shakenBefore: boolean; shakenAfter: boolean;
  woundsBefore: number; woundsAfter: number; woundsMax: number;
  incapacitated: boolean;
  canSoak: boolean;                   // applied && woundsInflicted > 0 && wild card && bennies > 0
}

interface SwadeSoakParams extends SwadeActorTargetParams {
  wounds: number;            // >= 1, the wounds just inflicted
  spendBenny?: boolean;      // default true ("No bennies left: <name>" when empty)
  modifier?: number; modifierLabel?: string;
  clearIncapacitated?: boolean;   // also clear Incapacitated on a full soak
  showInChat?: boolean;
}

interface SwadeSoakResult {
  actorId: string; tokenId?: string;
  roll: SwadeTraitRollResult;         // the Vigor roll (soak bonus included)
  woundsSoaked: number; woundsRemaining: number; woundsAfter: number;
  bennySpent: boolean; benniesLeft: number;
  shakenRemoved: boolean;             // full soak also removes Shaken
}

Only Wild Cards can soak (Only Wild Cards can soak: <name>).

Example:

{ "id": "3", "type": "swade/apply-damage", "params": { "tokenId": "gob3", "damage": 12, "ap": 2 } }
{ "id": "4", "type": "swade/soak", "params": { "actorId": "abc", "wounds": 1 } }

Bennies

interface SwadeBenniesParams extends SwadeActorTargetParams {
  gm?: true;                          // the GM's bennies on game.user instead of an actor's
  action: "spend" | "give" | "refresh";
}

interface SwadeBenniesResult {
  owner: "actor" | "gm"; actorId?: string; tokenId?: string;
  action: "spend" | "give" | "refresh";
  before: number; after: number; max?: number;
}

Statuses

swade/set-status toggles one of the 32 status ids the system registers through actor.toggleActiveEffect; linked statuses (Stunned → Distracted, Prone, Vulnerable; Bound → Entangled, Distracted; Entangled → Vulnerable) come from the system and appear in statuses.

interface SwadeSetStatusParams extends SwadeActorTargetParams {
  status: "shaken" | "incapacitated" | "dead" | "aiming" | "berserk" | "wild-attack" | "defending" | "holding"
        | "bound" | "entangled" | "frightened" | "distracted" | "encumbered" | "prone" | "stunned" | "vulnerable"
        | "bleeding-out" | "diseased" | "heart-attack" | "on-fire" | "poisoned" | "cover-shield" | "cover" | "reach"
        | "torch" | "burrowing" | "flying" | "invisible" | "blind" | "cold-bodied" | "smite" | "protection";
  active: boolean;
  overlay?: boolean;
}

interface SwadeSetStatusResult { actorId: string; tokenId?: string; status: string; active: boolean; statuses: string[] }

Sheet summary

swade/get-actor-summary { actorId | tokenId, sceneId? } is a side-effect-free normalized read of the sheet. Sections an actor type lacks are omitted (vehicles have no attributes, skills or bennies; groups carry almost nothing).

interface SwadeActorSummary {
  actorId: string; tokenId?: string; name: string;
  type: "character" | "npc" | "vehicle" | "group"; wildcard: boolean;
  attributes?: Record<"agility" | "smarts" | "spirit" | "strength" | "vigor", { die: number; modifier: number; wildDie: number }>;
  skills?: { id: string; name: string; die: number; modifier: number; wildDie: number; attribute: string }[];
  derived?: { parry: number; toughness: number; armor: number; size: number; pace: number; runningDie: string | null };
  bennies?: { value: number; max: number };
  wounds?: { value: number; max: number; ignored: number; penalty: number };   // penalty from calcWoundPenalties()
  fatigue?: { value: number; max: number; penalty: number };                  // calcFatiguePenalties()
  powerPoints?: Record<string, { value: number; max: number }>;
  status: { shaken: boolean; stunned: boolean; distracted: boolean; vulnerable: boolean; entangled: boolean; bound: boolean; incapacitated: boolean; active: string[] };
  conviction?: { value: number; active: boolean };
  rank?: string;
  initiative?: { levelHeaded: boolean; improvedLevelHeaded: boolean; quick: boolean; hesitant: boolean };
  weapons: { id: string; name: string; damage: string; ap: number; rof: number; range: string; parry: number; minStr: string; trait: string; traitMod: string; dmgMod: string; equipStatus: number; shots: number; currentShots: number; isMelee: boolean; isHeavyWeapon: boolean }[];
  powers: { id: string; name: string; pp: number; arcane: string; rank: string; range: string; duration: string; damage: string }[];
  vehicle?: { toughness: number; armor: number; wounds: { value: number; max: number }; handling: number; topSpeed: { value: number; unit: string }; outOfControl: boolean; wrecked: boolean };
}

Action cards

swade/get-action-cards { combatId? } (default: the active combat, else No active combat) lists the cards the combat dealt. The neutral roll-initiative / roll-all-initiative draw action cards in a SWADE world; initiative is cardValue + suitValue / 10.

interface SwadeActionCardsResult {
  combatId: string; round: number; turn: number;
  combatants: {
    combatantId: string; name: string; actorId: string | null; tokenId: string | null;
    initiative: number | null; cardValue: number | null; suitValue: number | null; cardString: string | null;
    hasJoker: boolean; roundHeld: number | null; turnLost: boolean; isGroupLeader: boolean; isDefeated: boolean;
  }[];
}

Call of Cthulhu 7th Edition (CoC7)

CoC7 commands run only when the world's game system is CoC7 (capitals; the wire prefix stays lowercase coc7/). Since 8.17.0, verified against CoC7 8.16 on Foundry 12–14. Each is a separate implementation under src/systems/coc7/; calling a coc7/* command in another world returns Operation '<command>' is not supported by game system '<world-system>'. Every actor-bound command addresses a world actor by actorId or one token's actor by tokenId (+ optional sceneId, default the active scene); results echo actorId and, when given, tokenId.

The system keeps its check classes internal (CoC7Check, CoC7DicePool, CoC7SanCheckCard), so the module rolls the d100 pool itself through Foundry's Roll (1d10 unit, one d10 per tens die; Dice So Nice animates them) and resolves it with a port of CoC7DicePool. showInChat therefore posts the module's own card (title, threshold with difficulty, the dice, the total, the outcome; the Roll objects attached), not the system's; blind: true whispers it to the GMs. Damage, hit points, Luck, conditions and bouts of madness go through the system's public Actor API.

Errors: Either actorId or tokenId is required, Actor not found: <id>, Token not found on active scene: <id> / Token not found on scene <sceneId>: <id>, Token has no actor: <id>, Scene not found: <id>, No active scene, Skill not found: <skill>, Weapon not found: <itemId>, Item is not a weapon: <name>, Weapon has no skill: <name>, Weapon has no damage for range: <band>, Actor has no characteristics: <name>, Actor has no sanity: <name>, Actor has no luck: <name>, Actor has no hit points: <name>, Actor has no conditions: <name>, Actor type is not supported: container, bonusDice and penaltyDice are exclusive, Either damage or heal is required, damage and heal are exclusive, amount must be at least 1 for spend and add, Not enough Luck: has <n>, needs <amount>, Bout of Madness table is not configured, Target token not found: <id>.

Shared shapes

interface Coc7ActorTargetParams {
  actorId?: string;   // either this …
  tokenId?: string;   // … or a token's actor (an unlinked token takes the hit alone)
  sceneId?: string;   // scene of the token; default the active scene
}

type Coc7DifficultyWire = 'unknown' | 'regular' | 'hard' | 'extreme' | 'critical';

interface Coc7CheckOptions {
  difficulty?: Coc7DifficultyWire;   // default: the world setting defaultCheckDifficulty
  bonusDice?: number;                // 0–2, exclusive with penaltyDice
  penaltyDice?: number;              // 0–2, exclusive with bonusDice
  flatDiceModifier?: number;         // added to the rolled number (house rule)
  flatThresholdModifier?: number;    // added to the threshold (house rule)
  blind?: boolean;                   // whisper the card to GMs
  showInChat?: boolean;              // post the module's card; default false
}

interface Coc7CheckResult {
  actorId: string; tokenId?: string;
  kind: 'skill' | 'characteristic' | 'attribute' | 'weapon';
  name: string;                      // skill name, STR … EDU, Idea, Know, Luck, Sanity
  threshold: number;                 // the value rolled against, before flatThresholdModifier
  difficulty: Coc7DifficultyWire;    // effective difficulty
  thresholds: { regular: number; hard: number; extreme: number; fumbleFrom: number };  // upper bounds, 0 = level absent; fumble from 96 or 100
  dice: { tens: { value: number; kept: boolean }[]; unit: number; bonusDice: number; penaltyDice: number };  // tens 0…90 (0 = 00)
  total: number;                     // 1–100 after flatDiceModifier
  successLevel: number;              // −99 fumble, 0 failure, 1 regular, 2 hard, 3 extreme, 4 critical
  outcome: 'fumble' | 'failure' | 'regular' | 'hard' | 'extreme' | 'critical';
  isSuccess: boolean | null;         // successLevel >= difficulty; null when the difficulty is unknown
  isCritical: boolean; isFumble: boolean;
  luckRequired: { regular: number; hard: number; extreme: number };  // Luck that would raise the roll to each level
  canPush: boolean;                  // a failed, pushable roll: call again for the pushed roll
  markedForDevelopment: boolean;     // the skill got its development tick
  messageId?: string;
}

Checks

coc7/roll-skill — Coc7ActorTargetParams & Coc7CheckOptions & { skill: string } → Coc7CheckResult. skill is a skill item id, a name (Spot Hidden, Fighting (Brawl), case-insensitive) or a CoC ID (i.skill.dodge). Threshold = item.system.value. canPush needs the skill's push property, a failure and no fumble. The development tick (system.flags.developement) is set on a rolled success (no bonus or penalty dice) when xpEnabled, the difficulty is known, the roll is not blind and the skill allows XP.

coc7/roll-characteristic — … & { characteristic: 'str' | 'con' | 'siz' | 'dex' | 'app' | 'int' | 'pow' | 'edu' | 'idea' | 'know' } → Coc7CheckResult. idea rolls INT (name Idea), know rolls EDU (Know); otherwise name is the upper-case key. No development tick.

coc7/roll-attribute — … & { attribute: 'luck' | 'sanity' } → Coc7CheckResult. A plain Luck or SAN roll without any loss (the loss flow is coc7/sanity-check). Sanity is never pushable.

{ "type": "coc7/roll-skill", "params": { "actorId": "abc123", "skill": "Spot Hidden", "bonusDice": 1, "showInChat": true } }

Attacks and damage

coc7/roll-attack — Coc7ActorTargetParams & Coc7CheckOptions & { itemId: string; alternativeSkill?: boolean } → Coc7AttackRollResult. itemId is a weapon item id or name. The check runs against the weapon's main skill, or the alternative skill (burst, automatic fire, thrown) with alternativeSkill: true. No target is resolved and no ammo is consumed.

interface Coc7AttackRollResult extends Coc7CheckResult {
  weapon: {
    id: string; name: string;
    skill: { id: string; name: string; value: number };
    ranged: boolean; malfunction?: number; impaling: boolean;
    damage: { normal: string; long?: string; extreme?: string };   // damage bonus already applied, e.g. "1d6+1d4"
  };
  isMalfunction: boolean;   // total >= weapon.malfunction
  impale: boolean;          // extreme or critical success with an impaling weapon
}

coc7/roll-damage — Coc7ActorTargetParams & { itemId: string; range?: 'normal' | 'long' | 'extreme'; successLevel?: 'regular' | 'hard' | 'extreme'; targetTokenId?: string; showInChat?: boolean } → Coc7DamageRollResult. The formula is the band's damage plus the damage bonus (addb → the actor's full db, ahdb → half of it as the system's halfDB computes it). successLevel: 'extreme' maximizes the formula; an impaling weapon then adds a normal damage roll on top (the system leaves the damage bonus out of that extra roll). targetTokenId previews the total against the target's armor, a number or a rolled formula; nothing is applied. showInChat posts the Foundry roll with the flavor <weapon> damage (<band>).

interface Coc7DamageRollResult {
  actorId: string; tokenId?: string;
  weapon: { id: string; name: string };
  range: 'normal' | 'long' | 'extreme';
  formula: string;
  damageBonus: string;      // the term appended: "+1d4", "+1D2", "-1" or ""
  impale: boolean; maximized: boolean;
  dice: { formula: string; results: number[]; total: number }[];
  total: number;
  versusTarget?: { tokenId: string; name: string; armor: number; armorFormula?: string; wouldInflict: number };
  messageId?: string;
}

coc7/apply-damage — Coc7ActorTargetParams & { damage?: number; heal?: number; ignoreArmor?: boolean; armor?: string | number } → Coc7ApplyDamageResult. Exactly one of damage (≥ 1, before armor) or heal (≥ 1, capped at the maximum). Damage goes through the system's actor.dealDamage (armor subtracted unless ignoreArmor; armor overrides the sheet's value; Major Wound at damage ≥ half the maximum, with Prone; Unconscious / Dying at 0 HP; Dead at damage ≥ the maximum; under the pulp rule a CON check card instead). Healing goes through setHp. Vehicles have no hit points here.

interface Coc7Conditions { criticalWounds: boolean; unconscious: boolean; dying: boolean; dead: boolean; prone: boolean; tempoInsane: boolean; indefInsane: boolean }

interface Coc7ApplyDamageResult {
  actorId: string; tokenId?: string;
  applied: number;          // damage that went through armor
  armorAbsorbed: number;
  healed: number;
  hpBefore: number; hpAfter: number; hpMax: number;
  majorWound: boolean; unconscious: boolean; dying: boolean; dead: boolean;
  conditions: Coc7Conditions;
}

Sanity

coc7/sanity-check — Coc7ActorTargetParams & { lossPassed: string | number; lossFailed: string | number; reason?: string; difficulty?: Coc7DifficultyWire; bonusDice?: number; penaltyDice?: number; apply?: boolean; showInChat?: boolean } → Coc7SanityCheckResult. The system's SAN check card flow without the card: the SAN roll (against attribs.san.value), then the loss — lossPassed on a success, lossFailed on a failure, the maximum of lossFailed on a fumble — capped by what the reason (a creature name) can still inflict (maxLossToSanReason), halved for a Mythos hardened investigator. apply (default true) writes what the system's setSan would, without its spend-Luck dialog: SAN, dailyLoss, the reason's running total, Temporary Insanity at 5+ in one go, Indefinite Insanity when dailyLoss reaches a dailyLimit above 0. Immune during a Bout of Madness (immune: 'bout') or when the reason is exhausted ('reason').

interface Coc7SanityCheckResult {
  actorId: string; tokenId?: string;
  roll: Coc7CheckResult;                 // kind 'attribute', name 'Sanity'
  immune: 'none' | 'bout' | 'reason';
  lossFormula: string; lossRolled: number;
  lossApplied: number;                   // after the per-reason cap and Mythos hardening
  limitedByReason: boolean; mythosHardened: boolean;
  applied: boolean;                      // false with apply: false (sanAfter / dailyLoss are then projections)
  sanBefore: number; sanAfter: number; sanMax: number;
  dailyLoss: number; dailyLimit: number;
  temporaryInsanity: boolean;            // lost 5+ at once: an INT roll decides the bout of madness
  indefiniteInsanity: boolean; permanentInsanity: boolean;
  luckOfferSkipped: boolean;             // the pulp rule "Luck halves SAN loss" is on and was not offered
  messageId?: string;
}

coc7/bout-of-madness — Coc7ActorTargetParams & { realTime?: boolean; duration?: number } → Coc7BoutOfMadnessResult. realTime (default true) draws from the world's real-time bout table (rounds), false from the summary table (hours); an unset table ('none') or one that does not resolve answers Bout of Madness table is not configured. Calls the system's actor.enterBoutOfMadness, which rolls the table, adds the drawn phobia or mania as a status item and sets Temporary Insanity for a real-time bout.

interface Coc7BoutOfMadnessResult {
  actorId: string; tokenId?: string;
  name: string; description: string;    // the table result, enriched HTML flattened to text
  phobia: boolean; mania: boolean;
  realTime: boolean; duration: number;
  tempoInsane: boolean;
}

Luck, conditions and the sheet

coc7/luck — Coc7ActorTargetParams & { action: 'spend' | 'set' | 'add'; amount: number } → { actorId, tokenId?, action, before, after }. spend goes through spendLuck (refused with Not enough Luck: has <n>, needs <amount>), set and add through setLuck (capped at 99). amount is an integer ≥ 0 (≥ 1 for spend / add).

coc7/set-condition — Coc7ActorTargetParams & { condition: 'criticalWounds' | 'unconscious' | 'dying' | 'dead' | 'prone' | 'tempoInsane' | 'indefInsane'; active: boolean; realTime?: boolean; duration?: number } → { actorId, tokenId?, conditions: Coc7Conditions, statuses: { id, name, phobia, mania }[] }. active: true calls conditionsSet (with realTime / duration, which only matter for tempoInsane), false calls conditionsUnset; the system creates or deletes the status effect when status icons are enabled and sets the sheet flag otherwise. statuses lists the actor's phobia and mania items.

coc7/get-actor-summary — Coc7ActorTargetParams → Coc7ActorSummary (read-only).

interface Coc7ActorSummary {
  id: string; name: string; type: string;
  characteristics: { key: 'str' | 'con' | 'siz' | 'dex' | 'app' | 'int' | 'pow' | 'edu'; label: string; value: number; half: number; fifth: number }[];
  attributes?: {                       // absent for vehicles
    hp: { value: number; max: number }; mp: { value: number; max: number };
    sanity: { value: number; max: number; dailyLoss: number; dailyLimit: number; mythosHardened: boolean };
    luck: number; mov: number; db: string; build: number; armor: string;
  };
  conditions: Coc7Conditions;
  statuses: { id: string; name: string; phobia: boolean; mania: boolean }[];
  skills: { id: string; name: string; value: number; half: number; fifth: number; base: string; specialization?: string; push: boolean; fighting: boolean; firearm: boolean; special: boolean; rare: boolean; development: boolean }[];   // sorted by name
  weapons: { id: string; name: string; skill?: { id; name; value }; alternativeSkill?: { id; name; value }; ranged: boolean; damage: { normal: string; long?: string; extreme?: string }; range: { normal: string; long?: string; extreme?: string }; usesPerRound: string; bullets?: number; ammo?: number; malfunction?: number; impaling: boolean; properties: string[] }[];
  spells: { id: string; name: string; costs: { hitPoints: string; magicPoints: string; sanity: string; power: string; others: string } }[];
  infos: { occupation: string; age: string; archetype?: string; organization?: string };
  creditRating?: number;               // the Credit Rating skill value when present
  creature?: { sanLoss: { passed: string; failed: string }; attacksPerRound: string };   // npc / creature
  vehicle?: { hp?: number; mov?: number; build?: number; armor?: number; crew: string; armed: boolean };
}

Containers answer Actor type is not supported: container.

Out of scope

Opposed and combined rolls, melee defense resolution, maneuvers, multiple-target or automatic fire, reload and ammunition, pushing as a system state (the agent re-rolls; canPush says when), the development phase, chases, the spend-Luck offer of the pulp SAN rule, Idea-roll clue handling, the system's own chat cards.


Error Handling

On error, response includes success: false and error message:

{
  "id": "123",
  "success": false,
  "error": "Actor not found: invalid-id"
}

Common errors:

  • Actor/Item/Scene/Combat not found
  • Invalid parameters
  • Combat not started (for turn navigation)
  • Permission denied

Clone this wiki locally