| name | fvtt-compendiums |
| description | 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. |
Foundry VTT Compendium Packs
Domain: Foundry VTT Module/System Development Status: Production-Ready Last Updated: 2026-01-05
Overview
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.
When to Use This Skill
- Creating content packs for distribution
- Registering packs in module/system manifests
- Importing/exporting documents programmatically
- Setting up version control workflows with CLI
- Querying and searching pack contents
Pack Registration
Manifest Configuration
{
"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"
}
]
}
Valid Document Types
Actor- Characters, NPCs, creaturesItem- Equipment, spells, featuresJournalEntry- Lore, handoutsRollTable- Random tablesScene- Maps and encountersMacro- Executable scriptsPlaylist- Audio collectionsCards- Card decksAdventure- Mixed content bundles
Directory Structure
my-module/
├── module.json
├── packs/
│ ├── monsters/ # LevelDB folder (V11+)
│ └── items/
└── src/
└── packs/ # JSON/YAML source (for version control)
Accessing Packs
Get Pack Reference
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
Load Index (Lightweight)
// Get minimal cached data
const index = await pack.getIndex();
for (const entry of index) {
console.log(entry._id, entry.name);
}
Get Single Document
const actor = await pack.getDocument(documentId);
console.log(actor.name, actor.system);
Get Multiple Documents
// All documents (expensive for large packs)
const allDocs = await pack.getDocuments();
// Filtered query
const npcs = await pack.getDocuments({ type: "npc" });
Search Contents
const results = await pack.search({
query: "dragon",
fields: ["name", "system.description"]
});
Import/Export
Import Document to World
// 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());
Export Document to Pack
// Requires unlocked pack
const pack = game.packs.get("my-module.monsters");
if (!pack.locked) {
await pack.importDocument(existingActor);
}
Bulk Import
await pack.importAll({
folderName: "Imported Monsters",
keepId: true // Preserve document IDs
});
CLI Workflow
Installation
npm install @foundryvtt/foundryvtt-cli --save-dev
Extract for Version Control
# Unpack to JSON/YAML
fvtt package unpack -n "monsters" \
--outputDirectory "./src/packs/monsters" \
--yaml \
--folders \
--omitVolatile
Repack for Distribution
# Pack back to LevelDB
fvtt package pack -n "monsters" \
--inputDirectory "./src/packs/monsters" \
--outputDirectory "./packs"
Programmatic API
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"
});
Version Control Setup
Git Attributes
# Treat LevelDB as binary
packs/** binary
Recommended Workflow
- Development: Edit JSON/YAML in
src/packs/ - Build: Run
fvtt package packbefore commit - Commit: Include both source and compiled packs
- Release: LevelDB packs ready for distribution
Volatile Fields
These fields change on access and should be omitted:
// Use --omitVolatile flag
_stats.createdTime
_stats.modifiedTime
_stats.lastModifiedBy
_stats.systemVersion
_stats.coreVersion
Common Patterns
Safe Pack Modification
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)
);
}
Filter Index by Name
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())
);
}
Import with Folder
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);
}
}
Common Pitfalls
1. Forgetting Async/Await
// 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"
2. Modifying Locked Packs
// 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");
}
3. Loading All Documents
// 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
4. User Data Overwrite
// WARNING: Module updates overwrite pack contents
// Never store user-created content in module packs
// Use world compendiums for user content
5. Missing System Field
// 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"
}
6. Wrong Pack Path
// V11+ uses folders, not .db files
// WRONG
"path": "./packs/monsters.db"
// CORRECT
"path": "./packs/monsters"
Implementation Checklist
- Register packs in manifest with correct type
- Include
systemfield for system-specific content - Use folders (not .db files) for V11+ packs
- Set up CLI for version control workflow
- Use
--omitVolatilewhen extracting for git - Check
pack.lockedbefore modifications - Use index for listings, documents for details
- Test import/export in fresh world
- Add
.gitattributesfor binary packs
References
- Compendium Packs Article
- CompendiumCollection API
- Content Packaging Guide
- V11 LevelDB Changes
- CLI Package
Last Updated: 2026-01-05 Status: Production-Ready Maintainer: ImproperSubset