Skip to content

Scripting Introduction

Journey includes a built-in scripting engine called Ceremony that lets you write server-side JavaScript to extend your server far beyond what JSON configuration alone can achieve. Powered by GraalJS, Ceremony gives you a full ES2023+ JavaScript runtime running inside your Minecraft server — with direct access to players, events, particles, combat, scheduling, HUD elements, and more.

If you have ever wanted to script a custom minigame, build a dynamic quest sequence, create an interactive puzzle, or just automate tedious admin tasks — Ceremony scripting is how you do it.


Ceremony is Journey’s JavaScript scripting layer. It embeds the GraalJS engine (a high-performance JavaScript implementation from Oracle’s GraalVM project) directly into your Fabric server. This means:

  • Server-side execution — Scripts run on the server, not the client. No client mods required for script logic.
  • ES2023+ features — Full modern JavaScript support including const/let, arrow functions, template literals, destructuring, async/await, Promises, optional chaining, nullish coalescing, and more.
  • Safe sandboxing — Scripts run inside a controlled environment with access only to the APIs Ceremony exposes. They cannot arbitrarily access the filesystem or network.
  • Hot-reloadable — Edit your scripts and reload them without restarting the server.

Ceremony scripts have access to a rich set of APIs:

CapabilityDescription
140+ EventsListen for player actions, block changes, entity interactions, Pokemon events, combat, movement, chat, and more
Game.* NamespacesAccess particles, sounds, combat, entities, scheduling, HUD, camera, regions, display, and state through organized namespaces
Player WrappersRich Player interface with messaging, teleportation, inventory access, effects, and metadata
State ManagementPersistent key-value storage via State and per-player data via PlayerData
Java InteropGraalJS allows calling Java/Kotlin classes directly when you need deeper access
Global FunctionsConvenience functions for messaging, sounds, titles, delays, and broadcasting
SchedulingTick-based timers, repeating tasks, sequences, and async delays
HUD ElementsBoss bars, action bars, sidebars, titles, and toast notifications

Ceremony scripts are plain .js files placed in the scripts/ folder at the root of your server directory (next to your mods/ and config/ folders):

server/
├── mods/
├── config/
├── scripts/
│ ├── welcome.js
│ ├── daily-rewards.js
│ ├── gym-battle.js
│ └── utils/
│ └── helpers.js
└── ...

Any file with a .js extension inside scripts/ will be loaded automatically when the server starts or when you run a reload.


Every Ceremony script follows a simple lifecycle:

// Called when the script is loaded or reloaded
function onEnable() {
console.log('My script is starting up!');
// Register event listeners
Events.on('PlayerJoinEvent', (event) => {
const player = wrapPlayer(event.getEntity());
msg(player, '<green>Welcome to the server!');
});
}
// Called when the script is unloaded (before reload or shutdown)
function onDisable() {
console.log('My script is shutting down!');
}
FunctionWhen It RunsPurpose
onEnable()Script load or /ceremony reloadRegister event listeners, initialize state, start timers
onDisable()Script unload, before reload, or server shutdownClean up resources, save state, cancel timers
Events.on()Any time during onEnable()Subscribe to specific game events

Because Ceremony uses GraalJS with ES2023+ support, you can write modern JavaScript freely:

// const and let (block-scoped variables)
const MAX_PLAYERS = 10;
let currentRound = 0;
// Arrow functions
const greet = (player) => msg(player, '<green>Hello!');
// Template literals
const announcement = `Round ${currentRound} is starting!`;
// Destructuring
const { x, y, z } = player.getPosition();
// Async/await with Promises
async function countdown(player) {
for (let i = 5; i > 0; i--) {
title(player, `<gold>${i}`, '<gray>Get ready...');
await delay(20); // 20 ticks = 1 second
}
title(player, '<green>GO!', '');
}
// Optional chaining and nullish coalescing
const teamName = player.getTeam()?.getName() ?? 'No Team';
// Spread operator
const allPlayers = [...Game.entities.players()];
// Array methods
const nearbyPlayers = allPlayers.filter(p => {
const dist = p.distanceTo(origin);
return dist < 50;
});

Ceremony ships with a TypeScript declaration file that provides autocomplete, type checking, and inline documentation in editors like VS Code:

  1. Copy ceremony-api.d.ts from the Ceremony release into a types/ folder in your scripts project.
  2. Add a triple-slash reference at the top of each script file:
/// <reference path="types/ceremony-api.d.ts" />
function onEnable() {
// Your editor now provides autocomplete for all Ceremony APIs
Events.on('PlayerJoinEvent', (event) => {
const player = wrapPlayer(event.getEntity());
msg(player, '<green>Welcome!');
});
}

Your project structure should look like:

scripts/
├── types/
│ └── ceremony-api.d.ts
├── welcome.js
└── daily-rewards.js

Ceremony supports .ts (TypeScript) files in addition to .js. TypeScript files are transpiled automatically before execution, giving you type-safety and better IDE support.

To use TypeScript:

  1. Copy globals.d.ts from the Ceremony release into your scripts/ directory (or a types/ subfolder).
  2. Add a triple-slash reference at the top of your .ts file:
/// <reference path="./globals.d.ts" />
function onEnable() {
Events.on('playerJoin', (player: any) => {
msg(player, '<green>Welcome!');
});
}

With TypeScript, your editor will provide richer autocomplete, inline errors, and documentation. The Script Editor’s Export dialog can export directly as .ts with type annotations included.


Ceremony supports require() for importing shared code between scripts. Modules use the CommonJS pattern:

scripts/utils/math-helpers.js
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function randomBetween(min, max) {
return min + Math.random() * (max - min);
}
module.exports = { clamp, randomBetween };
scripts/my-script.js
const { clamp, randomBetween } = require('./utils/math-helpers');
function onEnable() {
Events.on('playerJoin', (player) => {
const bonus = clamp(randomBetween(1, 100), 10, 50);
msg(player, `<gold>Your bonus: ${bonus}`);
});
}

Resolution order:

  1. Relative paths (./foo, ../bar) resolve from the current script’s directory
  2. Bare names (utils) resolve from the scripts/ root
  3. .js extension is added automatically if omitted

After editing a script, reload all scripts in-game with:

/ceremony reload

This will:

  1. Call onDisable() on every currently loaded script
  2. Unregister all event listeners
  3. Reload all .js files from the scripts/ folder
  4. Call onEnable() on every script

You do not need to restart the server. Changes take effect immediately.


Here are some examples of what servers have built with Ceremony scripting:

  • Custom gym battles with automated matchmaking, scoring, and badge rewards
  • Daily login rewards that scale with consecutive days
  • Interactive puzzles using block interactions, particle trails, and timed sequences
  • Spawn protection with dynamic particle borders and warning messages
  • Automated tournaments with brackets, timers, and announcements
  • Economy integration with custom shops, trading, and currency management
  • Dynamic weather events that spawn rare Pokemon during thunderstorms
  • Minigames like parkour races, scavenger hunts, and capture-the-flag

Ready to start scripting? Here is the recommended path:

  1. Global Functions — Learn the built-in utility functions available everywhere
  2. Game Namespace — Explore the full Game.* API for particles, combat, HUD, and more
  3. Your First Script — Follow a hands-on tutorial to build your first working script