fvtt-data-migrations
This skill should be used when moving data between storage locations, changing data structures, renaming fields, or removing deprecated data. Covers schema versioning, safe migration methods, the Foundry unset operator, and idempotent migrations.
What this skill does
# Foundry VTT Data Migrations
Implement safe, version-controlled data migrations for Foundry VTT modules when changing data structures or storage locations.
## When to Use This Skill
Invoke this skill when implementing changes that require migrating existing user data:
### ✅ ALWAYS Create Migration For:
1. **Moving data between storage locations:**
- `actor.system.field` → `actor.flags.myModule.field`
- `actor.system.field` → embedded item
- Flag namespace changes
2. **Changing data structures:**
- Array → Object/Map
- Object → Array
- Flat structure → nested structure
- Renaming properties
3. **Changing data types:**
- String → Number
- Boolean → String (enum)
- Single value → Array
- Null semantics changes
4. **Removing deprecated data:**
- Cleaning up orphaned flags
- Removing obsolete system fields
- Purging invalid data
5. **Foundry version compatibility:**
- API changes between Foundry versions
- Deprecation of core fields
### ❌ NO Migration Needed For:
1. **Additive changes only:**
- Adding NEW optional fields (with defaults)
- Adding NEW features that don't touch existing data
- UI-only changes (CSS, templates without data changes)
2. **Non-breaking changes:**
- Adding new flags alongside existing ones
- Extending data without modifying existing structure
- Backwards-compatible additions
## Foundry Migration System Overview
### Schema Version Pattern
Foundry modules track data structure versions using a `schemaVersion` setting:
```javascript
// In settings.js - register the version tracker
game.settings.register("my-module", "schemaVersion", {
name: "Schema Version",
scope: "world", // World-level (not per-client)
config: false, // Hidden from UI
type: Number,
default: 0, // 0 = never migrated
});
```
**How it works:**
1. New worlds start at version 0
2. Each migration increments the version (1, 2, 3...)
3. Migration runs ONCE per world, on first `ready` hook
4. If `currentVersion >= targetVersion`, migration is skipped
### Migration Lifecycle
```
World Created (v0)
↓
First Load → ready hook
↓
Migration.migrate() called
↓
Check: currentVersion (0) < targetVersion (1)?
↓ YES
Run migration steps
↓
Set schemaVersion = 1
↓
Show completion notification
↓
Future loads: currentVersion (1) >= targetVersion (1) → SKIP
```
## Step-by-Step Migration Workflow
### Step 1: Identify the Breaking Change
**Ask yourself:**
- What data structure is changing?
- Where is the old data stored? (system field? flag? embedded doc?)
- Where will the new data be stored?
- What transformation is needed?
**Example scenarios:**
```javascript
// Scenario A: Moving system field to flag
OLD: actor.system["background-details"]
NEW: actor.flags["bitd-alternate-sheets"].background_details
// Scenario B: Changing structure
OLD: actor.flags.myModule["equipped-items"] = [{ id: "abc" }, { id: "def" }]
NEW: actor.flags.myModule["equipped-items"] = { "abc": {...}, "def": {...} }
// Scenario C: Cleaning up orphaned data
OLD: actor.flags.myModule.abilityProgress = { "Reflexes": 2, "abc123": 1 }
NEW: actor.flags.myModule.abilityProgress = { "abc123": 1 } // Remove name-based keys
```
### Step 2: Increment Target Schema Version
**In `scripts/migration.js`:**
```javascript
export class Migration {
static async migrate() {
const currentVersion = game.settings.get(MODULE_ID, "schemaVersion") || 0;
const targetVersion = 2; // ← INCREMENT THIS (was 1, now 2)
if (currentVersion < targetVersion) {
// ... migration logic
}
}
}
```
**Version numbering:**
- Start: `targetVersion = 1` (first migration)
- Each new migration: increment by 1 (2, 3, 4...)
- Never skip numbers
- Never decrement
### Step 3: Create Migration Method
**Add a new static method to `Migration` class:**
```javascript
static async migrateFieldName(actor) {
const oldValue = foundry.utils.getProperty(actor, "system.old-field");
const newValue = actor.getFlag(MODULE_ID, "new_field");
// Check if migration needed
if (!oldValue) return; // No old data to migrate
if (newValue) {
// New data already exists - just clean up old
await actor.update({ "system.-=old-field": null });
return;
}
// Perform migration
const updates = {};
updates[`flags.${MODULE_ID}.new_field`] = oldValue;
updates["system.-=old-field"] = null;
await actor.update(updates);
console.log(`[MyModule] Migrated old-field for ${actor.name}`);
}
```
**Migration method naming:**
- `migrate{FeatureName}` - Descriptive of what's being migrated
- Examples: `migrateEquippedItems`, `migrateAbilityProgress`, `migrateLegacyFields`
### Step 4: Call Migration Method in migrate()
**Add to the migration sequence:**
```javascript
static async migrate() {
const currentVersion = game.settings.get(MODULE_ID, "schemaVersion") || 0;
const targetVersion = 2;
if (currentVersion < targetVersion) {
ui.notifications.info(
"My Module: Migrating data, please wait..."
);
for (const actor of game.actors) {
if (actor.type !== "character") continue;
// Step 1: Existing migration
await this.migrateEquippedItems(actor);
// Step 2: NEW migration (add here)
await this.migrateFieldName(actor);
}
await game.settings.set(MODULE_ID, "schemaVersion", targetVersion);
ui.notifications.info("My Module: Migration complete.");
}
}
```
**Order considerations:**
- Dependencies first (if migration B relies on migration A, run A first)
- Independent migrations can be in any order
- Comment the purpose of each step
### Step 5: Test Migration
**Testing checklist:**
1. **Create test world with OLD data structure:**
- Use console to create old-format data: `game.actors.getName("Test").update({ "system.old-field": "test" })`
- Verify old data exists
2. **Trigger migration:**
- Reload world (triggers `ready` hook)
- Watch console for migration logs
- Check for errors
3. **Verify migration results:**
- Check new data exists: `game.actors.getName("Test").getFlag("my-module", "new_field")`
- Check old data removed: `game.actors.getName("Test").system["old-field"]` (should be undefined)
- Verify notification appeared
4. **Verify migration runs ONCE:**
- Reload world again
- Check console - should NOT see migration logs (already at target version)
5. **Test with multiple actors:**
- Create several test actors with old data
- Verify all migrate correctly
## Safe Migration Patterns
### Pattern 1: System Field → Flag
```javascript
static async migrateLegacyFields(actor) {
const updates = {};
let changed = false;
// Get old and new locations
const oldValue = foundry.utils.getProperty(actor, "system.old-field");
const newValue = actor.getFlag(MODULE_ID, "new_field");
if (oldValue && !newValue) {
// Old exists, new doesn't - migrate
updates[`flags.${MODULE_ID}.new_field`] = oldValue;
updates["system.-=old-field"] = null;
changed = true;
} else if (oldValue && newValue) {
// Both exist - favor new, clean up old
updates["system.-=old-field"] = null;
changed = true;
}
if (changed) {
await actor.update(updates);
console.log(`[MyModule] Migrated old-field for ${actor.name}`);
}
}
```
**Key points:**
- Check both old and new locations
- Handle case where both exist (favor new)
- Handle case where only old exists (migrate)
- Batch updates into single `actor.update()` call
### Pattern 2: Array → Object/Map
```javascript
static async migrateEquippedItems(actor) {
const equipped = actor.getFlag(MODULE_ID, "equipped-items");
// Check if still in old format (array)
if (Array.isArray(equipped)) {
const newMap = {};
for (constRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.