The plugin grammar
specVersion 1
A NimBlock plugin is not code: it is a JSON file describing rules, read and executed by a Paper engine written once for everyone. This page states exactly what you are allowed to write in it, and nothing else is possible.
One rule, and nothing but a rule
The whole grammar fits in one sentence: when this happens, if these conditions hold, then do that. A plugin is a list of rules; a rule is one trigger, zero to 20 conditions and one to 50 actions.
No Java is generated anywhere, and nothing is compiled: the server loads an engine that reads the spec. That is the property that matters later, because a new Minecraft version is paid for once in that engine, and not in every plugin ever written with it.
What a spec looks like
{
"specVersion": 1,
"name": "Welcome",
"rules": [
{
"id": "welcome",
"name": "Welcome message",
"match": "all",
"trigger": { "type": "player.join" },
"conditions": [
{ "type": "player.has_permission", "params": { "permission": "nimblock.vip" } }
],
"actions": [
{ "type": "player.send_message", "params": { "message": "&6Welcome {player}!" } }
]
}
]
}
match is all (default) or any, and a
condition may carry "not": true. enabled: false keeps a
rule in the file without arming it.
The grammar, in EBNF
The terminal lists below are generated from the catalogue: they are literally the identifiers the engine can execute, not a hand-written transcription.
<plugin> ::= "{" "specVersion" ":" 1 ","
[ "id" ":" <slug64> "," ]
"name" ":" <label> ","
[ "description" ":" <text500> "," ]
"rules" ":" "[" <rule> { "," <rule> } "]" "}" (* 1..200 *)
<rule> ::= "{" [ "id" ":" <slug32> "," ]
"name" ":" <label> ","
[ "enabled" ":" <boolean> "," ] (* = true *)
[ "match" ":" ( "all" | "any" ) "," ] (* = "all" *)
"trigger" ":" <trigger> ","
[ "conditions" ":" "[" [ <condition>
{ "," <condition> } ] "]" "," ] (* 0..20 *)
"actions" ":" "[" <action>
{ "," <action> } "]" "}" (* 1..50 *)
<trigger> ::= "{" "type" ":" <trigger-id> [ "," "params" ":" <params> ] "}"
<condition> ::= "{" "type" ":" <condition-id> [ "," "not" ":" <boolean> ]
[ "," "params" ":" <params> ] "}"
<action> ::= "{" "type" ":" <action-id> [ "," "params" ":" <params> ] "}"
<params> ::= "{" [ <param-name> ":" <param-value>
{ "," <param-name> ":" <param-value> } ] "}"
<param-value> ::= <template> | <identifier> | <integer> | <number>
| <boolean> | <choice>
<template> ::= { <literal> | <variable> | <colour> } (* 0..2000 *)
<literal> ::= /[^{}]/ | "{{" | "}}"
<variable> ::= "{" /[a-zA-Z][a-zA-Z0-9_]*/ "}"
<colour> ::= "&" ( "0".."9" | "a".."f" | "k".."o" | "r" )
<identifier> ::= /[A-Za-z][A-Za-z0-9_:.\/-]{0,63}/
<slug32> ::= /[A-Za-z0-9_-]{1,32}/
<slug64> ::= /.{0,64}/
<label> ::= /.{1,64}/
<text500> ::= /.{0,500}/
<trigger-id> ::= "player.join"
| "player.quit"
| "player.chat"
| "player.death"
| "player.respawn"
| "block.break"
| "block.place"
| "command"
| "schedule.repeat"
<condition-id> ::= "player.has_permission"
| "player.is_op"
| "player.in_world"
| "player.health_below"
| "text.contains"
| "text.equals"
| "number.compare"
| "chance"
<action-id> ::= "player.send_message"
| "player.send_actionbar"
| "player.send_title"
| "player.play_sound"
| "player.give_item"
| "player.give_effect"
| "player.heal"
| "player.teleport"
| "player.kick"
| "broadcast"
| "server.run_command"
| "cancel_event"
| "log"
| "delay"
Two things do not show up in those production rules, because they are about
coherence rather than form. A condition or an action that requires a player can
only be written under a trigger that provides one: "heal the player" means nothing
under "every N seconds". And cancel_event can only be written under a
cancellable trigger. Both are rejected as you write, not at runtime.
Templates
Parameters of type text are templates:
{player} is replaced by what the trigger provided,
{{ gives a literal brace, and the colour codes
&a &l are translated.
A variable the trigger knows nothing about is an error, not text. Every trigger declares what it provides (see the list below): outside that list there is nothing to read, and a typo is rejected as you write rather than landing in the players' chat. A trigger's own parameters are not templates: they are read when the server starts, before there is anything to substitute.
Minecraft names come in two shapes depending on what they designate. An
item is named in either spelling (DIAMOND_SWORD or
diamond_sword). A sound or an effect
is named by its Minecraft key (entity.player.levelup,
speed) rather than the Bukkit constant: the key goes to the client
as is, and it survives version changes.
What the grammar does not allow
It is closed, and that is worth saying before anything else. What the catalogue lists is possible, the rest is impossible and will stay that way: there is no way to run arbitrary Java. That is what makes the studio instant (nothing to compile) and a spec harmless to install, wherever it came from.
Conditions do not nest: that is a deliberate limit of version 1,
a boolean tree draws badly in blocks, and all / any /
not are almost always enough. The one way out is the "run a command
as console" action, which on that server has exactly the power of the console,
no more and no less.
Triggers
What starts a rule, and the variables each one provides to templates.
player.join
A player joins
The moment the player enters the server, once their world has loaded.
provides:
{player} The player joining,
{world} The world they arrive in
player.quit
A player leaves
The moment the player leaves the server. They are still reachable, but any message sent is lost.
provides:
{player} The player leaving,
{world} The world they leave
player.chat
A player writes in chat
cancellable
Before the message is delivered to the other players. Cancellable: nobody then sees the message.
provides:
{player} Who wrote the message,
{world} Their world,
{message} The message written
player.death
A player dies
On the player's death, before the respawn screen.
provides:
{player} The player who died,
{world} Their world,
{killer} The player responsible, empty if there is none
player.respawn
A player respawns
When the player comes back into the game after dying.
provides:
{player} The player respawning,
{world} The respawn world
block.break
A player breaks a block
cancellable
Before the block disappears. Cancellable: the block stays put.
provides:
{player} The player breaking it,
{world} Their world,
{block} The block type (DIAMOND_ORE),
{x} Block X coordinate,
{y} Block height,
{z} Block Z coordinate
block.place
A player places a block
cancellable
Before the block is placed. Cancellable: the block is not placed.
provides:
{player} The player placing it,
{world} Their world,
{block} The block type placed,
{x} Block X coordinate,
{y} Block height,
{z} Block Z coordinate
command
A player types a command
Creates a new command on the server. ⚠️ A command whose name did not exist yet only appears after a server restart: Paper accepts new command names at startup only. The rule's contents, on the other hand, reload without a restart.
-
nametext Command name, without the slash -
descriptiontext optional Description shown in the help -
permissiontext optional Permission required, empty for everyone
provides:
{player} The player who typed the command,
{world} Their world,
{args} Whatever follows the command, as typed
schedule.repeat
Every N seconds
Repeats the rule for as long as the server runs. No player is involved: only actions that need none can be used.
-
secondsinteger [1..86400] Interval in seconds
Conditions
What filters. Zero to 20 per rule, combined by match.
player.has_permission
The player has the permission
needs a player
-
permissiontext Permission
player.is_op
The player is an operator
needs a player
player.in_world
The player is in the world
needs a player
-
worldtext World name
player.health_below
The player has fewer than X hearts
needs a player
In health points: 20 points make 10 hearts.
-
valuenumber [0..1024] Health points
text.contains
A text contains
-
texttext Text examined -
searchtext What to look for in it -
ignoreCaseboolean optional, default:trueIgnore case
text.equals
A text equals
-
texttext Text examined -
valuetext Expected value -
ignoreCaseboolean optional, default:trueIgnore case
number.compare
Compare two numbers
Both sides are templates: {y} compares against 62 without writing any code.
-
lefttext Left-hand side -
operator< | <= | == | != | >= | > Comparison -
righttext Right-hand side
chance
At random, X % of the time
-
percentnumber [0..100] Percentage
Actions
What happens. One to 50 per rule, executed in order.
player.send_message
Send a message to the player
needs a player
-
messagetext Message
player.send_actionbar
Show a message above the action bar
needs a player
-
messagetext Message
player.send_title
Show a title in large text
needs a player
-
titletext Title -
subtitletext optional Subtitle -
fadeInnumber [0..60] optional, default:0.5Fade in, in seconds -
staynumber [0..600] optional, default:3Duration in seconds -
fadeOutnumber [0..60] optional, default:0.5Fade out, in seconds
player.play_sound
Play a sound for the player
needs a player
-
soundidentifier Sound, as a Minecraft key (entity.player.levelup) -
volumenumber [0..10] optional, default:1Volume -
pitchnumber [0.5..2] optional, default:1Pitch
player.give_item
Give an item to the player
needs a player
Whatever does not fit in the inventory drops on the ground at their feet.
-
materialidentifier Item (DIAMOND_SWORD) -
amountinteger [1..2304] optional, default:1Amount
player.give_effect
Apply an effect to the player
needs a player
-
effectidentifier Effect, as a Minecraft key (night_vision) -
secondsinteger [1..86400] optional, default:10Duration in seconds -
amplifierinteger [0..255] optional, default:0Level, starting at 0
player.heal
Heal the player
needs a player
Health to full, hunger satisfied.
player.teleport
Teleport the player
needs a player
-
xnumber [-30000000..30000000] X coordinate -
ynumber [-512..1024] Height -
znumber [-30000000..30000000] Z coordinate -
worldtext optional World, empty for the player's own
player.kick
Kick the player
needs a player
-
reasontext Reason shown
broadcast
Announce to the whole server
-
messagetext Message
server.run_command
Run a command as the console
The grammar's escape hatch: anything the console can do. That is the console's full power over this server, so write it knowing what you are doing.
-
commandtext Command, without the slash
cancel_event
Cancel what just happened
needs a cancellable trigger
Only exists on cancellable triggers: the message is not delivered, the block is not broken.
log
Write to the server console
-
messagetext Message -
levelinfo | warn optional, default:infoLevel
delay
Wait
Pauses the rest of the rule's actions. A rule that waits can no longer cancel anything: "Cancel" must come first.
-
secondsnumber [0.05..3600] Seconds
The published files
The JSON Schema is generated from the catalogue on every deploy, and it is the machine-readable reference: an editor that follows it completes identifiers and rejects unknown parameters.
- plugin-1.schema.json : the schema, JSON Schema draft 2020-12.
- catalogue-1.json : the catalogue itself, the source everything else derives from (the validator, the schema, the editor blocks and the Java engine).
The catalogue carries French labels, French being the source language. The identifiers are not translated: they are what the engine executes.
What happens when the grammar changes
Adding a trigger, a condition or an action breaks nothing: a spec written before
keeps running. A breaking change increments
specVersion, and the engine then refuses what it does not understand
rather than running it sideways, and says so in the server console.
The URLs above carry that number:
plugin-1.schema.json will always designate this
grammar, even once another one exists.