Creating Custom Widgets

This guide explains how to create and register custom widgets for the Campaign Codex module. This allows other modules to add new functionality and interactive elements that can be embedded within Campaign Codex journal entries.

1. Create the module structure

Create a folder for your module using the same name as the module ID:

foldername/
├── module.json
└── scripts/
    ├── main.js
    └── widgets/
        └── WidgetName.js

Add a module.json file:

{
  "id": "foldername",
  "title": "Module Title",
  "description": "Addon widgets for Campaign Codex",
  "version": "1.0.0",
  "scripts": [],
  "esmodules": [
    "scripts/main.js"
  ],
  "compatibility": {
    "minimum": "13",
    "verified": "13"
  },
  "url": "",
  "readme": "",
  "authors": [
    {
      "name": "",
      "url": "",
      "flags": {}
    }
  ],
  "relationships": {
    "systems": []
  },
  "manifest": "",
  "download": ""
}

The id must match the module’s folder name.

2. Register the widgets

Create scripts/main.js.

Import each widget factory and register the resulting widget class when Foundry is ready:

import { createWidgetName } from "./widgets/WidgetName.js";

Hooks.once("ready", async () => {
  const ccApi = game.modules.get("campaign-codex")?.api;

  if (!ccApi) {
    console.error(
      "Widgets | Campaign Codex API was not found. Is Campaign Codex enabled?"
    );
    return;
  }

  const { CampaignCodexWidget, widgetManager } = ccApi;

  const WidgetName = createWidgetName(CampaignCodexWidget);

  widgetManager.registerWidget("Widget Name", WidgetName);

  console.log("Widgets | Registered with Campaign Codex.");
});

For every additional widget, repeat the import, factory call, and registerWidget call:

import { createFirstWidget } from "./widgets/FirstWidget.js";
import { createSecondWidget } from "./widgets/SecondWidget.js";

Hooks.once("ready", async () => {
  const ccApi = game.modules.get("campaign-codex")?.api;

  if (!ccApi) {
    console.error(
      "Widgets | Campaign Codex API was not found. Is Campaign Codex enabled?"
    );
    return;
  }

  const { CampaignCodexWidget, widgetManager } = ccApi;

  const FirstWidget = createFirstWidget(CampaignCodexWidget);
  const SecondWidget = createSecondWidget(CampaignCodexWidget);

  widgetManager.registerWidget("First Widget", FirstWidget);
  widgetManager.registerWidget("Second Widget", SecondWidget);

  console.log("Widgets | Registered with Campaign Codex.");
});

3. Create the widget class

Create scripts/widgets/WidgetName.js.

The widget must export a factory function that:

  1. Receives Campaign Codex’s CampaignCodexWidget base class.
  2. Creates a class that extends CampaignCodexWidget.
  3. Returns that class.
export function createWidgetName(CampaignCodexWidget) {
  return class WidgetName extends CampaignCodexWidget {
    // Add the widget implementation here.
  };
}

The CampaignCodexWidget base class is supplied by the Campaign Codex API at runtime.