API reference

Every script gets one global: ps. It is the only bridge between your code and the server. Types ship with @paperscript/sdk — with them installed your editor autocompletes everything on this page.

Chat strings. Every sendMessage / broadcast accepts MiniMessage on the modern line (<red>, <gradient:#a:#b>, hover/click) and legacy &-codes on legacy hosts.

Lifecycle

ps.onEnable(fn: () => void): voidps.onDisable(fn: () => void): void

onEnable runs after the script is evaluated — register commands and listeners here. onDisable runs on /ps reload or server stop — cancel your timers and flush state there.

ps.logger

ps.logger.info(message: string): voidps.logger.warn(message: string): voidps.logger.error(message: string): void

Writes to the server console, prefixed with your script name.

ps.events

ps.events.onPlayerJoin(handler: (event: { player: Player }) => void): voidps.events.onPlayerQuit(handler: (event: { player: Player }) => void): void
src/index.ts
ps.events.onPlayerJoin((event) => {
  event.player.sendMessage("<gold>Welcome, " + event.player.name + "!</gold>");
  ps.server.broadcast("<gray>" + event.player.name + " joined</gray>");
});

ps.commands

ps.commands.register(name, handler, description?, usage?): void

The handler receives a CommandContext:

MemberTypeMeaning
ctx.sender.namestringPlayer name, or "CONSOLE"
ctx.sender.opbooleanServer operator?
ctx.sender.playerbooleanIs an in-game player (not console)
ctx.sender.sendMessage(msg)voidReply to the sender
ctx.labelstringCommand label as typed
ctx.argsstring[]All arguments
ctx.arg(i)string | nullArgument at index, or null
src/index.ts
ps.commands.register(
  "spawn",
  (ctx) => {
    if (!ctx.sender.player) {
      ctx.sender.sendMessage("<red>Players only.</red>");
      return;
    }
    const p = ps.players.get(ctx.sender.name);
    const w = p && ps.worlds.get(p.location.world);
    if (p && w) {
      p.teleport(w.spawnLocation);
      ctx.sender.sendMessage("<green>Teleported to spawn.</green>");
    }
  },
  "Teleport to spawn",   // description
  "/spawn"               // usage shown in /help
);

Legacy caveat: on Nashorn hosts ctx.args is a Java array — index access and .length work, but prefer ctx.arg(i) for portability.

ps.scheduler

ps.scheduler.runTask(fn: () => void): numberps.scheduler.runTaskLater(fn: () => void, delayTicks: number): numberps.scheduler.runTaskTimer(fn: () => void, delayTicks: number, periodTicks: number): numberps.scheduler.cancelTask(taskId: number): void

All tasks run on the server main thread — never touch Bukkit state from anything else. 20 ticks = 1 second. Every call returns a task id for cancellation.

src/index.ts
// once, next tick
ps.scheduler.runTask(() => ps.logger.info("tick"));

// after 5 seconds (20 ticks = 1s)
const id = ps.scheduler.runTaskLater(() => {
  ps.server.broadcast("<gray>5 seconds passed</gray>");
}, 20 * 5);

// repeating: start after 20t, then every 100t
const timer = ps.scheduler.runTaskTimer(
  () => ps.server.broadcast("<aqua>Still running</aqua>"),
  20,
  100
);

ps.scheduler.cancelTask(timer);

ps.players & Player

ps.players.online(): Player[]ps.players.get(name: string): Player | null
MemberType
name, uniqueId, onlinestring, string, boolean (readonly)
health, foodLevelnumber (read/write)
locationLocation (readonly snapshot)
teleport(loc)boolean — false if the teleport failed
heal() / feed()void — full health / full hunger
allowFlight, flyingboolean (read/write)
gameModestring (readonly)
setGameMode(mode)boolean — false for an unknown mode name
sendMessage(msg)void
src/index.ts
const p = ps.players.get("Steve"); // null if offline
if (p) {
  p.sendMessage("<gold>Hi, " + p.name + "</gold>");
  p.heal();
  p.feed();
  p.allowFlight = true;
  p.flying = true;
  p.setGameMode("creative"); // false if the mode name is unknown

  const here = p.location;       // { world, x, y, z, yaw, pitch }
  p.teleport(ps.loc.create(here.world, here.x, here.y + 10, here.z));
}

for (const online of ps.players.online()) {
  ps.logger.info(online.name + " @ " + online.location.world);
}

ps.worlds & World

ps.worlds.all(): World[]ps.worlds.get(name: string): World | null
MemberType
namestring (readonly)
timenumber (read/write, ticks 0–24000)
playerCountnumber (readonly)
spawnLocationLocation (readonly)
setSpawnLocation(loc)void

ps.loc

ps.loc.create(world: string, x, y, z): Locationps.loc.create(world: string, x, y, z, yaw, pitch): Location

A Location is a plain immutable object: { world, x, y, z, yaw, pitch } — safe to JSON-serialize into storage and pass back to teleport.

ps.server

ps.server.version: string // server implementation versionps.server.minecraftVersion: string // e.g. "1.21.1"ps.server.onlineCount: numberps.server.maxPlayers: numberps.server.broadcast(message: string): void

ps.storage

ps.storage.get(key: string): string | nullps.storage.set(key: string, jsonValue: string): voidps.storage.remove(key: string): voidps.storage.save(): void

Per-script persistent key-value store. Values are JSON strings — serialize with JSON.stringify. Flushed automatically when the script is disabled; save() forces an early flush.

src/index.ts
// values are JSON strings — serialize yourself
ps.storage.set("kills", JSON.stringify({ Steve: 3 }));

const raw = ps.storage.get("kills");
const kills = raw ? JSON.parse(raw) : {};
kills.Steve = (kills.Steve ?? 0) + 1;
ps.storage.set("kills", JSON.stringify(kills));

ps.storage.remove("kills");
ps.storage.save(); // force flush (auto-saved on disable anyway)