This skill should be used when adding features that update actors or items, implementing hook handlers, modifying update logic, or replacing embedded documents. Covers ownership guards, no-op checks, batched updates, queueUpdate wrapper, atomic document operations, and letting Foundry handle renders automatically for multi-client sync.
View on GitHubImproperSubset/hh-agentics
fvtt-dev
fvtt-dev/skills/fvtt-performance-safe-updates/SKILL.md
January 21, 2026
Select agents to install to:
npx add-skill https://github.com/ImproperSubset/hh-agentics/blob/main/fvtt-dev/skills/fvtt-performance-safe-updates/SKILL.md -a claude-code --skill fvtt-performance-safe-updatesInstallation paths:
.claude/skills/fvtt-performance-safe-updates/# Foundry VTT Performance-Safe Updates
Ensure document updates in Foundry VTT modules don't cause multi-client update storms or render cascades.
## When to Use This Skill
Invoke this skill when implementing ANY of the following in a Foundry VTT module:
- Adding a new feature that updates actors or items
- Modifying existing update logic
- Adding UI elements that trigger document changes
- Implementing hook handlers that respond to document changes
- Replacing or swapping embedded documents (abilities, items, effects)
## Core Problem
Foundry VTT runs in multi-client sessions where hooks fire on ALL connected clients. Without proper guards:
- Every client triggers duplicate updates (2-10x redundant database writes)
- Update storms occur when updates trigger more updates across clients
- UI flickers when delete+create patterns cause "empty state" renders between operations
- Performance degrades exponentially with number of connected clients
## The Performance-Safe Pattern
### Step 1: Ownership Guards
**Before any document update, ask: "Should this run on every client?"**
```javascript
// ❌ BAD: Runs on every connected client
Hooks.on("deleteItem", (item, options, userId) => {
item.parent.update({ "system.someField": newValue });
});
// ✅ GOOD: Only owner/GM performs the update
Hooks.on("deleteItem", (item, options, userId) => {
if (!item.parent?.isOwner) return;
item.parent.update({ "system.someField": newValue });
});
```
**Common ownership checks:**
- `item.isOwner` - Current user owns this item
- `item.parent?.isOwner` - Current user owns the parent (actor/container)
- `actor.isOwner` - Current user owns this actor
- `game.user.isGM` - Current user is the GM
**Use GM-only guards for:**
- World-level changes
- Compendium updates
- Global settings modifications
### Step 2: Skip No-Op Updates
**Before calling update, check if the value actually changes:**
```javascript
// ❌ BAD: Always updates, even if value unchanged
await actor.update({ "system.