This skill should be used when creating compendium packs, registering packs in manifests, importing/exporting documents, querying pack contents, or using the CLI for pack management and version control workflows.
Manages Foundry VTT compendium packs for module development. Use when creating packs, registering them in manifests, importing/exporting documents, or using CLI tools for version control workflows.
/plugin marketplace add ImproperSubset/hh-agentics/plugin install fvtt-dev@hh-agenticsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Domain: Foundry VTT Module/System Development Status: Production-Ready Last Updated: 2026-01-05
Compendium packs store pre-built content (actors, items, journal entries, etc.) for distribution with modules and systems. Understanding pack management is essential for content creation.
{
"id": "my-module",
"packs": [
{
"name": "monsters",
"label": "Monsters",
"type": "Actor",
"path": "./packs/monsters",
"system": "dnd5e"
},
{
"name": "items",
"label": "Magic Items",
"type": "Item",
"path": "./packs/items"
}
]
}
Actor - Characters, NPCs, creaturesItem - Equipment, spells, featuresJournalEntry - Lore, handoutsRollTable - Random tablesScene - Maps and encountersMacro - Executable scriptsPlaylist - Audio collectionsCards - Card decksAdventure - Mixed content bundlesmy-module/
├── module.json
├── packs/
│ ├── monsters/ # LevelDB folder (V11+)
│ └── items/
└── src/
└── packs/ # JSON/YAML source (for version control)
const pack = game.packs.get("my-module.monsters");
// Check properties
console.log(pack.locked); // Edit lock status
console.log(pack.visible); // User visibility
console.log(pack.metadata); // Pack configuration
// Get minimal cached data
const index = await pack.getIndex();
for (const entry of index) {
console.log(entry._id, entry.name);
}
const actor = await pack.getDocument(documentId);
console.log(actor.name, actor.system);
// All documents (expensive for large packs)
const allDocs = await pack.getDocuments();
// Filtered query
const npcs = await pack.getDocuments({ type: "npc" });
const results = await pack.search({
query: "dragon",
fields: ["name", "system.description"]
});
// From pack to world
const pack = game.packs.get("my-module.monsters");
const doc = await pack.getDocument(docId);
const imported = await Actor.create(doc.toObject());
// Requires unlocked pack
const pack = game.packs.get("my-module.monsters");
if (!pack.locked) {
await pack.importDocument(existingActor);
}
await pack.importAll({
folderName: "Imported Monsters",
keepId: true // Preserve document IDs
});
npm install @foundryvtt/foundryvtt-cli --save-dev
# Unpack to JSON/YAML
fvtt package unpack -n "monsters" \
--outputDirectory "./src/packs/monsters" \
--yaml \
--folders \
--omitVolatile
# Pack back to LevelDB
fvtt package pack -n "monsters" \
--inputDirectory "./src/packs/monsters" \
--outputDirectory "./packs"
import { extractPack, compilePack } from "@foundryvtt/foundryvtt-cli";
// Extract
await extractPack({
packName: "monsters",
outputDir: "./src/packs",
yaml: true,
omitVolatile: true
});
// Compile
await compilePack({
packName: "monsters",
inputDir: "./src/packs",
outputDir: "./packs"
});
# Treat LevelDB as binary
packs/** binary
src/packs/fvtt package pack before commitThese fields change on access and should be omitted:
// Use --omitVolatile flag
_stats.createdTime
_stats.modifiedTime
_stats.lastModifiedBy
_stats.systemVersion
_stats.coreVersion
async function addToPack(packId, documentData) {
const pack = game.packs.get(packId);
if (pack.locked) {
ui.notifications.warn("Pack is locked");
return null;
}
return await pack.importDocument(
new Actor(documentData)
);
}
async function findByName(packId, searchName) {
const pack = game.packs.get(packId);
const index = await pack.getIndex();
return index.filter(entry =>
entry.name.toLowerCase().includes(searchName.toLowerCase())
);
}
async function importWithFolder(packId, folderName) {
const pack = game.packs.get(packId);
// Create folder if needed
let folder = game.folders.find(f =>
f.name === folderName && f.type === pack.metadata.type
);
if (!folder) {
folder = await Folder.create({
name: folderName,
type: pack.metadata.type
});
}
// Import all to folder
const docs = await pack.getDocuments();
for (const doc of docs) {
const data = doc.toObject();
data.folder = folder.id;
await doc.constructor.create(data);
}
}
// WRONG - returns promise, not document
const doc = pack.getDocument(id);
console.log(doc.name); // undefined!
// CORRECT
const doc = await pack.getDocument(id);
console.log(doc.name); // "Dragon"
// WRONG - will fail silently or error
await pack.importDocument(actor);
// CORRECT - check lock first
if (!pack.locked) {
await pack.importDocument(actor);
} else {
ui.notifications.warn("Unlock the pack first");
}
// BAD - memory issues with large packs
const all = await pack.getDocuments();
// BETTER - use index for listings
const index = await pack.getIndex();
// Only load specific documents when needed
// WARNING: Module updates overwrite pack contents
// Never store user-created content in module packs
// Use world compendiums for user content
// WRONG - pack won't work with system
{
"name": "items",
"type": "Item",
"path": "./packs/items"
}
// CORRECT - include system for typed content
{
"name": "items",
"type": "Item",
"path": "./packs/items",
"system": "dnd5e"
}
// V11+ uses folders, not .db files
// WRONG
"path": "./packs/monsters.db"
// CORRECT
"path": "./packs/monsters"
system field for system-specific content--omitVolatile when extracting for gitpack.locked before modifications.gitattributes for binary packsLast Updated: 2026-01-05 Status: Production-Ready Maintainer: ImproperSubset
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.