VTTForge on npm GitHub →
v0.1 in development

Forge sheets,
not boilerplate.

A modern SDK for building FoundryVTT v13+ systems and modules. Declarative APIs, schema-to-type inference, zero lock-in.

$ pnpm create vttforge
Design System
Foundry target
v13+
Layer prefix
vttforge.*
Tests
100+ + end-to-end
License
MIT
The problem

Replace 100s of lines
with declarative classes.

Every FoundryVTT sheet you've written has the same structural plumbing: drag-drop handlers, _prepareContext ceremony, fromUuid lookups, tab state machines. VTTForge turns it into one declarative class.

Without VTTForge ~140 lines per sheet
character-sheet.mjs javascript
class CharacterSheet extends ActorSheetV2 {

  // 36 lines of identical drag-drop wiring
  #createDragDropHandlers() {
    return this.options.dragDrop.map(d => {
      d.callbacks = {
        dragstart: this._onDragStart.bind(this),
        drop:      this._onDrop.bind(this),
      };
      return new DragDrop(d);
    });
  }

  async _prepareContext(options) {
    const context = await super._prepareContext(options);
    context.tabs = {
      primary: this._prepareTabs("primary"),
      /* every group, every sheet… */
    };
    return context;
  }

  async _onDropItem(event, data) {
    const item = await fromUuid(data.uuid);
    if (!item) return;
    // type-switching, validation, transfer…
  }
}
With VTTForge 12 lines · same behavior
character-sheet.ts typescript
import { BaseActorSheet } from "@vttforge/core";

class CharacterSheet extends BaseActorSheet() {
  static PARTS = {
    main: { template: "systems/demo/sheet.hbs" },
  };
  static TABS = {
    primary: { tabs: ["abilities", "inventory", "bio"] },
  };
  static DRAG_DROP = [{ dragSelector: ".item" }];

  // fromUuid already resolved, just handle your domain.
  async onDropItem(item, event) {
    await this.actor.createEmbeddedDocuments("Item", [item.toObject()]);
  }
}
What you get

Battle-tested
in production Foundry.

Every API is validated against a production FoundryVTT system before it ships. Where Foundry already does something well, we don't reinvent it.

{ }

Declarative sheet bases

BaseActorSheet and BaseItemSheet handle drag-drop wiring, tab state, and fromUuid resolution. You write domain logic, not plumbing.

@vttforge/core
T

Schema-to-type inference

One defineSchema() drives both runtime validation and your TypeScript types via InferSchema<T>. Stop double-typing your data models.

@vttforge/core · @vttforge/types

Cascade-layer CSS

@vttforge/styles nests under Foundry's system layer with a vendored prefix, no specificity wars, no !important. Inherits Theme V2 for free.

@vttforge/styles

Migration runner

createMigrationRunner() ships idempotent, versioned data migrations with a clean rollback story, runs once at world load, logs to chat.

@vttforge/core

HMR for Handlebars

The Vite plugin watches .hbs templates and hot-swaps them in a running Foundry world. Tweak markup without reloading a world.

@vttforge/vite-plugin · v0.2

Stable error registry

Every runtime error carries a VTTF-NNNN code from an append-only registry. Codes are stable across majors, log scrapers and IDE tooltips resolve forever.

@vttforge/core
Packages

Six packages,
one monorepo.

Cherry-pick what you need. @vttforge/core is enough for a system; the rest layer on top.

packagewhat it doesstatus
@vttforge/core Runtime: BaseActorSheet, BaseItemSheet, SystemConfig, registerSystem, fields(), InferSchema, createMigrationRunner, VttfError v0.1 in progress
@vttforge/styles Design tokens, sheet primitives, drag-drop affordances, opt-in themes via CSS cascade layers v0.1 in progress
@vttforge/vite-plugin Vite plugin: HMR for .hbs, CSS pipeline (PostCSS), manifest sync v0.2 planned
@vttforge/cli vttforge init / dev / build, scaffold systems and modules in TS or JS v0.3 planned
@vttforge/testing Quench helpers for system and module tests v1.0 planned
@vttforge/types Shared TypeScript types, full class-level inference, Drizzle-style $inferData v1.0 planned
Try it today

Working example
in one command.

The repo ships a real Foundry v13 system you can boot in Docker, tabbed character sheet, drag-drop inventory, demo migration. Requires Docker + a foundryvtt.com license.

Boot the example world

Clone the repo, fill in your Foundry license, run one compose command. The bundled examples/simple-system mounts read-only at localhost:30000.

terminal bash
# clone & install
git clone https://github.com/vttforge/vttforge
cd vttforge
corepack enable && pnpm install
pnpm build

# fill in your foundryvtt.com license
cp .env.example .env

# boot Foundry v13 with the example system mounted
docker compose -f docker-compose.dev.yml up
# http://localhost:30000

Coming with v0.1

Once @vttforge/cli ships in v0.3, scaffolding a new system or module takes one command. Dev server with HMR for Handlebars templates is in the v0.2 plugin.

terminal bash
# scaffold a system, or a module
npx @vttforge/cli init my-system \
  --type system --lang ts

npx @vttforge/cli init my-module \
  --type module --lang ts

cd my-system

# dev server with HMR for .hbs
vttforge dev \
  --foundry-data ~/Library/Application\ Support/FoundryVTT/Data

# production build
vttforge build
Roadmap

Where we are now.
And the road to 1.0.

v0.0
Names reserved
v0.1
Core runtime · sheet bases · styles · types
v0.2
Vite plugin · HMR · manifest sync
v0.3
CLI scaffolding · docs site
v1.0
Stable API · full inference · decorators

Built one Foundry system the hard way?
Try building the next one with VTTForge.

Every package is on npm. Scaffold one, file an issue with the boilerplate you keep writing by hand, or open the design system to see what ships with it.