Scaffold a new FoundryVTT v13 module repo (Vite + TS + bun + biome, CI, release-please) with basic/app/libwrapper variants. Use when bootstrapping or init-ing a foundry module.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Scaffold a new FoundryVTT v13 module repo (Vite + TS + bun + biome, CI, release-please) with basic/app/libwrapper variants. Use when bootstrapping or init-ing a foundry module.
--name foundryvtt-x --display "X" --desc "…" --variant basic
foundryvtt-module-scaffold
Bootstrap a new FoundryVTT v13 module repo built with Vite + TypeScript +
bun + biome, leaving only the actual module logic to implement. The generated
repo passes just check (typecheck + build + lint + test) from the first commit
and distributes via the GitHub-release manifest-URL convention.
When to Use This Skill
Use this skill when...
Use the alternative when...
Starting a new FoundryVTT module repo — CI-green TS toolchain, release-please, and a basic/app/libwrapper skeleton before writing module logic
You want the full pipeline (repo created + seeded + gitops-adopted) → foundryvtt-module
Spinning up a FoundryVTT module backlog idea
Adding a feature to an existing module — this creates a new repo
The architecture it scaffolds
TypeScript source in src/ (entry src/module.ts), built to dist/<id>.mjs
via Vite library mode.vite-plugin-static-copy places module.json, lang/,
styles/ (and templates/ for the app variant) into dist/, which Foundry
serves as the module root. tsc --noEmit type-checks; Vite emits — decoupled.
Type gate: bun run typecheck → tsc --noEmit. Foundry globals are typed
by loose local ambient shims (src/foundry-shims.d.ts), so the build is
self-contained and CI-green without the (beta, git-only) fvtt-types. Verify
the real Foundry API before relying on a shape — the shims are deliberately
loose. Opt into fvtt-types later if you want richer types.
Build: bun run build → vite build → dist/<id>.mjs + copied assets.
Dev: bun run dev → Vite dev server on :30001 proxying everything to
Foundry on :30000 except the module's own files (served with HMR).
Distribute via GitHub release: module.jsonmanifest →
, →
. release-please bumps in both
and ; the release job builds, zips , and
attaches the assets. No foundryvtt.com submission is needed to install by URL
(only to be in the in-app package browser).
releases/latest/download/module.json
download
releases/latest/download/<id>.zip
$.version
package.json
module.json
dist/
listed
Three variants
Variant
Use when
Adds on top of basic
basic (default)
Settings + lifecycle behavior — the minimal well-formed module.
init/ready hooks, a registered world setting, i18n, scoped CSS.
app
The module has a UI panel.
An ApplicationV2/HandlebarsApplicationMixin window (src/app.ts + templates/app.hbs) and a game.settings.registerMenu button that opens it.
libwrapper
The module patches a core/system method.
src/patches.ts with a libWrapper.register(...) call and a manual monkey-patch fallback when lib-wrapper is absent; a relationships.recommends entry for lib-wrapper.
Decision rule:basic for behavior driven by hooks/settings; app when the
module surfaces a window or dialog; libwrapper when it overrides a core method
(the conflict-safe way to do that on Foundry). Variants compose conceptually —
start from the closest one and add the rest by hand.
How to run
scaffold.py is stdlib-only. Run from the workspace where the module should
land (e.g. repos/laurigates/foundryvtt-dev/).
Basic settings+hooks module:
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-initiative-tweaks --display "Initiative Tweaks" --desc "Small quality-of-life tweaks to the combat initiative tracker."
Module with an ApplicationV2 UI panel:
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-party-overview --display "Party Overview" --desc "A dockable party status panel for GMs." --variant app
Module that patches a core method via libWrapper:
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-token-vision-tweak --display "Token Vision Tweak" --desc "Adjusts token vision drawing via a libWrapper-guarded patch." --variant libwrapper
Flags: --name (GitHub repo, e.g. foundryvtt-x), --id (Foundry module id;
default = --name minus the leading foundryvtt-), --display (title),
--desc, --variant {basic,app,libwrapper}, --fvtt-min / --fvtt-verified
(compatibility, default 12 / 13), --publisher (default laurigates),
--author, --dir (parent dir, default cwd).
Re-runs the finishing-pass audit against an existing module and emits a
machine verdict (MODULE_ID_MATCH=, ESMODULES=, MANIFEST_URL=,
DOWNLOAD_URL=, RELEASE_ZIP=, DECLARED_ASSETS=, LOCKFILE=, then
ISSUE_COUNT= and STATUS=). Exit 1 on ERROR.
STATUS=
Meaning
ERROR
Foundry cannot install, load, or update the published module — the id disagrees across module.json / vite.config.ts / src/constants.ts, esmodules names a file the build never emits, or an install URL resolves to a release asset nobody uploads.
WARN
It loads, but something is unfinished (a declared asset that 404s, no bun.lock yet). A fresh scaffold is WARN until bun install runs.
OK
Nothing outstanding.
The generated repo asserts the same invariants in tests/manifest.test.ts, which
its own test CI job runs on every PR — so a rename that drifts one of the five
places the module id appears fails a PR, not a user's install.
Alternative: the cargo-generate template (pilot)
templates/foundryvtt-module/ is a cargo-generate port of scaffold.py whose
emitted files are real files (so tsc/biome/actionlint can check them)
rather than Python strings. Output is byte-identical, enforced by
scripts/tests/test-template-parity.sh.
scaffold.py remains the default. Reach for the template to edit the
scaffold itself, or to try the flow before it is promoted:
Needs cargo-generate locally — not in the base image, and the main cost of the
port. CI installs it from the release tarball so the parity gate actually runs
(#2221). See templates/README.md
for the comparison, the one deliberate divergence (a non-kebab-case name), the
Liquid brace-collision fixes, and what promoting the template would take.
What you get
A repo where just check passes from the first commit: a real module.json
manifest, package.json (bun scripts), vite.config.ts, strict tsconfig.json,
biome.json, vitest.config.ts + a green Vitest smoke test (Foundry globals
stubbed in tests/setup.ts) and tests/manifest.test.ts (the manifest-vs-build
gate above), .github/workflows/ (ci.yml,
release-please.yml, renovate.yml), release-please-config.json + manifest,
renovate.json, a justfile, src/module.ts + src/settings.ts +
src/constants.ts + src/foundry-shims.d.ts, lang/en.json,
styles/<id>.css, CLAUDE.md, README.md, LICENSE, and an ADR recording the
toolchain decision. The app variant adds src/app.ts + templates/app.hbs;
libwrapper adds src/patches.ts.
After scaffolding
The generator prints the exact next steps. In order:
cd foundryvtt-<name>
git init -b main
bun install
just check
Seed main directly (the repo is unprotected until gitops adopts it) — pushing
a feature branch first would leave main missing on origin and force a rename +
default-branch fixup later. bun install writes bun.lock, which the seed
commit must include (CI uses --frozen-lockfile).
Then implement, and wire up infra:
Implement the module — for basic, src/module.ts + src/settings.ts;
for app, src/app.ts + templates/app.hbs; for libwrapper, replace the
Token._draw example in src/patches.ts with the real target.
Add the repo to gitops/repositories.tf with release_please = true and
a foundryvtt topic (mirror the foundryvtt-mcp entry). On apply, gitops
pushes the release-please App credentials.
Or skip steps entirely: run the /foundryvtt-module orchestrator, which
chains scaffold → gh repo create → seed main → the gitops PR.
Hard rules baked into the output
id is the single source of truth.module.jsonid, the install
folder, and the release zip name all derive from --id. Lowercase kebab-case
only.
ESM-only, paths byte-match the manifest.esmodules references
<id>.mjs; the Vite output filename is pinned to match. A mismatch is a silent
load failure — tests/manifest.test.ts fails the PR that introduces it.
Target the harness-pinned Foundry version. Keep module.jsoncompatibility.{minimum,verified} in sync with what you test against. Verify
the Foundry API against https://foundryvtt.com/api/ or the live console — not
memory.
Do not commit dist/. It is git-ignored and rebuilt; CI builds it for
releases.
Scoped CSS. Every selector is prefixed with the module id — keep it that
way so styles never clobber core or other modules.
Never hand-edit CHANGELOG.md or the version fields — release-please
owns them (it bumps both package.json and module.json).
Agentic Optimizations
Context
Command
Scaffold a basic module
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-X --display "X" --desc "…"
Scaffold an app module
python3 ${CLAUDE_SKILL_DIR}/scaffold.py --name foundryvtt-X --display "X" --desc "…" --variant app
The biome pin is single-sourced in scaffold.py's BIOME_VERSION constant so
biome.json and the CI setup-biome step never drift.
Action/tool versions in the generated workflows are current as of scaffolding;
the generated renovate.yml (laurigates reusable workflow) bumps them.
The generated module uses local ambient shims, not fvtt-types. This keeps
the build green and self-contained; switch tsconfigtypes to fvtt-types
(github:League-of-Foundry-Developers/foundry-vtt-types#main) for full API
types once you need them.
Quench (in-Foundry Mocha runner) and Playwright integration tests against the
harness are not scaffolded — add them when the module warrants runtime
coverage.