Module Template
This document describes the minimal scaffold for creating a new Purple Cat platform module. Use the @purple-cat-games/genai module as a working reference.
See also: Module Author Guide for the full API reference.
Directory Structure
my-module/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts # Package root export (optional)
│ ├── client/
│ │ └── index.ts # Client entry — exports register()
│ └── server/
│ └── index.ts # Server entry — exports router + initialize
└── dist/ # Build output (gitignored)
package.json
{
"name": "@purple-cat-games/my-module",
"version": "0.1.0",
"description": "My custom module for Purple Cat Platform",
"license": "UNLICENSED",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist", "package.json"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./client": {
"types": "./dist/client/index.d.ts",
"import": "./dist/client/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run",
"clean": "rm -rf dist"
},
"purple-cat-module": {
"type": "integration",
"client": "dist/client/index.js",
"server": "dist/server/index.js"
},
"peerDependencies": {
"@purple-cat/shared": "*",
"express": "^4.0.0",
"react": "^18.0.0"
},
"devDependencies": {
"@purple-cat/shared": "workspace:*",
"@types/express": "^4.17.0",
"@types/react": "^18.0.0",
"typescript": "^5.0.0",
"vitest": "^4.0.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
src/client/index.ts — Client Entry
import { moduleRegistry } from '@purple-cat/shared';
import { MyArtworkSource } from './MyArtworkSource';
export function register(): void {
moduleRegistry.registerArtworkSource({
id: 'my-source',
label: 'My Source',
icon: 'Sparkles',
moduleId: 'my-module',
component: MyArtworkSource,
});
}
src/server/index.ts — Server Entry
import { Router } from 'express';
import type { ModuleInitializeContext } from '@purple-cat/shared';
export const router = Router();
router.get('/status', (_req, res) => {
res.json({ ok: true, module: 'my-module' });
});
export async function initialize(context: ModuleInitializeContext): Promise<void> {
// `context.dataDir` is a per-module persistent directory created by the platform.
console.log(`my-module: initialized; data dir: ${context.dataDir}`);
}
src/index.ts — Package Root (Optional)
// Re-export shared types or utilities if other packages need them
export type { MyModuleConfig } from './shared/types.js';
Registering With the Platform
Add the module to modules.local.json in the platform root:
{
"modules": [
{ "package": "@purple-cat-games/my-module", "path": "../my-module" }
]
}
Then rebuild. The platform's Vite plugin will auto-generate the client import, and the server loader will mount the API routes at /api/modules/my-module/.
A first-party module
The platform's own modules — dnd5e, system8, scenes and system5 — live in this
repository under modules/ (since v0.118.0). Each is a pnpm workspace package
("private": true, @purple-cat/shared as workspace:*), built by
pnpm build:packages, linted and tested by pnpm verify with its own coverage
floor, and listed in modules.hosted.json by directory:
{ "package": "@purple-cat-games/my-module", "dir": "modules/my-module" }
Nothing is published. Every environment builds the module from the commit it
deploys: pnpm modules:bundle packs it and writes the resolved manifest the
install scripts read, and a plain pnpm dev loads it straight from modules/.
A module change ships with the platform change it goes with, in the same
version. Only modules kept in other repositories (genai, vtt-assets) are pinned
by version and installed from GitHub Packages.
Asset-Only Module Variant
For modules that only provide static assets (textures, token art, etc.), the scaffold is simpler:
{
"name": "@purple-cat-games/my-assets",
"version": "0.1.0",
"purple-cat-module": {
"type": "assets",
"assetPaths": {
"textures": "dist/textures"
}
},
"scripts": {
"build": "node scripts/build.mjs"
}
}
No client or server entries needed. The platform uploads the asset files during initialization and serves them via the configured storage provider.