Skip to content

Syntax

Single-line comments start with // and continue until the end of the line. Multi-line comments start with /* and end with */.

// This is a single line comment
/* This is a
multiline comment */

These comments are completely ignored by the compiler and will not show up in the generated data pack. If you want to include a comment in the data pack, you can use the doccomment syntax.

/// This is a doccomment

Literal commands are commands that are directly included in the output. They start with a / and are followed by the command.

/say Hello, world!

This will result in say Hello, world! being included in the .mcfunction file.

Changed in v0.2.0

Functions are blocks of code that can be executed. They start with fn followed by the name of the function, arguments in parentheses and a block of code. Optionally they can be preceded by annotations. When a function has the pub keyword in front of it, it will be accessible from other files.

src/main.shu
#[tick]
fn main() {
/say Hello, world!
}
#[deobfuscate]
pub fn hello(macro name) {
/say I can be called from other files!
run `say Hello $(name)`;
}

This code defines a function called main that will be executed every tick.

Functions can be called by using their name followed by parenthesis. Arguments can be specified in the parenthesis separated by commas.

#[tick]
fn main() {
hello("World");
}
fn hello(macro name) {
run `say Hello, $(name)!`;
}
Since v0.2.0

To early end a function execution or to return a value, the return command was added in Minecraft 1.20. Similarly, the return statement is available in Shulkerscript.

#[deobfuscate = "returns_value"]
fn returnsValue() {
return 5;
}

Annotations are special attributes that can be attached to functions. They start with # followed by the name of the annotation in square brackets. Some annotations can have arguments assigned to them with the = operator. Currently, the following annotations are supported:

  • #[tick]: The function will be executed every tick.
  • #[load]: The function will be executed when the data pack is loaded.
  • #[deobfuscate]: The function will keep the original name in the output (path of the .shu-file followed by the function name).
  • #[deobfuscate = "path/to/function"]: The function will be named as specified in the argument.

Functions can be defined with parameters by specifying them in the parenthesis. When calling the function, the arguments have to be passed in the same order.

fn hello(macro greeting, macro name) {
run `say $(greeting), $(name)!`;
}
#[load]
fn main() {
hello("Hello", "world");
}

Keep in mind that if a function has parameters, they cannot be used with the annotations #[tick] or #[load], as arguments cannot be provided in that case.

Since v0.2.0

Shulkerscript provides some internal functions. These can be called like user-defined functions, but have access to the compilation process and therefore greater possibilities.

FunctionDescription
printAllows to print a string or macro string to the chat. Variables in the macro string are substituted at runtime.

Example:

#[load]
fn load() {
int x = 5;
print(`Value of x=$(x)`);
}
Since v0.2.0

To use variables inside of strings, template strings can be used. Instead of normal quotation marks ", the backtick ` is used for this type of string. Inside a template string, $(VAR_NAME) can be used to place the value of the variable at that position in the string.

When wanting to use it in a regular command, use the run syntax.

fn macroFunction(macro name) {
run `say Hello $(name)`;
}

Functions and global variables from other files can be imported by using the from-import syntax.

src/main.shu
namespace "foo";
from "./foo" import bar;
#[load]
fn main() {
bar();
}
src/foo.shu
namespace "foo";
pub fn bar() {
/say Hello, world!
}

Multiple functions can be imported by separating them with a comma.

from "./foo" import bar, baz;
Changed in v0.2.0

In Minecraft, tags are used to group multiple items, blocks, entities, etc. together. In Shulkerscript, tags can be defined right in the code, where they are needed.

tag<"block"> "foo" [
"minecraft:stone",
"minecraft:dirt"
]

This will result in a tag of type block with the name foo containing the blocks minecraft:stone and minecraft:dirt.

If you want the tag to replace, instead of append to the existing tag, you can use the replace keyword.

tag<"block"> "foo" replace [
"minecraft:stone",
"minecraft:dirt"
]

The type has to be the name of the subfolder in the tags folder of the data pack. Most often you will use tags with the types:

  • function
  • block
  • item
  • entity_type
  • fluid
  • game_event

But you can also use custom types, refer to this page for more information.

Conditional statements are used to execute code based on a condition. They start with if followed by a condition in parentheses and a block of code. Optionally they can be followed by an else block.

if ("block ~ ~-1 ~ minecraft:stone") {
/say On stone
} else {
/say Not on stone
}

To learn more about how to combine or negate conditions, refer to the if-else statement reference.

Execute blocks are used to execute a block of code in a specific context. They consist of the keyword you would pass to the /execute command followed the argument as a string in parentheses and a block of code.

as ("@a") { // execute as all players
/say Hello, world!
}

Multiple execute blocks can be chained together by separating them with a comma.

positioned ("0 0 0"), in ("minecraft:overworld") {
/setblock ~ ~ ~ minecraft:stone
}
  • align
  • anchored
  • as
  • at
  • asat
  • facing
  • if
  • in
  • on
  • positioned
  • rotated
  • store
  • summon

For general information on the execute command, refer to the Minecraft Wiki.

Groupings are used to group multiple commands into one mcfunction file without declaring a new function. This can be used for commands that need to be executed atomically.

group {
/say Hello
/say World
}
Since v0.2.0

Variables can be used to store values. There are different types available, and they differ in how they are stored, what data can be stored in them and how they can be used.

KeywordStorage methodUsage
intScoreboardSingle integer values
boolData StorageSingle boolean values
valCompiler memoryCan store any data type, but has to be known at compile time
int[NUMBER]ScoreboardArray of NUMBER integers
bool[NUMBER]Data StorageArray of NUMBER booleans
int[]ScoreboardMap of integers
bool[]Entity tagMap of booleans (keys have to be valid entities in the world)
int x = 5;
bool y = true;
int z = x + 2;
int[2] arr;
arr[0] = 1;
arr[1] = 2;

Read more in the reference.

Since v0.2.0

While loops can be used to execute commands multiple times. The entire loop takes place within a single game-tick. For running commands every tick, the #[tick] annotation should be used on a function.

while ("entity @p") {
/say @p
}

Compile-time conditions will be expanded at compile-time into a list of commands without requiring logic in Minecraft.

val x = 0;
while (x < 3) {
run `say $(x)`;
x = x + 1;
}

This will become

say 0
say 1
say 2

The run keyword is used to evaluate the following expression and include the resulting command in the output.

run "say Hello, world!";

The lua keyword is used to embed Lua code in your Shulkerscript code. It can be combined with the run keyword to include the result of the Lua code in the output.

run lua() {
-- Lua code goes here
return "Hello, Lua!";
};
Since v0.2.0

Additional properties of values can be accessed via member access (or dot notation). For example, .length gets the length of a string. Additional information about variable member access can be found in the variable reference. If you need the actual name of the macro used in a function, you can get it with .name.