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):
mods\next toprism.exe(in development: the repository'smods/folder);- the player's mods folder:
%LOCALAPPDATA%\Prism\PrismNG\data\mods(C:\Users\<user>\AppData\Local\Prism\PrismNG\data\mods). ThePRISM_MODS_DIRvariable 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 inmods\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.*, permissionskinImport): in theintegrationSDK, documented in Mod API reference (version 2). - Download sources and mirrors (
downloads.register,downloads.registerBridge, permissionnetwork): documented here based on thefeature/mod-download-sourcesbranch; they only exist inmods/sdk/modding.d.tsonce that branch has been merged intointegration. Until then, the verification script is run with--sdk <d.ts of the branch>.
What is still missing is listed in LACUNES.md.