On this page

← All documentation

Writing a mod for Prism

Quick start: a mod folder, main.ts, a first HUD, installing and exporting.

A guide for third-party mod authors, readable by a human and by a code assistant (see llms.txt: a single file to paste into an LLM). Everything written here is drawn from the code in crates/modding and from the shipped mods; anything that is not is marked [TO VERIFY]. The detailed, historical reference remains TypeScript mods: the detailed guide.

Plan

Document Contents
This file Quick start: folder, main.ts, hello HUD, installation, export
Concepts: how a mod works Lifecycle, threads and budgets, events, permissions, errors, determinism, prohibitions
Mod API reference (version 2) Reference by namespace, verified against the SDK
Cookbook 17 complete recipes (judgement, speed, HUD, hit bar, modifier, key, notes/filter, stage, storage, skin, map script, leaderboard, tab, download source, bridge, skin import)
llms.txt Compact context for AI assistants + prompt template
LACUNES.md What the API does not (yet) allow, or what is surprising

The two other kinds of scripts: skins (skin.ts, defineSkin) and map scripts (defineChart).

Quick start

1. The folder

A mod is a folder containing main.ts (or index.ts); there is no JSON manifest, the only declaration is the defineMod call.

mon-mod/
  main.ts          required entry point
  lib/format.ts    relative modules imported by main.ts (never outside the folder)
  fonts/Title.ttf  fonts declared in `fonts` (.ttf/.otf)
  images/star.png  PNG images (hud.image, stage.sprite)

A folder without main.ts (like mods/sdk) is ignored; so is a name that starts with .. The folder name is not the id: the id comes from defineMod.

2. A hello HUD (under 40 lines)

/// <reference path="../sdk/modding.d.ts" />

export default defineMod({
  id: "hello-hud",          // a-z, 0-9, ., -, _ ; 1 to 64 characters ; stable
  name: "Hello HUD",
  version: "1.0.0",         // semver
  apiVersion: 2,            // must equal the game's API version
  author: "Moi",
  description: "The combo in big type, and the accuracy below it.",
  setup() {
    ctx.on("game.songStart", () => {
      hud.group({
        id: "panel",
        flow: { direction: "column", align: "end", gap: 0.004 },
        layout: { x: 0.98, y: 0.5, anchor: "topRight" },
      });
      // `bind`: the overlay updates the value by itself, without going back through the script.
      hud.text({ id: "combo", parent: "panel", text: "", bind: { kind: "combo" }, style: { size: 0.06 } });
      hud.text({ id: "acc", parent: "panel", text: "", bind: { kind: "accuracy", decimals: 2 }, style: { size: 0.03 } });
    });
    ctx.on("game.songEnd", () => hud.clear());
    log.info("Hello HUD loaded");
  },
});

Lengths (size, width, gap…) are fractions of the screen height; x/y are fractions of the parent (the screen at the top level).

3. Where to put it

Two roots, read in this order (Create a mod):

  1. mods\ next to prism.exe (in development: the repository's mods/ folder);
  2. the player's mods folder: %LOCALAPPDATA%\Prism\PrismNG\data\mods (C:\Users\<user>\AppData\Local\Prism\PrismNG\data\mods). The PRISM_MODS_DIR variable replaces only this data root.
Copy-Item -Recurse mon-mod "$env:LOCALAPPDATA\Prism\PrismNG\data\mods\"

The Mods page (interface labels are in English by default: Open folder, Reload, Install…, Export, Uninstall): Open folder opens this root; Reload re-reads all mods without restarting the game. The mod appears in the list, with its messages (log.info/log.warn) and its errors beneath it. If two folders declare the same id, the first one loaded wins and a message says so.

4. Enable, disable, uninstall

  • Each mod has a toggle (keyboard: Space) on the Mods page; the choice is kept (Settings.disabledMods) without uninstalling the mod or erasing its storage. A change made during a play session waits until you return to the menu.
  • Uninstall deletes the folder (<mods folder>/<id>); the mod's storage is kept for a reinstall. For a mod in mods\ next to the exe: the folder is deleted if it is writable, otherwise an error names the path. Removing a mod means deleting its folder.
  • Installing, exporting, uninstalling and reloading are refused during a play session.

5. Sharing: exporting a .pvmod

Mods page → Export: a ZIP archive <id>-<version>.pvmod. To install it: the Install… button, or drag and drop the file onto the window. Rules (Package rules): .ts, .ttf, .otf, .png, .json (data), .md, .txt files; at most 64 MiB, 1024 entries, 256 files; main.ts or index.ts at the root; hidden entries and node_modules ignored; symbolic links and encrypted entries refused. Reinstalling a mod with the same id replaces it (update, reinstall, or rollback).

Checking your types

The game transpiles without type checking: only execution (budgets, Rust validations) catches you. For types, using the interface's TypeScript:

node apps/web/node_modules/typescript/bin/tsc --noEmit --strict --target es2022 --lib es2022 --module esnext --moduleResolution bundler mods/mon-mod/main.ts

The declarations are mods/sdk/modding.d.ts; the line /// <reference path="../sdk/modding.d.ts" /> is enough (it has no effect at runtime). Outside the repository, copy the file and adjust the path. mods/sdk/modding.ts (typed models and listening shortcuts such as onJudgement) is optional: a mod that uses it copies it into its folder.

Verifying this documentation

node scripts/check-modding-docs.mjs            # symbols and recipes checked against the SDK
node scripts/check-modding-docs.mjs --strict   # + fails if an SDK type is not mentioned anywhere
node scripts/check-modding-docs.mjs --sdk chemin/modding.d.ts   # contre un autre SDK

The script fails if a documented symbol has disappeared from the SDK, if a function or event is missing from Mod API reference (version 2), or if a ts recipe no longer passes tsc --strict (TypeScript is looked up with --tsc, $TSC, or apps/web/node_modules). After an API change: cargo run -p modding --example write_sdk, then this script.

Recent features

  • Local leaderboard (leaderboard.query, leaderboard.best), selection tabs (tabs.register, tabs.extend) and skin import (skinImport.*, permission skinImport): in the integration SDK, documented in Mod API reference (version 2).
  • Download sources and mirrors (downloads.register, downloads.registerBridge, permission network): documented here based on the feature/mod-download-sources branch; they only exist in mods/sdk/modding.d.ts once that branch has been merged into integration. Until then, the verification script is run with --sdk <d.ts of the branch>.

What is still missing is listed in LACUNES.md.

Source in the game repository: docs/modding/README.md