HomeDeveloper docsCompendium packs: 3 installed and 7 local types
Examples by kind 9 min read

Compendium packs: 3 installed and 7 local types

Complete original JSON for every pack type, exact manifest restrictions and downloadable fixtures.

A pack uses type, called a “pack kind” in documentation. It differs from entry.kind returned by the directory and from entry.type, the system's document type. Do not put kind at the root of manifest.json.

There are two origins with different limits: collections supplied by an installed system package and local catalogs prepared by the operator. All Beacon data below is original and synthetic.

Pack typeDirectory kindInstalled systemLocal catalog
actor_packactorYesYes
item_packitemYesYes
spell_packitemYesYes
journal_packjournalNoYes
scene_packsceneNoYes
card_packdeckNoYes
deck_packdeckNoYes

Installed-system preparation

Use a target release supporting content.compendiums, available since VTT 0.2.1. The system must be installed, active at the campaign's exact pinned version and not revoked. Start from the minimal package and use this manifest. It explicitly registers character, gear and spell; these names belong to the example system.

json
{
  "id": "example.beacon",
  "name": "Beacon Example System",
  "version": "1.0.0",
  "sdk": {
    "requires": ">=1.0.0 <2.0.0",
    "tested": "1.0.0"
  },
  "entry": "main.js",
  "description": "Original example document types and compendium data.",
  "author": "Example author",
  "license": "MIT",
  "system": {
    "actorTypes": [
      {
        "id": "character",
        "label": "Character"
      }
    ],
    "itemTypes": [
      {
        "id": "gear",
        "label": "Gear"
      },
      {
        "id": "spell",
        "label": "Spell"
      }
    ]
  },
  "content": {
    "compendiums": [
      "compendiums/beacon.json"
    ]
  }
}

Place the collection at compendiums/beacon.json inside the same package. Download the three-pack collection. The minimal main.js below only demonstrates types and data; add sheets through the SDK surfaces. Include the chosen license, customize author/name and follow Systems and modules for ZIP installation or signing. Do not modify release files after installation.

javascript
export default {
  start() {},
  register() {},
  stop() {},
};

Installed system: actor_pack

Imports Beacon Scout as a character actor with stamina 6.

This is a complete single-pack collection; use it alone or combine packs in the downloadable collection packs array.

json
{
  "name": "Beacon Examples",
  "packs": [
    {
      "id": "beacon-actors",
      "label": "Beacon Actors",
      "type": "actor_pack",
      "entries": [
        {
          "id": "beacon-scout",
          "name": "Beacon Scout",
          "type": "character",
          "systemId": "example.beacon",
          "data": {
            "stamina": 6
          }
        }
      ]
    }
  ]
}

Validate: locate beacon-scout in the system pack and import as GM. The directory shows kind: "actor"; the imported document has type: "character".

Installed system: item_pack

Imports Beacon Rope as a gear item with length 10.

This is a complete single-pack collection; use it alone or combine packs in the downloadable collection packs array.

json
{
  "name": "Beacon Examples",
  "packs": [
    {
      "id": "beacon-items",
      "label": "Beacon Items",
      "type": "item_pack",
      "entries": [
        {
          "id": "beacon-rope",
          "name": "Beacon Rope",
          "type": "gear",
          "systemId": "example.beacon",
          "data": {
            "length": 10
          }
        }
      ]
    }
  ]
}

Validate: locate beacon-rope in the system pack and import as GM. The directory shows kind: "item"; the imported document has type: "gear".

Installed system: spell_pack

Imports Beacon Glow as a spell item with color blue. spell_pack is an item compatibility alias; it does not add spell rules to the core.

This is a complete single-pack collection; use it alone or combine packs in the downloadable collection packs array.

json
{
  "name": "Beacon Examples",
  "packs": [
    {
      "id": "beacon-spells",
      "label": "Beacon Spells",
      "type": "spell_pack",
      "entries": [
        {
          "id": "beacon-glow",
          "name": "Beacon Glow",
          "type": "spell",
          "systemId": "example.beacon",
          "data": {
            "color": "blue"
          }
        }
      ]
    }
  ]
}

Validate: locate beacon-glow in the system pack and import as GM. The directory shows kind: "item"; the imported document has type: "spell".

Validate the contract before installation

Each collection is limited to 8 MiB, 64 packs and 20,000 entries; each entry is limited to 256 KiB and JSON nesting to 24 levels. A manifest accepts up to 16 collection paths. IDs must be unique within their pack/collection, up to 120 characters, use ASCII letters, digits, dots, underscores, colons or hyphens, and start with a letter or digit. Names/labels are limited to 120 characters. Entry document types must be registered by the manifest and systemId must be example.beacon for this example.

Installed-system collections have NO campaigns list. The host binds access to the campaign's approved version. A player needs a valid read grant; importing requires GM. Declared collections are not served through assets.json: discover IDs with api.compendiums.state and read through the authorized API.

To run the real validator in a development checkout, save the JSONs as manifest.json and compendiums/beacon.json in a disposable folder. Run this in python manage.py shell with that folder as the working directory, or adjust the two paths:

python
import json
from pathlib import Path
from gravewright.compendiums.content import validate_collection

manifest = json.loads(Path("manifest.json").read_text(encoding="utf-8"))
collection = json.loads(Path("compendiums/beacon.json").read_text(encoding="utf-8"))
validate_collection(
    collection,
    manifest["id"],
    {row["id"] for row in manifest["system"]["actorTypes"]},
    {row["id"] for row in manifest["system"]["itemTypes"]},
)
print("Collection accepted")

Local catalogs: operator preparation

This is a host-operator file integration, not a browser module write capability. In a development checkout use the directory configured by settings.GRAVEWRIGHT_CONTENT_ROOT; do not assume a global directory or edit .env for an experiment. Configuration and the Runner determine the effective root.

A local catalog uses a subdirectory with index.json and a campaigns array containing real campaign UUIDs authorized by the operator. The UUID below is synthetic: replace it with your disposable campaign using example.beacon. These catalogs are read only in the tabletop. journal_pack/scene_pack/card_pack/deck_pack belong to this origin and are rejected in an installed package's content.compendiums.

Download the complete local index.json and original beacon.webp artwork. Preserve local/index.json and local/artwork/beacon.webp. Each block below is one complete pack for the index packs array. The minimal collection wrapper is:

json
{
  "name": "Beacon Local Examples",
  "systemId": "example.beacon",
  "campaigns": [
    "00000000-0000-4000-8000-000000000001"
  ],
  "packs": []
}

Local catalog: actor_pack

json
{
  "id": "beacon-actors",
  "label": "Beacon Actors",
  "type": "actor_pack",
  "entries": [
    {
      "id": "beacon-scout",
      "name": "Beacon Scout",
      "type": "character",
      "systemId": "example.beacon",
      "data": {
        "stamina": 6
      }
    }
  ]
}

Expected result and validation: Creates a character actor. The type must exist in the campaign system; this example uses stamina 6. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Local catalog: item_pack

json
{
  "id": "beacon-items",
  "label": "Beacon Items",
  "type": "item_pack",
  "entries": [
    {
      "id": "beacon-rope",
      "name": "Beacon Rope",
      "type": "gear",
      "systemId": "example.beacon",
      "data": {
        "length": 10
      }
    }
  ]
}

Expected result and validation: Creates a gear item with length 10. Do not use an item type from a different system. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Local catalog: spell_pack

json
{
  "id": "beacon-spells",
  "label": "Beacon Spells",
  "type": "spell_pack",
  "entries": [
    {
      "id": "beacon-glow",
      "name": "Beacon Glow",
      "type": "spell",
      "systemId": "example.beacon",
      "data": {
        "color": "blue"
      }
    }
  ]
}

Expected result and validation: Creates a spell item. The directory kind remains item rather than a native spell entity. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Local catalog: journal_pack

json
{
  "id": "beacon-journals",
  "label": "Beacon Journals",
  "type": "journal_pack",
  "entries": [
    {
      "id": "beacon-notes",
      "name": "Beacon Notes",
      "type": "diary",
      "systemId": "example.beacon",
      "data": {}
    }
  ]
}

Expected result and validation: Creates an empty diary journal named Beacon Notes. Add original content through the interface after import. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Local catalog: scene_pack

json
{
  "id": "beacon-scenes",
  "label": "Beacon Scenes",
  "type": "scene_pack",
  "entries": [
    {
      "id": "beacon-harbor",
      "name": "Beacon Harbor",
      "type": "scene",
      "systemId": "example.beacon",
      "data": {
        "width": 1400,
        "height": 980
      }
    }
  ]
}

Expected result and validation: Creates the Beacon Harbor scene at 1400 × 980 pixels. This minimal entry has no background image, walls or objects. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Local catalog: card_pack

json
{
  "id": "beacon-cards",
  "label": "Beacon Cards",
  "type": "card_pack",
  "entries": [
    {
      "id": "beacon-card-deck",
      "name": "Beacon Signals",
      "type": "deck",
      "systemId": "example.beacon",
      "data": {
        "cards": [
          {
            "name": "Beacon Signal",
            "artwork": "artwork/beacon.webp",
            "quantity": 1
          }
        ]
      }
    }
  ]
}

Expected result and validation: Creates a deck instance with one Beacon Signal card. WebP artwork must exist relative to the index directory. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Local catalog: deck_pack

json
{
  "id": "beacon-decks",
  "label": "Beacon Decks",
  "type": "deck_pack",
  "entries": [
    {
      "id": "beacon-deck",
      "name": "Beacon Watch",
      "type": "deck",
      "systemId": "example.beacon",
      "data": {
        "cards": [
          {
            "name": "Beacon Watch",
            "artwork": "artwork/beacon.webp",
            "quantity": 2
          }
        ]
      }
    }
  ]
}

Expected result and validation: Creates a deck instance with two Beacon Watch cards. quantity expands copies. card_pack and deck_pack use the same importer. Refresh compendiums, read the entry by ID and import as GM; inspect the created resource in the disposable campaign.

Install only the local example catalog

With both downloads saved under a local folder and the test campaign created, run this in the disposable installation's python manage.py shell. Set sample to the download folder and campaign_id to the real UUID. It reads the configured root and creates a new beacon-examples directory; it fails if that directory already exists, preserving existing installations and content.

python
import json
import shutil
from pathlib import Path
from uuid import UUID
from django.conf import settings

sample = Path("downloaded-examples/local").resolve()
campaign_id = str(UUID("00000000-0000-4000-8000-000000000001"))
collection = json.loads((sample / "index.json").read_text(encoding="utf-8"))
artwork = sample / "artwork" / "beacon.webp"
if not artwork.is_file():
    raise FileNotFoundError(artwork)
collection["campaigns"] = [campaign_id]
root = Path(settings.GRAVEWRIGHT_CONTENT_ROOT).resolve()
destination = root / "beacon-examples"
destination.mkdir(parents=True, exist_ok=False)
(destination / "artwork").mkdir()
shutil.copy2(artwork, destination / "artwork" / "beacon.webp")
(destination / "index.json").write_text(
    json.dumps(collection, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(destination)

Errors and final checks

An installed-system collection with unregistered document types, a wrong systemId, duplicate IDs or one of the four local-only pack types fails validation before installation. In the tabletop, an inactive/revoked version or changed approved bytes makes content unavailable. Do not repair integrity failures by editing installed files: prepare a new version.

For local catalogs, missing campaigns or a wrong UUID hides the catalog. Cards/decks require 1–500 cards, integer quantity between 1 and 500, at most 500 expanded cards and valid WebP files up to 10 MiB inside the catalog directory; external paths and missing files fail. Check the directory tree and every import result. Packs start GM-visible; grant reading through the permission flow and confirm that a player may read but cannot import.

How to use AI to create original-content packs

text
Inspect content.py, catalog.py and the target release manifest. Build an installed system with one actor_pack, one item_pack and one spell_pack using only declared document types, the correct systemId and original content. Provide complete JSONs, validate with validate_collection and demonstrate state/read/import. Explain why journal_pack, scene_pack, card_pack and deck_pack cannot enter that ZIP; if I request a local catalog, use the effective setting and explicit campaigns without replacing existing files.