Compendium entries: all five kinds
Capture actor, item, journal, scene and deck resources with authorized reads and GM imports.
api.compendiums.add captures a resource that already exists in the campaign. kind selects its resource category; resourceId is its real UUID. The server stores a snapshot with dependencies in a portable bundle. It does not accept arbitrary sheet JSON instead of resourceId.
Run these examples in a module mounted in a disposable campaign using its api and signal. You must be GM, have the source documents in the same campaign and choose an unlocked table-created compendium. Native and installed-system compendiums are read only. Create the destination once and keep created.id as packId:
export async function createExamplePack(api, signal) {
const created = await api.compendiums.create({ name: "Beacon Collection" }, {
requestId: crypto.randomUUID(), signal,
});
return created.id;
}kind: "actor": Actor snapshot
Use the UUID of a Beacon Scout actor created with the example system character type. The snapshot preserves actor type and data.
export async function storeActor(api, packId, resourceId, signal, requestId = crypto.randomUUID()) {
const added = await api.compendiums.add({
packId,
kind: "actor",
resourceId,
name: "Beacon Scout",
}, { requestId, signal });
return api.compendiums.read({ packId, id: added.id }, { signal });
}Expected result: storeActor(api, packId, resourceId, signal) returns an entry with kind: "actor", id, name, data and version. The source remains in the campaign.
Validate: inspect the compendium entry; import a copy in a compatible test campaign and open the created document.
kind: "item": Item snapshot
Use the UUID of a Beacon Rope item with the gear type registered by the campaign system. The snapshot preserves item type and data.
export async function storeItem(api, packId, resourceId, signal, requestId = crypto.randomUUID()) {
const added = await api.compendiums.add({
packId,
kind: "item",
resourceId,
name: "Beacon Rope",
}, { requestId, signal });
return api.compendiums.read({ packId, id: added.id }, { signal });
}Expected result: storeItem(api, packId, resourceId, signal) returns an entry with kind: "item", id, name, data and version. The source remains in the campaign.
Validate: inspect the compendium entry; import a copy in a compatible test campaign and open the created document.
kind: "journal": Journal snapshot
Use the UUID of the Beacon Notes journal. The same kind accepts native journal document types; a quest still uses kind journal when stored in a compendium.
export async function storeJournal(api, packId, resourceId, signal, requestId = crypto.randomUUID()) {
const added = await api.compendiums.add({
packId,
kind: "journal",
resourceId,
name: "Beacon Notes",
}, { requestId, signal });
return api.compendiums.read({ packId, id: added.id }, { signal });
}Expected result: storeJournal(api, packId, resourceId, signal) returns an entry with kind: "journal", id, name, data and version. The source remains in the campaign.
Validate: inspect the compendium entry; import a copy in a compatible test campaign and open the created document.
kind: "scene": Scene snapshot
Use the UUID of the campaign scene Beacon Harbor. The read body may show only its name; the portable bundle retains the exported resource and dependencies.
export async function storeScene(api, packId, resourceId, signal, requestId = crypto.randomUUID()) {
const added = await api.compendiums.add({
packId,
kind: "scene",
resourceId,
name: "Beacon Harbor",
}, { requestId, signal });
return api.compendiums.read({ packId, id: added.id }, { signal });
}Expected result: storeScene(api, packId, resourceId, signal) returns an entry with kind: "scene", id, name, data and version. The source remains in the campaign.
Validate: inspect the compendium entry; import a copy in a compatible test campaign and open the created document.
kind: "deck": Deck snapshot
Use the UUID of the existing Beacon Signals deck from api.cards.state().decks. Do not use a deck_definition_id: add expects a deck instance.
export async function storeDeck(api, packId, resourceId, signal, requestId = crypto.randomUUID()) {
const added = await api.compendiums.add({
packId,
kind: "deck",
resourceId,
name: "Beacon Signals",
}, { requestId, signal });
return api.compendiums.read({ packId, id: added.id }, { signal });
}Expected result: storeDeck(api, packId, resourceId, signal) returns an entry with kind: "deck", id, name, data and version. The source remains in the campaign.
Validate: inspect the compendium entry; import a copy in a compatible test campaign and open the deck to check its cards.
Reading and importing are different actions
state lists metadata without bodies or portable bundles. read returns authorized data without creating documents. import creates a copy and requires GM. Use the entry id returned by an example above:
export async function importExampleEntry(api, packId, entryId, signal) {
return api.compendiums.import({ packId, id: entryId }, {
requestId: crypto.randomUUID(), signal,
});
}The import destination is the mounted block's campaign; this call has no field for choosing a different campaign. To test another campaign, use a block mounted there and its own resource and compendium IDs. Each logical action needs its own UUID; retain it and pass the function's fifth argument only when retrying that same add after transport failure. If add committed but read failed, inspect state/read before adding again: these calls are separate transactions.
Errors and validation
Writes require GM. Resources from another campaign and inaccessible packs are unavailable; locked packs reject editing; an unknown kind rejects add. A mismatched system rejects import. Do not treat a failed read as empty content or force versions. For configure/remove/delete, read the pack/entry version and use the operation's documented version field.
Validate every kind with a real source, then try the same add as a player, with a locked pack and with another campaign's UUID. Confirm that the original is unchanged and the imported copy retains its resources. A player with a valid read grant may read; importing remains a GM action.
How to use AI to build a snapshot library
Inspect frontend.json and compendiums/services.py on the target release. Extend storeScene with a readable scene selector and an unlocked GM compendium selector. Use a real resourceId, a new requestId per action and read the result. Handle errors, empty state and retry without duplicate captures. Do not send arbitrary scene JSON to add or replace the bundle with a name.