Skip to content

Your First Script

This tutorial walks you through building a complete welcome script from scratch. By the end, you will have a script that greets players with a styled message, plays a sound, shows a title, enforces a cooldown, and spawns particles — all in under 50 lines of code.


Create a new file called welcome.js inside the scripts/ folder at your server root:

server/
├── mods/
├── config/
├── scripts/
│ └── welcome.js
└── ...

If the scripts/ folder does not exist yet, create it manually. Ceremony will automatically load any .js file placed here.


Open welcome.js in your editor and add the triple-slash reference at the very top of the file. This enables IntelliSense autocomplete if you are using VS Code or a similar editor:

/// <reference path="types/ceremony-api.d.ts" />

Every Ceremony script needs an onEnable() function. This is the entry point that runs when the script is loaded or reloaded:

/// <reference path="types/ceremony-api.d.ts" />
function onEnable() {
Logger.info('Welcome script loaded!');
}

Save the file and run /ceremony reload in-game. You should see Welcome script loaded! in your server console. If you see an error instead, double-check for typos.


Now add an event listener inside onEnable() that fires whenever a player joins the server. The playerJoin event provides the raw serverPlayer object — you need to wrap it with wrapPlayer() to access the convenience API:

function onEnable() {
Logger.info('Welcome script loaded!');
Events.on('playerJoin', (serverPlayer) => {
const player = wrapPlayer(serverPlayer);
msg(serverPlayer, '<green>Welcome to the server, <bold>' + player.name + '</bold>!');
});
}

Make the welcome feel polished by playing a level-up sound when the player joins. Add this line right after the msg() call:

Events.on('playerJoin', (serverPlayer) => {
const player = wrapPlayer(serverPlayer);
msg(serverPlayer, '<green>Welcome to the server, <bold>' + player.name + '</bold>!');
sound(serverPlayer, 'entity.player.levelup', 0.8, 1.2);
});

The parameters for sound() are:

ParameterValueDescription
playerserverPlayerWho hears the sound
soundName'entity.player.levelup'The Minecraft sound ID
volume0.8Volume (0.0 to 1.0+)
pitch1.2Pitch (0.5 = low, 1.0 = normal, 2.0 = high)

Show a large welcome title on the player’s screen. Titles support MiniMessage formatting for colors and styles:

Events.on('playerJoin', (serverPlayer) => {
const player = wrapPlayer(serverPlayer);
msg(serverPlayer, '<green>Welcome to the server, <bold>' + player.name + '</bold>!');
sound(serverPlayer, 'entity.player.levelup', 0.8, 1.2);
title(serverPlayer, '<gold>Welcome!', '<gray>' + player.name, 10, 60, 20);
});

The title() parameters are:

ParameterValueDescription
playerserverPlayerWho sees the title
main'<gold>Welcome!'Large text in the center of the screen
sub'<gray>' + player.nameSmaller subtitle below the main text
fadeIn10Ticks to fade in (10 ticks = 0.5 seconds)
stay60Ticks to display (60 ticks = 3 seconds)
fadeOut20Ticks to fade out (20 ticks = 1 second)

Right now the welcome fires every single time a player joins, including disconnects and reconnects. Use the player data cooldown system to only show the welcome once per hour:

Events.on('playerJoin', (serverPlayer) => {
const player = wrapPlayer(serverPlayer);
// Check if the player is still on cooldown (3600 seconds = 1 hour)
if (player.data.isOnCooldown('welcome_message')) {
return; // Skip the welcome, they saw it recently
}
// Set the cooldown for 1 hour (3600 seconds)
player.data.cooldown('welcome_message', 3600);
msg(serverPlayer, '<green>Welcome to the server, <bold>' + player.name + '</bold>!');
sound(serverPlayer, 'entity.player.levelup', 0.8, 1.2);
title(serverPlayer, '<gold>Welcome!', '<gray>' + player.name, 10, 60, 20);
});

Spawn a circle of particles at the player’s feet when they join to make the welcome visually striking:

Events.on('playerJoin', (serverPlayer) => {
const player = wrapPlayer(serverPlayer);
if (player.data.isOnCooldown('welcome_message')) {
return;
}
player.data.cooldown('welcome_message', 3600);
msg(serverPlayer, '<green>Welcome to the server, <bold>' + player.name + '</bold>!');
sound(serverPlayer, 'entity.player.levelup', 0.8, 1.2);
title(serverPlayer, '<gold>Welcome!', '<gray>' + player.name, 10, 60, 20);
// Spawn a circle of happy villager particles around the player
Game.particles.circle(
serverPlayer.serverLevel(), // the world/level
player.pos.x, // center X
player.pos.y + 0.5, // center Y (slightly above feet)
player.pos.z, // center Z
ParticleTypes.HAPPY_VILLAGER,// particle type
2.0, // radius
20 // number of particles
);
});

Here is the full assembled script with all the pieces together:

/// <reference path="types/ceremony-api.d.ts" />
function onEnable() {
Logger.info('Welcome script loaded!');
Events.on('playerJoin', (serverPlayer) => {
// Wrap the raw player to access convenience properties
const player = wrapPlayer(serverPlayer);
// Only show the welcome once per hour
if (player.data.isOnCooldown('welcome_message')) {
return;
}
player.data.cooldown('welcome_message', 3600);
// Send a colored chat message
msg(serverPlayer, '<green>Welcome to the server, <bold>' + player.name + '</bold>!');
// Play a level-up sound (slightly louder than default, higher pitch)
sound(serverPlayer, 'entity.player.levelup', 0.8, 1.2);
// Show a large title on screen with fade in/stay/fade out
title(serverPlayer, '<gold>Welcome!', '<gray>' + player.name, 10, 60, 20);
// Spawn a particle circle at the player's feet
Game.particles.circle(
serverPlayer.serverLevel(),
player.pos.x,
player.pos.y + 0.5,
player.pos.z,
ParticleTypes.HAPPY_VILLAGER,
2.0,
20
);
});
}
function onDisable() {
Logger.info('Welcome script unloaded!');
}

  1. Save the file to scripts/welcome.js
  2. Run /ceremony reload in-game or from the server console
  3. Check the console for Welcome script loaded!
  4. Disconnect and rejoin the server
  5. You should see the chat message, hear the sound, see the title, and see particles at your feet

In this tutorial you learned how to:

ConceptAPI Used
Script lifecycleonEnable(), onDisable()
Event listeningEvents.on('playerJoin', callback)
Player wrappingwrapPlayer(serverPlayer)
Chat messagesmsg(player, miniMessage)
Sound effectssound(player, name, volume, pitch)
Screen titlestitle(player, main, sub, fadeIn, stay, fadeOut)
Cooldownsplayer.data.cooldown(), player.data.isOnCooldown()
ParticlesGame.particles.circle(...)

Now that you have a working script, explore these topics:

  • Common Patterns — Reusable code patterns for guards, traversal, scheduling, and more
  • Example Scripts — Full annotated scripts for treecapitator, vein miner, treasure hunting, and more
  • Game Namespace — Deep dive into Game.particles, Game.combat, Game.hud, and other namespaces