PixelScript

Ship server features while the server is running.

PixelScript runs your logic as JavaScript or TypeScript on the JVM, calling the real Bukkit/Paper API. Save a file and the change is live.No rebuild, no restart, no reconnect.

You are not learning a scripting language. You are writing Java with JavaScript syntax, against the same API you already know, with the iteration loop of a web app. Generated .d.ts definitions mean your IDE autocompletes against the exact Bukkit version you are running.

Documentation
Watching for changes
1import { error, success } from '@/patches/chat/messages';
2
3const PERMISSION = 'dc.command.fly';
4
5registerCommand('fly', (sender, args) => {
6  if (!sender.hasPermission(PERMISSION)) {
7    sender.sendMessage(error('You do not have permission for that.'));
8    return;
9  }
10
11  const enabled = !sender.getAllowFlight();
12  sender.setAllowFlight(enabled);
13
14  sender.sendMessage(
15    success(enabled ? 'Flight enabled.' : 'Flight disabled.')
16  );
17});
18
19// A complete, working feature. Save the file and /fly exists.
20// Delete it and the command is gone, unregistered cleanly.
21
JavaScriptUTF-8
PixelScript

Every snippet on this page is taken from a live production server, not written for the brochure.

How it works

There is no build step and no separate process. Your scripts are loaded into the server's own JVM, and a save is a targeted reload rather than a restart.

Your scripts

Plain .js and .ts files on disk, in whatever tree you want. A file watcher notices the moment one changes.

init.ts
patches/init.js
features/warp/index.js
utils/messages.js
PixelScript

The runtime

TypeScript is compiled, then everything is compiled to JVM bytecode rather than interpreted. The changed script and its dependents are torn down and rebuilt, and their commands, listeners and tasks are unregistered along with them.

Bytecode
Reload tree
File watcher
Type generation
Timings

The live server

The new version registers itself against the real Bukkit/Paper API. Players stay connected, the world stays loaded, and nothing else on the server is touched.

$ features/warp/index.js changed
$ unloaded 1 script, 3 dependents
$ reloaded in 41ms

Because your code runs inside the server, there is no bridge to marshal across and no API subset to work around. Everything a Java plugin can do, a script can do with the same objects, the same events and the same services, reached from JavaScript or TypeScript instead of from a jar you have to rebuild.

What you get

A runtime that behaves predictably on a live server, not a sandbox that falls over the moment your codebase grows past one file.

Live reload

Edits are detected and applied without restarting. Commands, listeners and scheduled tasks are unregistered and re-registered for you.

A real reload tree

Watched scripts are barriers, so changing one feature does not reboot your whole server’s logic. Imported modules cascade to their importers. You decide where the boundaries are.

The full Bukkit API

Bukkit, Scheduler, Sql, DataFile, registerCommand, registerListener and fetch are globals. Everything else is one Script.loadClass away, and any Java library can be pulled in at runtime.

Generated types

Every Java class your scripts touch lands in a generated definitions.d.ts, so your IDE autocompletes against the exact Bukkit version you are running.

A two-way bridge

Java plugins can hold a type-safe proxy to an implementation written in JavaScript, and it never goes stale across reloads.

Observability

Timings are collected for listeners, commands, tasks, queries and class loads, so a slow feature is something you look up rather than something you argue about.

A closer look

The parts you will use every day, with the code you would actually write.

Hot reloading

Save a file and the change is applied. Commands, listeners and scheduled tasks are unregistered and re-registered for you, so nothing is left behind from the version you just replaced.

In-game console output showing scripts reloading with their individual load times

Every script reports what it cost to load, so a slow reload has an obvious culprit.

features/bossbar/bossbar.js
1const bar = Bukkit.createBossBar(
2  '☆ Welcome ☆', $.BarColor.BLUE, $.BarStyle.SEGMENTED_6
3);
4
5registerListener($.PlayerJoinEvent, (event) => bar.addPlayer(event.getPlayer()));
6registerListener($.PlayerQuitEvent, (event) => bar.removePlayer(event.getPlayer()));
7
8// Anything the runtime cannot unregister for you gets an unload callback,
9// so a reload never leaves a stale bar floating on someone's screen.
10Script.addUnloadCallback(() => {
11  bar.removeAll();
12  bar.setVisible(false);
13});
14
15// ...and the new version adopts everyone who is already online.
16Bukkit.getOnlinePlayers().forEach((player) => bar.addPlayer(player));
JavaScriptPixelScript

Measured, not claimed

The same four workloads, written twice as PixelScript and as Skript, then timed on the same Paper server, in the same JVM, in the same run. Every implementation reports a checksum, and the report refuses to compare them if those disagree.

50–271×

faster than Skript once the work is the language itself: loops, strings, collections and function calls. Your code is compiled to JVM bytecode and JIT-compiled like anything else, so what is left is interop overhead, not interpretation.

1.9–2.8×

faster than Skript when the Bukkit call dominates. Nobody outruns the server; the gap is not one number and we are not going to pretend it is.

PixelScriptSkripttime per unit of work · logarithmic scale · lower is better

Work dominated by the language

Loops, arithmetic, strings, collections, function calls. Nothing but the runtime.

Arithmetic

150,000 iterations

sqrt, multiply, modulo and floating point accumulation. No allocation, no API.

PixelScript
10.1 ns
Skript
2.74 µs
10 ns10 µs

per iteration · PixelScript is 271× faster than Skript

Runtime

10,000 iterations

String building, list append, keyed map put/get and user-defined function calls.

PixelScript
159 ns
Skript
8.09 µs
10 ns10 µs

per iteration · PixelScript is 51× faster than Skript

Work dominated by the Bukkit call underneath

Everyone is waiting on the same server code, so everyone converges.

Block writes

4,096 blocks

Writing and reading back a 16×16×16 region through the Bukkit block API.

PixelScript
1.92 µs
Skript
5.31 µs
10 ns10 µs

per block · PixelScript is 2.8× faster than Skript

Object churn

2,000 items

ItemStack and ItemMeta creation, display name, lore, Location construction and distance.

PixelScript
3.88 µs
Skript
7.35 µs
10 ns10 µs

per item · PixelScript is 1.9× faster than Skript

Dispatch is subtracted. Each suite is entered through a Bukkit command, which costs the server 100–180 µs before any benchmark code runs, and it is the noisiest measurement in the set. It is measured separately, per language, and taken back out of every figure above instead of being quietly folded into them.

Scale matters more than the ratio. A ratio on top of a few nanoseconds is still a few nanoseconds. Where the gap actually shows up in your MSPT is the language-bound work: a loop that costs 10 ns an iteration instead of 2.7 µs is the difference between a tick you never notice and one you do.

Paper 26.1.2 · Skript 2.16.0 · JDK 25 · 24-core Linux · median of 36 invocations

Documented, publicly

Read the whole thing before you commit to anything. Nothing is behind a login.

Using an AI assistant?

Coding agents already speak JavaScript. What they need is the runtime: the load tree, the globals, the threading rules. One command drops a condensed reference into your project and the full docs into your workspace.

shell
git clone --depth 1 https://github.com/pixelib/pixelscript-docs .pixelscript-docs \
  && cp .pixelscript-docs/CLAUDE.md ./CLAUDE.md
Onboarding an AI assistant

Ready to build?

Stop waiting on restarts. Build features in the time it takes to describe them, and fix the ones that are already live.