Cobble
Cobble is a Python-like language that compiles to Minecraft Java Edition data
packs. You write functions, events, conditions, loops, and resources in .cbl
files, and the compiler turns them into .mcfunction files, function tags, and
JSON resources.
from stdlib import event
def on_load():
/tellraw @a {"text":"Cobble loaded","color":"green"}
def reward(player, amount):
/give {player} minecraft:diamond {amount}
stdlib.addEventListener(event.LOAD, on_load)
Cobble targets Minecraft Java Edition 26.3 (pack format 121.0). The current
crates.io release is 0.10.0. This is the nightly documentation, built from the
main branch, where 0.11 is in development. What it says about 0.11, such as
the per-pack <namespace>.vars objective and persist variables, may change
before the release.
Where To Start
- Installation and Your First Pack set up a project and load it in a world.
- The Language Guide covers the syntax chapter by chapter.
- The Standard Library lists the built-in helpers and resource declarations.
- The CLI reference documents every command.
You can also try Cobble without installing anything in the browser compiler.
Links
- Source and issues: github.com/deveworld/cobble
- Rust library API: docs.rs/cobble-lang
- Changelog and roadmap
Installation
Cobble is a Rust program published on crates.io as cobble-lang. Install it
with Cargo, which rustup installs along with Rust. The
installed command is cobble.
cargo install cobble-lang --locked
cobble --version
For a specific version:
cargo install cobble-lang --version 0.10.0 --locked
To build from source:
git clone https://github.com/deveworld/cobble.git
cd cobble
cargo build --release
# the binary is target/release/cobble
Requirements
- A current stable Rust toolchain to install or build Cobble.
- Minecraft Java Edition 26.3 to load the generated packs.
- Java and
curlfor command validation (cobble build --validate). The first validated build downloads the Minecraft server jar and extracts its command tree intodata/commands.json. Builds without--validateneed neither.
cobble doctor reports what is installed and whether the command tree matches
the supported Minecraft version.
Your First Pack
Create A Project
cobble init --name my_datapack
cd my_datapack
This writes cobble.toml, src/main.cbl, and a .gitignore.
cobble init --list-templates shows the other starter templates.
Write Some Code
Replace src/main.cbl with:
from stdlib import event
def on_load():
/tellraw @a {"text":"Hello from Cobble!", "color":"green"}
def reward(player, amount):
/give {player} minecraft:diamond {amount}
stdlib.addEventListener(event.LOAD, on_load)
on_load runs whenever the pack loads. reward takes two parameters, so it
becomes a macro function.
Build
cobble build --validate
The pack is written to output/. --validate checks every generated command
against the Minecraft 26.3 command tree; leave it out for a quick build.
cobble check reports source errors without building.
Load It In Minecraft
Copy or build the pack into a world’s datapacks/ folder:
cobble build -o ~/.minecraft/saves/MyWorld/datapacks/my_datapack
~/.minecraft is the Linux location. On Windows it is %APPDATA%\.minecraft
($env:APPDATA\.minecraft in PowerShell), and on macOS
~/Library/Application\ Support/minecraft.
Then, in the world:
/reload
/datapack list
/function my_datapack:reward {player:"@s", amount:3}
/datapack list shows the pack as enabled. To turn it off or on, use
/datapack disable "file/my_datapack" and /datapack enable "file/my_datapack".
Rebuild On Save
cobble link --world ~/.minecraft/saves/MyWorld
cobble watch --link
cobble link saves the world’s datapacks/ folder for this project, and
cobble watch --link rebuilds into it whenever a source file changes. Run
/reload in game to pick up each build.
If Something Goes Wrong
- The pack is missing from
/datapack list. Check that the folder is directly underdatapacks/and containspack.mcmeta, and that the world runs Minecraft 26.3. - A function is not found. Run
/reloadafter building, and call it with the project namespace, such asmy_datapack:on_load. - Changes do not appear. Check that the build went to the world you have
open, then
/reload. - Variables look wrong. Cobble keeps numeric variables on the
<namespace>.varsobjective:/scoreboard players get <name> my_datapack.vars.
Example Packs
The repository’s
examples/ folder
has complete packs you can build and use as-is or adapt:
| Example | What it is |
|---|---|
lobby | Server essentials: a first-join welcome and starter kit, a spawn teleport with a cooldown, playtime, death, and kill stats, a safe spawn zone, rotating tips, and a server menu dialog on the pause screen. |
parkour | A parkour course kit: build a course in game, then players get a timer, ordered checkpoints, fall resets, personal bests, a server record, and a first-clear advancement. |
magic_items | Three craftable right-click items with cooldowns: a Blink Rod, a Healing Staff, and a Frost Wand. |
hello_world.cbl | The smallest pack: a load message. |
Build a project example from its folder:
cd examples/lobby
cobble build --validate
The examples README lists the commands each pack adds and the Cobble features it uses.
Syntax And Types
Blocks And Comments
Cobble uses indentation-based blocks after : and needs no braces or
semicolons. Unexpected indentation and dedents that do not match a previous
block level are reported as diagnostics before parsing. Lines starting with /
are Minecraft commands; see Minecraft Commands.
# A comment runs to the end of the line.
def my_function():
"""
A docstring describes the function.
It can span multiple lines.
"""
/say Hello, world!
Data Types
Numbers
score = 10
health = 100
Numbers are internally stored as Minecraft scoreboard values.
Strings
message = "Hello, world!"
Booleans
is_active = True
is_disabled = False
Booleans are stored as integers in Minecraft scoreboards (0 for False, 1 for True).
Lists, Maps, and None
from stdlib import datapack
items = ["minecraft:diamond", "minecraft:emerald"]
config = {"chance": 1, "note": "missing"}
first_item = items[0]
chance = config.chance
datapack.predicate("maybe", {
"type": "minecraft:random_chance",
"chance": 1,
"comment": None
})
Lists and maps are storage-backed values. Cobble supports attribute and subscript access when the base value resolves to a storage path, including literal or constant subscript segments, runtime integer list/string indexes, and runtime string map keys. Arbitrary Python-style indexing is not a general runtime feature yet.
len(value) returns the length of literal arrays, maps, and strings, as well as
storage-backed list, map, and string values. Storage-backed lengths compile to a
Minecraft data get storage ... result stored in a scoreboard integer.
None is supported where Cobble serializes data pack JSON resources and is
emitted as JSON null. It has no scoreboard value, so keep it out of numeric
and boolean expressions. Minecraft NBT/SNBT storage has no null type, so
storage literals reject None; use an explicit sentinel value for storage.
Type System
A variable’s type is inferred from its first assignment and stays fixed after that:
x = 5 # integer
y = True # boolean
name = "Bob" # string
x = 20 # OK: still an integer
x = True # error: x is an integer
Assigning a value of another type is a type-mismatch error:
Error: type-mismatch: Type mismatch for variable 'x'.
╭─[ main.cbl:6:1 ]
│
6 │ x = True
│ ┬
│ ╰── Type mismatch for variable 'x'.
│
│ Note: Variable was previously defined as type: integer
│ Cannot reassign to type: boolean
│ Use a different variable name or keep all assignments to 'x' the same type.
───╯
Numeric Range And Precision
Numbers are scoreboard integers, so they range from -2147483648 to
2147483647. A float literal is truncated to an integer and a literal outside
the range is clamped, each with a warning:
pi = 3.14159 # warning: truncated to 3
huge = 9999999999 # warning: clamped to 2147483647
Variables
Numbers and booleans are fake players on the pack’s variable objective,
<namespace>.vars. Strings, lists, and maps live in the storage
<namespace>:global. Each pack gets its own objective and storage, so two
Cobble packs can use the same variable names on one server. The examples on
this page use the namespace mypack.
Module-level Variables
Variables defined at the top level (outside functions) are set in the
_cobble_init function every time the data pack loads, which includes
/reload and server starts:
score = 0
lives = 3
max_health = 20
Persistent Variables
Declare a module-level variable with persist to keep its value across
/reload and server restarts:
persist best_time = 0
persist champion = "nobody"
round = 1 # back to 1 on every load
The initializer only runs while the value is unset:
execute unless score best_time mypack.vars matches -2147483648.. run scoreboard players set best_time mypack.vars 0
execute unless data storage mypack:global vars.champion run data modify storage mypack:global vars.champion set value "nobody"
persist is only allowed at module level, and its initializer must be a
constant: a number, boolean, string, list, or map literal, or a const
expression. Assign the variable in a function to change it; the new value
stays until something changes it again.
The temp Objective In Raw Commands
Raw commands and execute headers name the variable objective temp. Cobble
rewrites temp to <namespace>.vars wherever a command expects an objective:
scoreboard arguments, if score and unless score, store result score,
scores={...} selector filters, and score text components.
def read_level():
level = 0
store result score level temp:
/scoreboard players get @s lvl
/execute if score level temp matches 10.. run say high level
This compiles to execute store result score level mypack.vars run scoreboard players get @s lvl and execute if score level mypack.vars matches 10.. run say high level. Other text, such as a chat message that contains the word
temp, stays unchanged. Commands typed in chat or in command blocks use the
real objective name, for example /scoreboard players get level mypack.vars.
Variables In Functions
Assignments inside a function run each time the function is called:
def my_function():
score = 0
score = score + 10 # Compiles to: scoreboard players add score mypack.vars 10
score = score - 5 # Compiles to: scoreboard players remove score mypack.vars 5
config = {"count": 1}
values = [1, 2, 3]
i = 1
config.count = 5
values[i] = config.count + 2
Storage-backed list, map, and string paths can be assigned through attribute or subscript targets inside functions. Dynamic list indexes and string map keys use the same runtime storage path support as storage reads.
The global Keyword
global name marks that a function changes a module-level variable:
score = 0
def increment_score():
global score
score = score + 1
Minecraft scoreboards have no local scope. Every variable with the same name
is the same fake player on the pack’s objective, so a function that assigns
score changes the module-level score with or without global. Use
global to make that intent visible, and use distinct names for values that
should stay separate.
Compile-time Constants
const declares a value that the compiler substitutes wherever the name is
used. Constants can appear in expressions, in raw commands as {NAME}, and as
persist initializers:
const MAX_HEALTH = 100
const BASE_DAMAGE = 10
const CRITICAL_MULTIPLIER = 2
def apply_damage():
health = MAX_HEALTH
damage = BASE_DAMAGE * CRITICAL_MULTIPLIER
/say Max health is {MAX_HEALTH}
/ inside a const declaration is real division; see
Integer Division And Modulo.
Functions
Function Definition
def greet():
"""Greet all players"""
/tellraw @a {"text":"Hello!", "color":"green"}
Functions with Parameters
Cobble supports function parameters using Minecraft’s macro system:
def give_reward(player, amount):
"""Give a reward to a player"""
/give {player} minecraft:diamond {amount}
/tellraw {player} {"text":"You received diamonds!", "color":"gold"}
Use {param_name} in Cobble source. Cobble compiles it to Minecraft’s
$(param_name) macro syntax, so the line becomes a macro line in the generated
function. Text outside the braces, such as minecraft:diamond, stays
literal.
Parameter names must be unique. Default parameter values, *args, and
**kwargs are not supported.
Calling Functions
def main():
greet()
give_reward("Steve", 5)
Functions can return integer scoreboard values:
def double(value):
return value * 2
def main():
score = double(5) + 1
Cobble compiles return expr to Minecraft’s function return value. Calls used
inside expressions are executed with execute store result score ... run function ..., so returned values are scoreboard integers. Integer-returning
user functions can be used in assignments, arithmetic expressions, and
conditions such as if ready(): or while should_continue():. Imported
functions have the same return metadata after their module is processed. Use
storage or explicit output variables for non-integer data.
Recursion is not supported. A function may not call itself, directly or
through a cycle of other functions. Recursive calls are rejected at compile
time with an error such as
Recursive call to 'fact' is not supported. Rewrite the recursion as a loop (while/for) instead.
Rewrite recursion as a while or for loop with explicit state.
Parameterized functions can call other parameterized functions and forward their own parameters:
def give_reward(player, item, count):
/give {player} minecraft:{item} {count}
def reward_nearby(player, item, count):
give_reward(player, item, count)
/tag {player} add rewarded
Control Flow
The examples on this page use the namespace mypack.
If, Elif, And Else
def check_score(score):
if score >= 10:
/say You have enough points!
/give @p minecraft:diamond
elif score >= 5:
/say Almost there.
else:
/say You need more points!
Comparisons are ==, !=, >, >=, <, and <=. Either side can be an
arithmetic expression or a call to a function that returns an integer:
def has_key():
return 1
def check_value(a, b):
if a % 5 == 2 and b ^ 2 == 25:
/say Both conditions are true!
if has_key() and not a == 0:
/say Function calls can drive conditions
Cobble evaluates complex operands into temporary scores before the comparison.
A nested if inside an if compiles to a helper function when its body needs
more than one command.
Boolean Operators
and, or, and not combine conditions in if, elif, while, and
execute-block if/unless modifiers:
def check_conditions():
x = 5
y = 10
if x > 0 and y < 15:
/say test
if x == 5 or y == 10:
/say test
if not x == 10:
/say x is not 10
and chains if clauses on one execute command, not turns if into
unless, and not not cancels out. or sets a temporary flag that each
operand can raise. The two tests above compile to:
execute if score x mypack.vars matches 1.. if score y mypack.vars matches ..14 run say test
scoreboard players set or_temp_0 mypack.vars 0
execute if score x mypack.vars matches 5 run scoreboard players set or_temp_0 mypack.vars 1
execute if score y mypack.vars matches 10 run scoreboard players set or_temp_0 mypack.vars 1
execute if score or_temp_0 mypack.vars matches 1 run say test
not (a and b) compiles with De Morgan’s laws, and or can nest inside and
and the other way round.
In an execute-block modifier, a condition that starts with a Minecraft
condition keyword (score , entity , block , data , predicate , …)
is passed through as raw Minecraft syntax. Other conditions go through the same
translation as if statements:
def check_players():
as @a if entity @s[tag=special]:
/say Special player!
Execute Blocks
Execute blocks run their body under Minecraft execute modifiers:
def reward_new_players():
as @a if score @s level matches 0:
/scoreboard players set @s level 1
/tellraw @s {"text":"Welcome!"}
A body that compiles to a single command is inlined
(execute as @a if ... run tellraw ...). A longer body is compiled into a
helper function called once per execution context
(execute as @a if ... run function ns:execute_temp_N), so the modifiers and
conditions are evaluated once per entity and the whole body runs for that
entity before the next one starts. Updating the condition’s own score inside
the body, loops, if/else, and multi-step assignments therefore behave per
entity. Blocks with a store modifier keep one execute store ... command per
body command, because storing a helper call would capture only the helper’s
return value; bodies containing a raw /return also keep that form so the
return still leaves the enclosing function.
Headers accept the same placeholders as raw commands: selector aliases,
{CONST} values, and {param} macro arguments, anywhere in the header.
SNBT and strings can appear in raw conditions too:
const RADIUS = 24
@Spawn = @e[type=minecraft:marker,tag=spawn,limit=1]
def heal_near_spawn():
at @Spawn as @a[distance=..{RADIUS}]:
/effect give @s minecraft:regeneration 3 0 true
def use_wand():
if items entity @s weapon.mainhand *[minecraft:custom_data~{wand:"frost"}]:
/say frost
For Loops
def spawn_particles():
for i in range(5):
asat @s:
/summon minecraft:pig ~ ~1 ~
/particle minecraft:heart ~ ~ ~ 0.5 0.5 0.5 0 10
def count_by_twos():
# Step support: increment by 2 each iteration
for i in range(10) by 2:
/say i = 0, 2, 4, 6, 8
def countdown():
# Negative step: count backwards
for i in range(10) by -1:
/say i = 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
def stepped_range():
for i in range(1, 6, 2):
/say i = 1, 3, 5
def literal_array():
for direction in ["north", "south"]:
/say direction = {direction}
def runtime_list():
values = [2, 4, 6]
total = 0
for value in values by 2:
total = total + value
def runtime_list_path():
groups = [[2, 3], [7, 11]]
row = 1
total = 0
for value in groups[row]:
total = total + value
def runtime_literal_array():
first = 2
total = 0
for value in [first, first + 3, 5]:
total = total + value
For loops over literal ranges and literal scalar arrays are expanded at compile
time and emit the repeated commands directly. range(...) loops with runtime
start, stop, count, or step expressions compile to scoreboard-backed recursive
helper functions. Runtime literal arrays such as [first, first + 3] are
materialized into temporary storage and then iterated as runtime lists. Loops
over storage-backed list expressions such as for value in values: or
for value in groups[row]: compile to an index loop that reads the list length
at runtime and copies each element into the loop variable before running the
body. by steps also work for runtime list loops; negative steps start at the
last item.
Unrolling Support:
range(n)iterates0..n.range(start, stop, step)uses Python-style half-open bounds.for i in range(n) by step:remains supported for compatibility.- Negative
byoverrange(n)starts atn - 1and counts down. - Runtime
range(...)supports dynamic count/start/stop/step expressions. - A statically known step of
0is rejected. If a dynamic step expression evaluates to0at runtime, the loop executes zero iterations so the generated datapack does not recurse forever. - Literal arrays containing only numbers, strings, or booleans are unrolled.
- Literal arrays containing runtime expressions are materialized and iterated at runtime.
- Runtime storage-backed list iteration supports list expressions/paths. The loop variable is mirrored as storage and as a score, so numeric list items can be used in arithmetic and any item can be displayed through storage-backed text output.
- Runtime list
bysteps support positive, negative, and dynamic integer expressions. A statically known step of0is rejected; a dynamic step that evaluates to0executes zero iterations. - A single loop may expand up to 1024 iterations. Expansions above 256 emit a warning.
- Nested unrolling is capped at 65,536 aggregate iterations and one outer unroll may emit at most 65,536 generated commands.
Loop problems are reported with these diagnostic kinds, located at the for
statement:
| Kind | Severity | Reported when |
|---|---|---|
unroll-limit-exceeded | error | a loop expands past 1024 iterations, nested loops past 65,536 iterations, or one outer loop past 65,536 commands |
unroll-large-expansion | warning | a loop expands past 256 iterations |
unroll-non-literal | error | the iterable is none of: a literal array, a literal or runtime range, a runtime literal array, or a storage-backed list |
unroll-bad-step | error | a range step is zero or non-integer |
unroll-bad-range | error | a range stops below its start with a positive step, or the array is empty |
runtime-for-unsupported | error | a runtime range has a non-integer bound or step, or a runtime list loop iterates something that is not a list |
runtime-for-bad-step | error | a static runtime step is zero, or a step is non-integer |
While Loops
def count_down():
counter = 10
while counter > 0:
/say @a Count: {counter}
counter = counter - 1
A while loop compiles to a function that calls itself until the condition is
false. All iterations run within one game tick, so a loop with many iterations
can stall the server. Spread long work across ticks with a tick event handler
or /schedule.
Match Statements
match branches on an integer value:
def check_score():
score = 75
match score:
case 0:
/say No score
case 1 to 50:
/say Low score
case 51 to 80:
/say Medium score
case 81 to 100:
/say High score
case _:
/say Out of range
case 5:matches exactly 5.case 1 to 10:matches 1 through 10, inclusive.case _:matches anything the earlier cases did not.
The first matching case runs and no other. Cases must not overlap; the compiler reports an error such as:
Match case overlap detected: case 3 to 7 overlaps with a previous case.
Previous case ended at: 5
Current case starts at: 3
Cobble compiles a match into a search tree of execute if score ... matches ... checks that splits the case ranges into quarters at each level. A case
body with one command is inlined; a longer body becomes a helper function.
Arithmetic
Numeric variables are scoreboard integers. Arithmetic compiles to
scoreboard players operation commands on the pack’s <namespace>.vars
objective.
def calculate():
a = 10
b = 3
total = a + b # 13
diff = a - b # 7
product = a * b # 30
quotient = a / b # 3
remainder = a % b # 1
power = b ^ 3 # 27
mixed = a + b * 2 - a / 5 # 14, standard precedence
a += 1 # same as a = a + 1
a -= 1
a *= 2
a /= 2
a %= 3
b ^= 2
Precedence
From highest to lowest:
^(power), right to left*,/,%, left to right+,-, left to right==,!=,<,<=,>,>=
How Expressions Compile
- Constant operands go through helper fake players such as
#multiplier,#divisor, and#modulus, becausescoreboard players operationonly takes scores. - Intermediate results use fake players such as
#expr_temp_<n>. - A power with a constant exponent is expanded at compile time:
x ^ 3becomesx * x * x. - A power with a runtime exponent calls a generated helper function. A
negative runtime exponent gives
0, since scores cannot hold fractions. - Compound assignment works on variables and storage-backed targets.
x += ycompiles asx = x + y. - Expressions made only of constants are folded at compile time.
Integer Division And Modulo
/ and % follow Minecraft’s scoreboard /= and %=: division rounds
toward negative infinity, and modulo is a floor modulus whose sign follows the
divisor. Constant folding uses the same rules, so a folded expression and the
same expression computed from variables agree:
a = -7 / 2 # -4
b = -7 % 3 # 2
c = 1 / 3 * 3 # 0, each step is integer arithmetic
const declarations are compile-time values that raw commands may substitute,
so / in a const declaration is real division: const HALF = 1 / 2
substitutes 0.5 into {HALF}. % is the same floor modulus there. Storing
a fractional constant in a score truncates it, with a precision warning.
Division By Zero
Dividing by a constant zero is a compile error:
Division by zero in constant expression: 10 / 0
A divisor that is only known at runtime is not checked. Minecraft rejects the
operation, the command fails, and the target keeps the left operand’s value,
so 10 / b with b = 0 leaves 10. Check the divisor first:
def share(total, players):
if players != 0:
each = total / players
else:
each = 0
Minecraft Commands
A line that starts with / is a Minecraft command. Cobble writes it to the
.mcfunction file without the slash:
def setup():
/scoreboard objectives add deaths deathCount
/gamerule spawn_mobs false
A # comment after a command is removed from the output.
JSON Text
tellraw, title, and other commands with JSON text work as written:
def announce():
score = 10
/tellraw @a {"text":"Game Started!", "color":"gold", "bold":true}
/title @a title {"text":"Welcome", "color":"aqua"}
/tellraw @a [{"text":"Score: {score}", "color":"gold"}, {"text":" ready"}]
In tellraw and title, a {variable} inside a JSON text field or a JSON
array string becomes a score or NBT text component. The styling of the
surrounding text is kept. A /say line with a {variable} becomes a
tellraw @a command with the same components. Unlike say, that message has
no [name] prefix, and selectors in it such as @s appear as plain text.
Placeholders
{name} inserts a function parameter, a constant, or the variable of a loop
that Cobble expands at compile time (see
For Loops) into a raw command:
def teleport_player(player, x, y, z):
/tp {player} {x} {y} {z}
/tellraw {player} {"text":"Teleported!", "color":"green"}
Function parameters become macro arguments, so that line compiles to a macro
line such as $tp $(player) $(x) $(y) $(z). Constants and the values of those
loops are substituted at compile time.
A Cobble variable, including the variable of a runtime loop, works as a
placeholder only in say, tellraw, and title text, as described under
JSON Text. In any other command it is a compile error. To use a
variable’s value there, pass it to a function parameter:
count = 3
def give_diamonds(amount):
/give @a minecraft:diamond {amount}
def reward():
give_diamonds(count)
Unknown, invalid, and unclosed placeholders are errors. Write {{ and }} for
literal braces:
def help():
/say Type {{name}} to see a placeholder
say Type {name} to see a placeholder
The temp Objective
Raw commands refer to Cobble variables through the objective name temp,
which Cobble rewrites to the pack’s <namespace>.vars objective. See
The temp Objective In Raw Commands.
Selectors And Entity Templates
Entity Selector Definitions
@Name = @selector[...] defines a selector alias at module level:
@Player = @a[type=player,gamemode=survival]
@Boss = @e[type=zombie,tag=boss]
@Admin = @a[tag=admin]
def give_rewards():
as @Player:
/give @s diamond
as @Boss:
/effect give @s strength 10 2
def admin_command():
/tellraw @Admin {"text":"Admin message"}
Cobble replaces each alias at compile time, matching whole names only, in raw commands and in execute-block targets and conditions. Aliases are shared by every file in a project, so define each one once.
Entity Templates
Entity templates name a reusable summon shape. Define a template with
define @Name = @selector[...], provide the NBT payload after create, and end
the declaration with end. Create an entity from the template with
create @Name inside a function or execute block.
define @ShopKeeper = @e[type=villager,tag=shop_keeper]
create {
"VillagerData": {
"profession": "minecraft:librarian",
"level": 5,
"type": "minecraft:plains"
},
"CustomName": {"text": "Shop Keeper", "color": "gold"},
"Tags": ["shop_keeper"],
"PersistenceRequired": True,
"NoAI": True
}
end
def spawn_shop():
as @a[tag=spawn_shop] at @s:
create @ShopKeeper
create @ShopKeeper compiles to a summon at the current execute position:
execute as @a[tag=spawn_shop] at @s run summon minecraft:villager ~ ~ ~ {VillagerData:{...},CustomName:{...},Tags:["shop_keeper"],PersistenceRequired:1b,NoAI:1b}
The payload uses Cobble map and list literals and is written as SNBT. The
entity type comes from the selector’s type= argument, so give every template
one. Other selector arguments are not applied to the new entity; add Tags to
the payload, as above, when the alias should match the entities it creates.
create @Name must name a template defined in the project, and template names
share the project-wide name set with functions and selector aliases.
Modules And Imports
A project can split its code across several .cbl files:
src/
├── main.cbl
├── player.cbl
└── utils.cbl
import name loads name.cbl from the importing file’s directory:
# utils.cbl
def helper_function():
/say Helper called
@AllPlayers = @a[gamemode=!spectator]
# main.cbl
import utils
def test():
helper_function()
utils.helper_function()
as @AllPlayers:
/say Test
Imported functions, selector aliases, and entity templates join one shared set
of names, so a function can be called as helper_function() or
utils.helper_function(). from utils import helper_function also checks that
the name exists in utils.cbl. Imported functions that return an integer can
be used in expressions and conditions.
These are compile errors:
- the same function, selector alias, or entity template name in two files;
- a circular import (the message shows the import chain);
- a missing file (the message shows the importing file and the expected path).
Use one import per line. Import aliases (import a as b), wildcard imports,
import a, b, and dotted or relative module names are not supported.
Building A Directory
cobble build src/ compiles every file in the directory. With a
cobble.toml, [build] entry_points limits the build to the listed files, and
their imports are resolved from there. See
Project Configuration.
Events
Register a function to run when the pack loads or on every game tick:
from stdlib import event
def on_load():
/scoreboard objectives add kills dummy
/tellraw @a {"text":"Data pack loaded!", "color":"green"}
def on_tick():
as @a at @s:
/particle minecraft:happy_villager ~ ~2 ~ 0.5 0.5 0.5 0 1
stdlib.addEventListener(event.LOAD, on_load)
stdlib.addEventListener(event.TICK, on_tick)
event.LOADruns when the pack loads: on/reloadand on server start.event.TICKruns every game tick, 20 times per second.
Cobble adds the functions to the minecraft:load and minecraft:tick function
tags.
Differences From Python
Cobble borrows Python’s look, but programs run as Minecraft commands. Values
are scoreboard integers or storage entries, and every function is a
.mcfunction file.
Not Supported
These fail with a diagnostic before compilation:
- classes, decorators,
try/except/finally,with,lambda,yield,async,await,break,continue,raise,assert,del, andnonlocal; - default parameter values, duplicate parameter names,
*args, and**kwargs; - list and dict comprehensions;
for ... else;- dotted or relative imports, import aliases, and
import a, b; - recursion, direct or through other functions.
cobble check --experimental-python-compat lists the supported Python-like
constructs and suggests a Cobble rewrite for unsupported ones; see
Experimental Features.
Values And Expressions
- Numbers are 32-bit integers. Floats are truncated, with a warning.
- Lists and maps live in storage. Subscripts work when the base resolves to a
storage path. List and string indexes may be literals, constants, or runtime
integers such as
items[i]; map keys may be attributes, string literals, or runtime strings such asconfig[key]. - Functions return integers only. Use storage or a module variable for other results.
- A bare expression such as
score + 1produces no command and is rejected. Use an assignment, a call, a raw command, orpass. forloops iteraterange(...), literal arrays, and storage-backed lists.- All of a pack’s variables share one scoreboard objective, so there is no
local scope; see The
globalKeyword. whileloops and runtimeforloops compile to functions that call themselves, and every iteration runs in the same game tick.
Browser Compiler
The browser compiler at /try compiles one in-memory file. Check projects
with local imports using the CLI, which resolves files from disk.
Standard Library Modules
The standard library is a set of helpers that the compiler turns into commands and resource files. They are grouped into modules; import a module before calling its helpers:
from stdlib import event, text, score
| Import | Effect |
|---|---|
from stdlib import text | Activates the text module. |
from stdlib import text, score | Activates text and score. |
from stdlib import score.objective | Activates score.objective, which from stdlib import score leaves inactive. |
import stdlib | Activates every module, as in 0.7.x. |
The modules are text, score, score.objective, random, timer,
storage, item_component, schedule, selector, position, bossbar,
team, entity, math, event, datapack, and the experimental
resource_pack. This page covers the command helpers. event is described in
Events, datapack and item_component in
Data Pack Resources, and resource_pack in
Resource Packs.
Calling a helper from a module that isn’t imported fails with the
missing-stdlib-module diagnostic:
Error: missing-stdlib-module: module 'text' not imported.
Add `from stdlib import text` or use `import stdlib` to enable all modules.
See https://deveworld.github.io/cobble/docs/stdlib/modules.html for the import and versioning model.
The [stdlib] version setting in cobble.toml controls this check.
version = 2 is the default and requires the imports above. version = 1
keeps every module active as in 0.7.x and prints a deprecation warning once per
build. Helper behavior and generated output are the same under both versions.
[stdlib]
version = 2
Text
from stdlib import text
def notify():
text.tellraw("@a", {"text": "Loaded", "color": "green"})
text.tellraw("@a", text.plain("Plain message"))
text.tellraw("@a", text.colored("Gold message", "gold"))
text.tellraw("@a", text.score("@s", "points"))
text.tellraw("@a", text.selector("@p"))
text.title("@a", "Ready")
text.subtitle("@a", {"text": "Go", "bold": True})
text.actionbar("@a", "Running")
text.plain, text.colored, text.score, and text.selector return JSON
text components for use inside other helpers or JSON resource declarations.
Colors accept Minecraft named colors or #RRGGBB.
Score
from stdlib import score, score.objective
def update_score():
score.set("points", 10)
score.add("points", 5)
score.remove("points", 2)
score.copy("backup", "points")
score.operation("points", "+=", "backup")
score.reset("backup")
score.objective.add("points", "dummy", "Points")
score.objective.display("sidebar", "points")
Score helpers use the pack’s variable objective, <namespace>.vars.
Random
from stdlib import random
def roll():
random.int("roll", 1, 6)
random.bool("coin")
Random helpers compile to Minecraft’s random value command and store
the result in a scoreboard value. A single-value random.int("score", n, n)
range is lowered to a direct scoreboard set because Minecraft rejects
degenerate random value n..n ranges.
Timer
from stdlib import timer
def cooldown():
timer.set("cooldown", 20)
timer.tick("cooldown")
timer.done("cooldown") # writes cooldown_done as 0 or 1
timer.reset("cooldown")
Storage
from stdlib import storage
def save_status():
storage.set("status", {"ready": True, "count": 3})
storage.merge("status", {"extra": "ok"})
storage.copy("status_copy", "status")
storage.set(storage.path("players", "current"), "alex")
storage.append("events", "loaded")
storage.get(storage.index("events", 0))
storage.read_score("event_count", "events")
storage.read_score("ready_flag", "status.ready", 10)
storage.remove("status.extra")
Storage helpers write to <namespace>:global. storage.read_score(score, path) stores what data get returns: a number (floored), or the size of a
list or compound, or the length of a string. With the optional third scale
argument, the value at path must be a number. storage.path(),
storage.child(), and storage.index() return storage path strings for use
inside storage helpers; they do not emit commands when used by themselves.
Schedule, Bossbar, Team, Entity, Selector, And Position
from stdlib import schedule, bossbar, team, entity, selector, position
def setup_ui():
schedule.once("tick", "20t", "replace")
schedule.clear("tick")
bossbar.add("timer", "Timer")
bossbar.set_max("timer", 100)
bossbar.set_players("timer", "@a")
team.add("runners", "Runners")
team.modify("runners", "color", "green")
entity.tag_add("@a", "runner")
entity.effect_give("@a", "minecraft:speed", 10, 1, True)
entity.teleport(selector.current(), position.relative(0, 1, 0))
selector.* and position.* return plain selector or coordinate strings for
use inside other helpers and emit no commands when used by themselves. These
helpers are thin wrappers over Minecraft commands, so use raw commands when a
specialized option is not covered yet.
Math
from stdlib import math
def calculate():
root = math.sqrt(100)
magnitude = math.abs(-50)
low = math.min(10, 20)
high = math.max(10, 20)
math.sqrt uses a generated scoreboard helper.
Data Pack Resources
Top-level datapack.* declarations generate tags, predicates, advancements,
loot tables, recipes, item modifiers, and dialogs as JSON files in the 26.x
folder layout. Resource IDs must use lowercase namespace:path syntax or lowercase relative
paths. Cobble reports uppercase characters, invalid path separators, and common
namespace/path mistakes with focused diagnostics.
from stdlib import datapack
datapack.function_tag("utility", ["mypack:setup"])
datapack.function_tag("minecraft:load", ["mypack:setup"])
datapack.block_tag("solid_blocks", ["minecraft:stone"])
datapack.item_tag("reward_items", [
"minecraft:diamond",
{"id": "minecraft:netherite_ingot", "required": False}
])
datapack.entity_type_tag("targets", ["minecraft:zombie"])
datapack.predicate("is_sneaking", {
"type": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"flags": {
"is_sneaking": True
}
}
})
datapack.dialog("notice", {
"type": "minecraft:notice",
"title": {"text": "Notice"}
})
# Adds a button for the dialog to the pause screen.
datapack.dialog_tag("minecraft:pause_screen_additions", ["mypack:notice"])
Supported resource declarations:
datapack.function_tag(name, values, replace=False?)datapack.block_tag(name, values, replace=False?)datapack.item_tag(name, values, replace=False?)datapack.entity_type_tag(name, values, replace=False?)datapack.dialog_tag(name, values, replace=False?)datapack.predicate(name, json)datapack.advancement(name, json)datapack.loot_table(name, json)datapack.recipe(name, json)datapack.item_modifier(name, json)datapack.dialog(name, json)
Typed tag declarations with the same ID are merged, deduplicated, and sorted.
Tag values may be string resource IDs or object entries with id and optional
required. Object entries with required: true are equivalent to strings;
required: false entries remain optional Minecraft tag entries.
Pass an optional third literal boolean argument to emit tag replace; when
multiple declarations are merged, True wins over False.
Pass-through JSON declarations with identical JSON are accepted once; different
JSON for the same ID is a compile error.
Resource names may use nested paths and explicit namespaces, such as
other_pack:checks/is_ready. Predicate, advancement, loot table, recipe, item
modifier, and dialog declarations require object JSON values.
JSON resources are written as given, in Minecraft 26.3’s formats. Because a
server whose packs fail to load refuses to start, Cobble reports the common
pre-26.3 shapes as datapack-resource-format errors:
- predicates use
type(formerlycondition), including insideall_of,any_of, andinvertedterms; - item modifiers use
type(formerlyfunction) and a singlecondition(formerly aconditionslist); - loot tables use
modifier(formerlyfunctions) and a singlecondition(formerlyconditions) on tables, pools, and entries, and aminecraft:tagentry names its tag initems("#minecraft:logs"); - a root advancement with
displaysetsdisplay.background; - cooking recipes set
cookingtime.
Minecraft silently ignores lists such as conditions and functions, so
Cobble reports them too.
Item component helpers build the component JSON for minecraft:set_components
item modifiers:
from stdlib import datapack, item_component, text
datapack.item_modifier("items/reward_components", {
"type": "minecraft:set_components",
"components": item_component.components(
item_component.custom_name(text.colored("Cobble Reward", "gold")),
item_component.lore([text.plain("Generated by Cobble")]),
item_component.unbreakable()
)
})
item_component.* helpers return JSON component objects for resource
declarations and emit no commands when used by themselves.
Checking The Output
Generated resources are listed in the build manifest and the source map,
included in ZIP output, and reported by cobble inspect --json:
cobble check --json
cobble build --validate --zip
cobble inspect output --json
Resource Packs (Experimental)
Cobble can also write resource-pack assets next to the data pack. The feature
is experimental and off by default. Enable it with
cobble build --experimental-resource-pack or in cobble.toml:
[experimental]
resource_pack = true
Then import the resource_pack module.
Models And Language Files
from stdlib import resource_pack
resource_pack.item_model("mypack:custom_sword", {
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "mypack:item/custom_sword"
}
})
resource_pack.block_model("display_block", {
"parent": "minecraft:block/cube_all",
"textures": {
"all": "mypack:block/display_block"
}
})
resource_pack.lang("en_us", {
"item.mypack.custom_sword": "Custom Sword",
"block.mypack.display_block": "Display Block"
})
resource_pack.item_model writes assets/<namespace>/models/item/<path>.json,
resource_pack.block_model writes
assets/<namespace>/models/block/<path>.json, and resource_pack.lang writes
assets/<namespace>/lang/<locale>.json. The same lowercase namespace/path
validation used by datapack.* applies here. JSON must be an object, and lang
values must be strings. Multiple lang declarations for the same namespace and
locale merge when repeated keys have identical values; conflicting values are
an error.
Static Assets
When resource-pack output is enabled, Cobble also copies static files from the
project assets/ directory into the generated pack. Use
assets/<namespace>/... for textures, sounds, blockstates, and other
resource-pack files:
assets/mypack/textures/item/custom_sword.png
assets/mypack/models/item/generated_wand.json
Static asset paths must stay inside the project assets/ tree, use lowercase
Minecraft-safe path segments, and must not be symlinks. A validated build
stages generated and static assets before it replaces the final output.
Diagnostics
Resource-pack helpers report these diagnostic kinds:
| Kind | Reported when |
|---|---|
resource-pack-not-enabled | a resource_pack.* helper is called without the opt-in |
resource-pack-bad-id | a resource ID or locale name is malformed |
resource-pack-non-object | a model or lang payload is not a JSON object |
resource-pack-duplicate | two declarations write the same path with different JSON |
Checking The Output
ZIP output includes assets/, and cobble inspect --json lists generated
models, lang files, and copied assets:
cobble build --experimental-resource-pack --validate --zip
cobble inspect output --json
CLI Commands
cobble <command> --help prints the options for each command. Commands that
read a project look for cobble.toml in the given path or the current
directory; see Project Configuration.
cobble init
Initialize a new Cobble project. Without --name, Cobble initializes the
current directory. With --name <NAME>, Cobble creates and initializes that
project directory.
cobble init [OPTIONS]
Options:
--name <NAME>- Set the project name (default: current directory name)--description <DESC>- Set the project description--pack-format <NUM>- Set the pack format version (default:121.0; Cobble currently requires Minecraft Java Edition 26.3)--template <NAME>- Starter template:minimal,stdlib,validation,resource-heavy,game-mechanic,web-demo, orplugin-diagnostics(default:stdlib)--list-templates- List available templates and exit without writing files
Example:
cobble init --name my_datapack --description "My awesome data pack"
cobble init --name smoke_pack --template validation
cobble init --name demo_pack --template web-demo
cobble init --name plugin_pack --template plugin-diagnostics
cobble init --list-templates
After creation, Cobble prints the exact next commands for the selected target
directory, including cobble build --dry-run, cobble build --validate, and
cobble watch.
Before writing project files, init refuses target paths that traverse an
existing symlink component.
This creates:
cobble.toml- Project configuration filesrc/main.cbl- Main source file with example code.gitignore- Git ignore file
cobble build
Compile Cobble source files into a Minecraft data pack.
cobble build [SOURCE] [OPTIONS]
Arguments:
SOURCE- Source file or directory to compile. When omitted, Cobble usesbuild.sourceandbuild.entry_pointsfromcobble.toml.
Options:
-o, --output <DIR>- Output directory for the data pack (default:./output)--namespace <NAME>- Override the namespace (default: from cobble.toml or directory name)--pack-format <NUM>- Override pack format version (currently must be121.0;121is accepted as the same format)--description <DESC>- Override pack description-v, --verbose- Show verbose output-q, --quiet- Suppress successful build progress and summary output--zip- Create a ZIP archive of the data pack--experimental-resource-pack- Enable experimentalresource_pack.*output and projectassets/passthrough--validate- Validate generated.mcfunctionfiles after building--dry-run- Compile and print the build summary without writing final output--commands-json <PATH>- Path tocommands.jsonfor validation (default:data/commands.json)
Examples:
# Build current project
cobble build
# Build specific file
cobble build src/main.cbl
# Build with custom output
cobble build -o ~/minecraft/saves/MyWorld/datapacks/my_pack
# Build a single example file
cobble build examples/hello_world.cbl
# Build and create ZIP file
cobble build --zip
# Build and validate generated commands
cobble build --validate
# Build quietly for scripts
cobble build --quiet
# Compile and inspect the output plan without writing output
cobble build --dry-run
# Dry-run with command validation through a temporary staging output
cobble build --dry-run --validate
# Build with all options
cobble build src/ -o output/ --namespace mypack --pack-format 121.0 --zip --validate --verbose
# Build with the supported pack format explicitly
cobble build --pack-format 121.0
When a build would write files, Cobble refuses output paths that are existing
non-directories, traverse an existing symlink component, or contain existing
symlink descendants. This applies to normal builds, validated builds, and
--dry-run --validate staging output. A pure --dry-run does not write output.
A plain (non---validate) build into an existing non-empty output directory
also requires this project’s ownership marker: a
.cobble/build_manifest.json whose namespace and project_id match the
current project. Unmarked directories and directories owned by another Cobble
project are refused; pick a new or empty output directory, or use
cobble build --validate for staged publishing. Inside an owned output
directory a build deletes and rewrites what Cobble generates: the
data/<namespace>/function/ tree, the .cobble/ metadata directory,
data/minecraft/tags/function/, namespaces recorded in
.cobble/generated_namespaces.json by the previous build, and the generated
JSON resource directories (tags, advancements, loot tables, recipes,
predicates, item modifiers, dialogs) of generated namespaces. Files Cobble
does not generate elsewhere in the directory are left in place.
When --validate is enabled, Cobble fails the build if any generated command is
invalid for Minecraft Java Edition 26.3. If the default data/commands.json
is missing, Cobble downloads the Minecraft server jar and generates it
automatically. This requires curl and Java. Cobble tries Mojang’s current
Piston manifest host, the legacy launcher manifest host, and a pinned 26.3
server jar URL. The default data/commands.json path is refused if it would
traverse an existing symlink component; use an explicit --commands-json path
for a deliberate custom command tree.
--dry-run parses and transpiles sources, prints the same build summary, and
does not replace or clean the final output directory. When combined with
--validate, Cobble writes only a temporary staging data pack, validates it,
and removes the staging directory afterward. --dry-run cannot be combined
with --zip.
--quiet suppresses successful build progress and summary output. If validation
fails, Cobble still prints validation diagnostics before returning an error.
Compiler warnings are still shown. --quiet cannot be combined with
--verbose.
If your network blocks those endpoints, use one of these overrides:
COBBLE_COMMANDS_JSON_URL=https://example.com/commands.json cobble build --validate
COBBLE_MINECRAFT_SERVER_URL=https://example.com/server.jar cobble build --validate
COBBLE_MINECRAFT_SERVER_JAR=/path/to/server.jar cobble build --validate
COBBLE_MINECRAFT_SERVER_SHA1=<sha1> COBBLE_MINECRAFT_SERVER_URL=https://example.com/server.jar cobble build --validate
For custom command-tree paths, generate the file manually:
scripts/setup_commands_json.sh 26.3
cp data/commands.json /tmp/commands.json
Every non-dry-run build writes .cobble/build_manifest.json with the Cobble
version, Minecraft target, pack format, namespace, source input, configured
entry points, compiled files, generated namespaces, generated function/resource
counts, generated resource entries, and validation summary when validation ran.
.cobble/source_map.json is written when generated commands are available to
map generated commands and generated JSON resources back to Cobble source.
Source-map file paths are written relative to the project/source root when
Cobble can determine one, avoiding unnecessary absolute paths in generated
metadata.
See Metadata Files for the stable field list.
cobble check
Check Cobble source files without building. check reports the source-aware
diagnostics listed in Diagnostics with file, line, and
column, including diagnostics in imported files.
cobble check [SOURCE] [OPTIONS]
Options:
--json- Print a machine-readable report withschema_version,ok,source,files_checked,files,diagnostics, anderror_count--symbols- Include experimental document-symbol metadata; requires--json--experimental-plugins- Enable the experimental diagnostics-only plugin host skeleton--experimental-python-compat- Include the experimental diagnostics-only Python compatibility report
Example:
cobble check src/main.cbl
cobble check examples/
cobble check --json src/main.cbl
cobble check --json --symbols src/main.cbl
cobble check --json --experimental-plugins src/main.cbl
cobble check --json --experimental-python-compat src/main.cbl
JSON output is written to stdout. On failure the process still exits non-zero;
human-oriented error text may be written to stderr while stdout remains valid
JSON. --symbols adds an experimental_symbols array for editor prototypes;
that field is experimental even though the top-level JSON report has
a stable schema_version. --experimental-plugins and
--experimental-python-compat add the experimental_plugins and
experimental_python_compat objects described in
Experimental Features.
cobble watch
Watch source files for changes and automatically rebuild.
cobble watch [SOURCE] [OPTIONS]
Arguments:
SOURCE- Source file or directory to watch. When omitted, Cobble usesbuild.sourcefromcobble.toml.
Options:
-o, --output <DIR>- Output directory for the data pack--namespace <NAME>- Override the namespace--pack-format <NUM>- Override pack format version--description <DESC>- Override pack description-v, --verbose- Show verbose output--zip- Create a ZIP archive after each build--experimental-resource-pack- Enable experimentalresource_pack.*output and projectassets/passthrough--link- Build into the pack path configured bycobble link--validate- Validate generated.mcfunctionfiles after each successful build--commands-json <PATH>- Path tocommands.jsonfor validation (default:data/commands.json)
Examples:
# Watch current directory
cobble watch
# Watch with custom output
cobble watch src/ -o ~/minecraft/saves/MyWorld/datapacks/my_pack
# Watch and validate after each rebuild
cobble watch src/ --validate
# Watch into a configured linked datapack
cobble link --datapacks ~/minecraft/saves/TestWorld/datapacks
cobble watch --link --validate
# Watch with all options
cobble watch src/ -o output/ --namespace mypack --zip --validate --verbose
--link cannot be combined with --output or --namespace. Linked outputs use
the project namespace from cobble.toml so link --status, doctor --json,
watch --link, and clean --linked can validate the same manifest ownership
marker.
This will:
- Perform an initial build
- Watch for changes in
.cbl,.cobble, andcobble.tomlfiles - Coalesce rapid editor save events into one rebuild
- Ignore generated output,
.cobble/, staging directories, ZIP files, and common editor temporary files - Reload valid
cobble.tomlchanges and update the watched source directory - Show timestamped build status and any errors
- Continue watching until Ctrl+C is pressed
When --validate is enabled, watch uses the same staging behavior as
cobble build --validate: a failed rebuild or validation failure does not
replace the last valid output.
cobble fmt
Format Cobble source files.
cobble fmt [SOURCE] [OPTIONS]
Arguments:
SOURCE- Source file or directory to format. When omitted, Cobble usesbuild.sourcefromcobble.toml. Formatting a directory formats all.cbland.cobblefiles below it.
Options:
--check- Check formatting without writing changes. The command exits non-zero when any file would be reformatted.--diff- Print formatter differences without writing changes. The command exits non-zero when any file would be reformatted.
Examples:
cobble fmt src/
cobble fmt --check examples/
cobble fmt --diff src/main.cbl
cobble fmt src/main.cbl
The formatter is conservative. It normalizes indentation, line endings, trailing whitespace, a UTF-8 BOM, blank EOF padding, and the final newline while preserving raw Minecraft command payloads, string contents, multiline docstring bodies, inline JSON/SNBT-looking text, and comments. Cobble validates the formatted candidate before writing; if any target file still has language or syntax diagnostics, formatting aborts and no files are written. Formatting refuses source paths and target files that traverse existing symlink components.
cobble validate
Validate generated .mcfunction files against Minecraft Java Edition 26.3’s command tree.
cobble validate <DATAPACK_DIR> [OPTIONS]
Arguments:
DATAPACK_DIR- Generated data pack directory to validate
Options:
--commands-json <PATH>- Path tocommands.jsongenerated from the Minecraft server reports (default:data/commands.json; auto-generated when missing)
Examples:
# Build and validate a data pack; data/commands.json is generated if missing
cobble build -o output
cobble validate output
# Use a custom command tree path
scripts/setup_commands_json.sh 26.3
cp data/commands.json /tmp/commands.json
cobble validate output --commands-json /tmp/commands.json
The validator uses Minecraft’s exported Brigadier command tree, including 26.x commands such as dialog, fetchprofile, waypoint, stopwatch, version, and return run.
It also applies the tree’s permission levels: data pack functions run at level 2
(gamemasters, the default function-permission-level), so commands that need
a higher level, such as transfer, tick, op, or stop, are reported because
Minecraft refuses to load a function that uses them. If your server raised
function-permission-level, set COBBLE_FUNCTION_PERMISSION_LEVEL (0-4) to the
same value. Macro lines follow Minecraft’s template rules: each $ line needs
at least one $(variable), and variable names are letters, digits, or _.
Automatic generation for the default data/commands.json path refuses existing
symlink components before creating temporary files or replacing the command
tree.
Validation output includes the number of macro commands checked and skipped.
When the validator can identify an error position, it prints a caret under the
generated command text. If .cobble/source_map.json is present, diagnostics also
include the originating Cobble source location when available.
cobble inspect
Summarize Cobble metadata in a generated data pack directory.
cobble inspect <DATAPACK_DIR> [OPTIONS]
Arguments:
DATAPACK_DIR- Generated data pack directory containing.cobble/build_manifest.json
Options:
--json- Print a machine-readable report withschema_version,ok,status,manifest, andsource_map_entries
Examples:
cobble build --validate -o output
cobble inspect output
cobble inspect output --json
The command reads .cobble/build_manifest.json and, when present,
.cobble/source_map.json. ZIP archives created by cobble build --zip include
pack files (pack.mcmeta, data/**, and resource-pack assets/** when
present), so inspect a generated directory before or alongside ZIP packaging.
JSON output is written to stdout and keeps schema_version: 1 for the
inspection contract.
cobble link
Configure a local Minecraft datapacks/ target for watch workflows.
cobble link [PROJECT_PATH] [OPTIONS]
Arguments:
PROJECT_PATH- Project directory or file used to findcobble.toml(default: current directory)
Options:
--datapacks <DIR>- Direct path to a worlddatapacks/directory--world <DIR>- Path to a world directory; Cobble uses<DIR>/datapacks--minecraft <DIR>- Path to.minecraft; Cobble uses<DIR>/saves/<pack-name>/datapacks--pack-name <NAME>- Directory name for the linked data pack (default: project namespace)--dry-run- Show the resolved target without writing link state--status- Show saved link state and whether generated Cobble metadata is present at the linked pack path--clear- Remove saved link state without deleting the linked data pack
Examples:
cobble link --datapacks ~/minecraft/saves/TestWorld/datapacks
cobble link --world ~/minecraft/saves/TestWorld
cobble link --status
cobble link --clear
link writes project-local state to .cobble/link_state.json. The base
directory given to --world or --minecraft must already exist, so a
mistyped path fails with an error; Cobble creates only the datapacks/ (and
saves/<pack-name>/) subdirectories automatically. A direct --datapacks
target directory is created when configuring a real link. Link configuration
does not delete or replace existing data packs. Link state reads, writes, and
clears refuse symlink components in the project-local .cobble/link_state.json
path.
link --status, doctor --json, watch --link, and clean --linked all
reject a saved pack_path that is not under the saved target_path or would
traverse an existing target symlink. Use cobble watch --link to build into
the configured pack path. If that pack path already exists, watch --link
requires a valid .cobble/build_manifest.json, pack.mcmeta, and data/; the
manifest namespace and project_id must match the current project. Copied,
stale, or namespace-only forged markers are refused
until the path is moved aside or rebuilt by the owning Cobble project.
link --status includes recovery hints for the common cases: configure a
missing link with cobble link --datapacks <DIR>, clear and recreate tampered
link state with cobble link --clear, create a missing linked pack with
cobble watch --link, or move aside an unrelated pack before rebuilding.
cobble clean
Remove Cobble-generated output after safety checks.
cobble clean [PROJECT_PATH] [OPTIONS]
Arguments:
PROJECT_PATH- Project directory or file used to findcobble.toml(default: current directory)
Options:
-o, --output <DIR>- Output directory to clean. When omitted, Cobble usesbuild.outputfromcobble.toml.--dry-run- Print what would be removed without deleting files--linked- Clean the pack path configured bycobble link--yes- Confirm destructive linked cleanup
Examples:
cobble clean --dry-run
cobble clean --output output
cobble clean --linked --dry-run
cobble clean --linked --yes
clean only removes directories that look like Cobble-generated data pack
output. The target must be a directory and contain .cobble/build_manifest.json,
pack.mcmeta, and data/. Cobble refuses symlink outputs, existing symlink
parent components, symlink descendants, non-directories, unmarked directories,
and a configured output that resolves to the project root.
When a cobble.toml is in scope, clean — including clean --output <DIR> —
also requires the target’s build-manifest project_id to match the current
project, so one project cannot clean another project’s output; the namespace
check applies to linked cleanup.
Linked cleanup also requires the saved link state to resolve under the saved
datapacks/ target and the linked pack manifest namespace and project_id to
match the current project. Linked cleanup requires --yes for real deletion;
--dry-run does not require confirmation.
clean --dry-run prints the marker path that made the output eligible for
cleanup, the marker namespace, the marker project_id when present, the
required data pack files that were checked, and a symlink safety summary. For
linked output, the dry run ends with the exact confirmation shape:
cobble clean --linked --yes.
cobble doctor
Report project and validation environment status without contacting the network.
cobble doctor [PROJECT_PATH] [OPTIONS]
Arguments:
PROJECT_PATH- Project directory or file to inspect (default: current directory)
Options:
--commands-json <PATH>- Command tree path to inspect (default:data/commands.json)--json- Print a machine-readable report withschema_version, top-levelstatus, Cobble target metadata, config status, experimental output status, command tree status, experimental link status, and tool checks
Examples:
cobble doctor
cobble doctor examples/lobby --commands-json data/commands.json
cobble doctor --json
The report includes the Cobble version, Minecraft target, pack format, Java and
curl availability, cobble.toml status, configured output status, and the
default command tree SHA-1 match when data/commands.json exists.
JSON output is written to stdout. doctor --json never downloads validation
data; missing tools, missing config, and missing command trees are reported as
status fields so CI and editor integrations can decide how strict to be.
Configured output status is reported under experimental_output; it includes
the resolved output path, whether the directory exists, and whether it has a
Cobble build marker for the current project namespace and project identity. Link
status is reported under experimental_link; it includes saved link state,
whether the linked pack currently has Cobble build metadata, and whether that
metadata matches the project namespace and project_id.
cobble migrate
Report an experimental migration plan for moving Cobble projects between release tracks. The experimental path inspects projects moving from 0.8, 0.9, or 0.10 to 0.11 (and from 0.8 or 0.9 to 0.10), with a narrow config-only apply mode.
cobble migrate [PATH] [OPTIONS]
Arguments:
PATH- Project path or source file to inspect (default: current directory)
Options:
--from <VERSION>- Cobble version to migrate from:0.8,0.9, or0.10(default:0.10)--to <VERSION>- Cobble version to migrate to:0.10or0.11(default:0.11)--json- Print a machine-readable report withschema_version,ok,changed,from,to,apply,project_path,config,source,diagnostics, andactions--apply- Permit migration rewrites when supported by a migration action
Examples:
cobble migrate
cobble migrate path/to/project
cobble migrate --json
cobble migrate --from 0.9 --to 0.11 --apply
migrate is experimental. Dry-run/report mode is the default, and
no files are changed unless --apply is supplied. --apply only
updates supported cobble.toml settings such as project.pack_format; it does
not rewrite source files or automatically enable experimental feature flags.
Before writing a migrated config, Cobble creates a timestamped backup next to
cobble.toml and reports the backup path in text and JSON output.
JSON output is written to stdout. For the supported experimental route, the
report inspects cobble.toml when present, scans .cbl and .cobble files
under configured [build] source or src, and reports deterministic planned
actions for config inspection, pack-format updates, source scanning, stdlib
notes, resource-pack and Python compatibility experimental config notes, manual
step reporting, and config-only apply. Reports also include config before/after
change summaries and source-location details for migration-sensitive constructs
such as resource_pack.*, legacy import stdlib, and unsupported Python-like
syntax. Routes that end at 0.11 also list every raw command and execute header
that names the temp objective (temp-objective-reference), with the total in
source.temp_objective_references. Unsupported routes skip inspection, leave
changed as false, and exit non-zero.
See Upgrading for the steps for each release.
Project Configuration
cobble init writes a cobble.toml at the project root:
[project]
name = "my_datapack"
description = "my_datapack Data Pack"
namespace = "my_datapack"
version = "1.0.0"
pack_format = "121.0"
[build]
source = "src"
output = "output"
entry_points = []
[stdlib]
version = 2
[experimental]
resource_pack = false
plugins = false
python_compat = false
| Key | Meaning |
|---|---|
project.name | Project name. |
project.description | Pack description written to pack.mcmeta. |
project.namespace | Namespace for functions, resources, the <namespace>.vars objective, and the <namespace>:global storage. Lowercase, no spaces. |
project.version | Your project’s version; Cobble does not use it. |
project.pack_format | Must be 121.0 (or 121). |
build.source | Source directory. Default src. |
build.output | Output directory. Default output. |
build.entry_points | Files to compile, relative to build.source. Imports are resolved from these files. Empty means every file in the source directory. |
stdlib.version | 2 (default) requires per-module stdlib imports; 1 is deprecated. See Standard Library Modules. |
experimental.resource_pack | Enables resource-pack output. |
experimental.plugins | Enables the plugin diagnostics in cobble check. See Experimental Features. |
experimental.python_compat | Adds the Python compatibility report to cobble check. |
Unknown keys, misplaced keys, and experimental flags that are not booleans are
errors. Command-line options such as --namespace and --output override the
file for one build.
Supported Minecraft Version
| Minecraft | Pack format |
|---|---|
| Java Edition 26.3 | 121.0 |
Cobble targets one Minecraft version at a time and rejects other pack formats.
pack.mcmeta gets min_format and max_format set to [121, 0].
Output Layout
output/
├── pack.mcmeta
├── .cobble/
│ ├── build_manifest.json
│ └── source_map.json
└── data/
├── minecraft/
│ └── tags/
│ └── function/
│ ├── load.json
│ └── tick.json
└── my_datapack/
└── function/
├── _cobble_init.mcfunction
├── on_load.mcfunction
└── ...
Resource declarations add folders such as data/<namespace>/predicate/, and
resource-pack output adds assets/. The .cobble/ files are described in
Metadata Files.
Diagnostics
cobble check, cobble build, and the browser compiler run the same
source-aware checks before compiling. They report:
- unsupported Python syntax;
- a missing
:after a block header, unexpected or inconsistent indentation; - unclosed delimiters and strings;
- duplicate functions and parameters, including across imported files or a compiled directory;
- expressions that produce no command;
- missing and circular imports, and diagnostics inside imported files;
- non-
stdlibimports in the single-file browser compiler; - undefined variables, functions, and stdlib modules;
- undefined, invalid, or unclosed
{name}placeholders in raw commands; - type mismatches;
- malformed
datapack.*arguments, resource IDs, and tag entries.
Compilation then adds its own errors, such as loop expansion limits and JSON resource formats.
Output Format
Each diagnostic names its kind, points at the source, and adds a hint when there is one:
Error: missing-block-colon: Block headers must end with `:`
╭─[ main.cbl:1:8 ]
│
1 │ def f()
│ ┬
│ ╰── Block headers must end with `:`
│
│ Note: Add `:` at the end of the line before the indented block.
───╯
cobble check --json writes the same diagnostics to stdout as JSON with
file, line, column, severity, kind, message, help, and a
formatted text version. The browser compiler shows the same fields.
Kinds Listed Elsewhere
- Loop kinds such as
unroll-limit-exceeded: For Loops. missing-stdlib-module: Standard Library Modules.datapack-resource-format: Data Pack Resources.resource-pack-*kinds: Resource Packs.temp-objective-referenceincobble migrate: Upgrading.
Cobble Metadata Files
Cobble writes generated-pack metadata under .cobble/ in every non-dry-run
build output. These files are intended for inspection, validation diagnostics,
and future editor tooling. Cobble also writes project-local workflow metadata
under the project’s .cobble/ directory for commands such as link.
Each file’s version field is a per-file schema version that changes
independently of the Cobble package version.
.cobble/build_manifest.json
The build manifest summarizes the build inputs, target version, generated outputs, generated resources, and optional validation result.
Stable top-level fields for schema version 1:
| Field | Type | Meaning |
|---|---|---|
version | number | Build manifest schema version. Currently 1. |
cobble_version | string | Cobble package version that produced the build. |
minecraft_version | string | Supported Minecraft Java Edition target. |
pack_format | number or object | Serialized pack-format value. |
pack_format_text | string | Human-readable pack-format value such as 121.0. |
namespace | string | Generated data pack namespace. |
description | string | Pack description written to pack.mcmeta. |
project_root | string | Canonical project/source root used for ownership diagnostics. |
project_id | string | Stable SHA-1 identity derived from project_root. |
generated_at_unix_epoch_ms | number | Generation timestamp in Unix epoch milliseconds. |
input | object or null | Source input summary. |
generated_namespaces | array | Namespaces generated in data/. |
generated | object | Generated output counts. |
resources | array | Generated JSON resource entries. |
validation | object or null | Validation summary when validation ran. |
0.8.0 Additive Fields
The following optional top-level fields are added in 0.8.0. They are omitted when not applicable and do not require a schema version bump.
| Field | Type | Meaning |
|---|---|---|
stdlib_version | number | Stdlib version declared in cobble.toml. 1 or 2. Omitted when no stdlib helpers are used. |
active_stdlib_modules | array | Stdlib modules activated by imports. Empty when import stdlib is used (all modules active). |
unrolled_loops | number | Number of for loops expanded at compile time. Omitted when zero. |
0.9.0 Additive Top-Level Fields
The following optional top-level fields are added in 0.9.0. They are omitted when not applicable and do not require a schema version bump.
| Field | Type | Meaning |
|---|---|---|
experimental_features | array | Experimental features enabled for this build, e.g. ["resource_pack"]. Omitted when empty. |
Native CLI builds set project_root to the canonical project/source root and
project_id to the SHA-1 identity derived from that path. Browser/WASM
compilation has no local project root, so its manifest leaves project_root and
project_id empty. Browser/WASM output also uses 0 for
generated_at_unix_epoch_ms; native CLI builds use the current system time.
Only local CLI link, watch, doctor, and clean workflows use project_id for
ownership checks.
input fields:
| Field | Type | Meaning |
|---|---|---|
source | string | Source file or source directory. |
entry_points | array | Configured entry points used for the build. |
compiled_files | array | Cobble files compiled as entry points. |
generated fields:
| Field | Meaning |
|---|---|
functions | Number of generated .mcfunction files. |
commands | Number of generated command lines. |
source_map_entries | Number of source-map entries written. |
function_tags | Total generated function tag JSON files. |
stdlib_function_tags | Function tags generated by stdlib event registration. |
custom_function_tags | Function tags generated by user declarations. |
json_function_tags | Function tags declared through datapack.function_tag. |
advancements | Generated advancement JSON count. |
loot_tables | Generated loot table JSON count. |
recipes | Generated recipe JSON count. |
predicates | Generated predicate JSON count. |
item_modifiers | Generated item modifier JSON count. |
json_resources | Generic datapack.* JSON resources. |
total_json_resources | Legacy typed resources plus generic JSON resources. |
0.9.0 Additive generated Fields
| Field | Meaning |
|---|---|
resource_pack_models | Generated resource-pack model JSON count. Omitted when zero. |
resource_pack_langs | Generated resource-pack lang JSON count. Omitted when zero. |
resource_pack_static_assets | Static resource-pack assets copied from project assets/. Omitted when zero. |
Each resources entry has:
| Field | Type | Meaning |
|---|---|---|
kind | string | Resource kind such as function_tag, predicate, resource_pack_model, resource_pack_lang, or resource_pack_static_asset. |
namespace | string | Resource namespace. |
path | string | Resource path without namespace or file extension. |
resource_pack_model, resource_pack_lang, and
resource_pack_static_asset are emitted under the resource-pack opt-in. See
Resource Packs.
When validation runs, validation has:
| Field | Type | Meaning |
|---|---|---|
enabled | boolean | Always true when the object is present. |
commands_json | string | Command-tree file used for validation. |
files_checked | number | .mcfunction files checked. |
commands_checked | number | Regular commands checked. |
macro_commands_checked | number | Macro commands checked. |
commands_skipped | number | Commands skipped by validator rules. |
errors | number | Command validation errors. |
source_map_errors | number | Source-map consistency errors. |
.cobble/source_map.json
The source map links generated command lines and generated JSON resource files back to Cobble source locations when available. It is written when generated metadata exists.
Stable top-level fields for schema version 1:
| Field | Type | Meaning |
|---|---|---|
version | number | Source-map schema version. Currently 1. |
entries | array | Generated file mappings. |
Each entry has:
| Field | Type | Meaning |
|---|---|---|
generated_path | string | Generated file path relative to the data pack root. |
generated_line | number | 1-based line number in the generated file. |
command | string | Generated command text without a leading slash, or the mapped generated JSON line text. |
source | object or null | Cobble source location when known. |
kind | string | Command origin: UserCommand, StdLib, RuntimeSetup, ControlFlow, JsonGenerated, or Unrolled. |
JsonGenerated entries are added in 0.9.0 for generated data-pack and
resource-pack JSON files. Static resource-pack passthrough assets are listed in
the build manifest, but they are not source-mapped because Cobble copies them
without generating their contents.
Unrolled is added in 0.8.0 for commands generated by compile-time
unrolling of literal for loops. See
For Loops.
source has:
| Field | Type | Meaning |
|---|---|---|
file | string | Source path, usually relative to the project/source root. |
line | number | 1-based Cobble source line. |
column | number | 1-based Cobble source column. |
Experimental Doctor Output Status
doctor --json reports configured build output status under
experimental_output. This field is not part of the stable metadata contract
yet; it lets CI and editor tooling distinguish an unbuilt project from an
existing Cobble-generated output directory without running a build.
The report includes the resolved configured output path, whether that path
exists, and marker information for .cobble/build_manifest.json. A missing
configured output is reported as not_present and does not make the top-level
doctor status a warning. Existing output with no Cobble marker, a stale marker,
a mismatched namespace, or a mismatched project_id is reported as warning.
Symlink traversal, symlink descendants, non-directory output paths, or
unreadable marker state are reported as error.
Project-local .cobble/link_state.json
cobble link writes this file in the project directory. It records the
explicit local target used by watch --link, clean --linked, and
doctor --json.
Stable fields for schema version 1:
| Field | Type | Meaning |
|---|---|---|
version | number | Link-state schema version. Currently 1. |
target_kind | string | One of datapacks, world, or minecraft. |
target_path | string | Resolved target datapacks/ directory. |
pack_name | string | Directory name of the linked data pack. |
pack_path | string | Final linked data pack directory. |
doctor --json reports link state under experimental_link. That field is not
part of the stable metadata contract yet; it is labeled experimental so editor
and CI tooling can try it before 1.0 schema commitments.
Commands that consume link state validate that pack_path remains under
target_path, still ends with pack_name, and does not rely on an existing
target symlink. Link state reads, writes, and clears also refuse symlink
components in the project-local .cobble/link_state.json path. Tampered link
state is reported as an error by doctor --json, shown as invalid by
link --status, and refused by watch --link and clean --linked.
The linked pack is considered Cobble-owned only when its output directory
contains a valid .cobble/build_manifest.json alongside normal data pack files
such as pack.mcmeta and data/. For linked workflows, the manifest
namespace must match the current project namespace and project_id must match
the current project root identity. Copied, stale, or namespace-only forged
manifests are reported as invalid and are not cleaned or replaced by
watch --link.
Compatibility
- Schema version
1fields above are treated as stable for 0.x tooling. - New optional fields may be added without a schema version bump.
- Removing or renaming a stable field requires a schema version bump and a changelog note.
- Tests assert the object shapes for these stable fields to catch accidental field removal.
Experimental Features
These features are off by default and may change between releases. Each one
is enabled with a command-line flag or a key under [experimental] in
cobble.toml.
| Feature | Flag | cobble.toml |
|---|---|---|
| Resource-pack output | --experimental-resource-pack | resource_pack = true |
| Plugin diagnostics | cobble check --experimental-plugins | plugins = true |
| Python compatibility report | cobble check --experimental-python-compat | python_compat = true |
| Editor symbols | cobble check --json --symbols |
Resource-pack output has its own page: Resource Packs.
Plugin Diagnostics
Plugins are lint manifests in plugins/*.toml. Cobble reads them and runs the
built-in rules they list. It never loads or runs code from a plugin. Start from
the template:
cobble init --template plugin-diagnostics --name linted-pack
cd linted-pack
cobble check --experimental-plugins
plugin_version = 1
name = "example_lints"
kind = "diagnostics"
description = "Example diagnostics-only lint manifest"
minimum_cobble_version = "0.10.0"
diagnostic_rules = [
"example_lints.no_tellraw",
"example_lints.no_raw_op",
"example_lints.no_gamemode_creative",
"example_lints.max_raw_command_length",
]
[capabilities]
read_project_metadata = true
read_source_text = true
emit_diagnostics = true
Rules only run when the manifest grants both read_source_text and
emit_diagnostics. Unknown manifest fields and capabilities are errors. With
--json, findings appear under experimental_plugins.diagnostics[] with
kind = "experimental-plugin-diagnostic".
Python Compatibility Report
cobble check --experimental-python-compat adds a report of the Python-like
constructs Cobble supports, the ones your sources use, and the unsupported ones
it found. Unsupported constructs stay errors; an entry includes a
suggested_cobble_alternative when there is a direct rewrite. With --json,
the report is the experimental_python_compat object. The browser compiler
has the same report behind its Python compatibility toggle.
Editor Symbols
cobble check --json --symbols adds an experimental_symbols array of
document symbols for editor integrations. The rest of the JSON report keeps its
stable schema_version.
Upgrading
Each section covers one release step. To skip a release, follow the sections
in order; cobble migrate --from <old> --to <new> reports the combined
changes. The changelog
lists every change.
0.10 To 0.11
0.11 is still in development. This section lists the changes on main so far
and will grow before the release.
Cobble 0.11 keeps the Minecraft Java Edition 26.3 target and pack format
121.0, so cobble.toml and the command tree stay as they are. The changes are
in generated output: each pack keeps its variables on its own objective, and
module variables can opt into keeping their values across loads.
Recommended Flow
- Review the project with the migration report (0.10 to 0.11 is the default route):
cobble migrate --json
The report lists every raw command and execute header that names the
temp objective (temp-objective-reference). These lines keep working;
the list shows where your pack reads or writes Cobble variables, which helps
with step 3.
- Rebuild and re-run project checks:
cobble check
cobble build --validate
- Update anything outside the pack that reads Cobble variables by objective
name, such as command blocks, other data packs, plugins, or commands typed
in chat.
/scoreboard players get round tempbecomes/scoreboard players get round <namespace>.vars.
Per-Pack Variable Objective
0.10 kept every pack’s numeric variables and internal helper scores on one
shared temp objective, so two Cobble packs on one server that used the same
variable name overwrote each other. The math and __internal__ objectives
were shared the same way.
In 0.11 these scores live on <namespace>.vars, for example parkour.vars
for the namespace parkour. The load function creates it with
scoreboard objectives add <namespace>.vars dummy. Strings, lists, and maps
already lived in the per-namespace storage <namespace>:global and are
unchanged.
temp stays the name Cobble source uses for this objective. The compiler
rewrites it to <namespace>.vars in scoreboard arguments, if score,
unless score, store result score, scores={...} selector filters, and
score text components. Other text, such as a chat message containing the word
temp, is left alone. See
The temp Objective In Raw Commands.
Values already stored under temp in an existing world are not carried over.
Module variables reset on every load in 0.10 as well, so the new objective
starts from the same values. Once no other pack uses it, remove the old
objective with /scoreboard objectives remove temp; do the same for math and
__internal__ if no other pack uses them.
Persistent Module Variables
Module variables still reset on every load. Declare one with persist to keep
its value across /reload and server restarts:
persist best_time = 0
persist champion = "nobody"
The initializer only runs while the score or storage value is unset. persist
is only allowed at module level and takes a constant initializer. Packs that
kept records in a hand-written objective to survive reloads can switch to
persist for pack-wide values; per-player records still need their own
objective.
persist is not a reserved word: a variable named persist keeps working.
Tooling Changes
cobble migratedefaults to--from 0.10 --to 0.11. Routes from 0.8 and 0.9 to 0.11 are also supported; pass--to 0.10for the previous route.- The serde AST adds
Assignment.persist, omitted from JSON whenfalse.
0.9 To 0.10
Cobble 0.10.0 targets Minecraft Java Edition 26.3 and requires pack format
121.0 (121 names the same format). Like 0.9, the migration path is
report-first: normal build, check, and watch commands never rewrite project
files.
Recommended Flow
- Review the project with the migration report (0.9 to 0.10 is the default
route; use
--from 0.8for older projects):
cobble migrate --json
- Update
cobble.tomlonly after reviewing the report:
cobble migrate --apply
- Replace the 26.1.2 command tree. The default
data/commands.jsonis fingerprint-checked against the 26.3 tree, so an old file is refused; delete it and let--validateregenerate it, or generate it explicitly:
rm data/commands.json
scripts/setup_commands_json.sh 26.3
- Re-run project checks and a validated build:
cobble check
cobble build --validate
--apply updates [project].pack_format to 121.0 and writes a timestamped
backup of cobble.toml first. It does not rewrite source files.
Minecraft 26.2 And 26.3 Changes To Review
Cobble emits datapack.* JSON and raw commands as written, so sources written
for 26.1.2 may need these updates.
JSON resources (Cobble reports these as datapack-resource-format errors,
because a 26.3 server whose packs fail to load refuses to start):
- Predicates:
conditionis nowtype. - Item modifiers:
functionis nowtype;conditionslists are a singlecondition(combine several withminecraft:all_of). - Loot tables:
functionsis nowmodifier,conditionsis a singlecondition, and aminecraft:tagentry’snameis nowitems("#minecraft:logs"). - Root advancements with
displayneeddisplay.background. - Cooking recipes need
cookingtime(it no longer defaults to 200).
Other resource changes Cobble does not check:
- Entity predicates (26.2):
typeis nowminecraft:entity_type, unknown keys are rejected, andtype_specificsub-predicates moved to top-level keys such asminecraft:type_specific/player. - Advancement trigger fields were renamed (for example
recipetorecipesandblocktoblocks), and predicate lists are no longerall_ofshorthand. - Number providers need an explicit
type, and several were renamed (sumtoadd,producttomul). - Block state SNBT:
Name/Propertiesare nowid/properties.
Raw commands:
- Slot arguments of
itemandexecute if itemsare slot sources; existing slot strings such ascontainer.0andweapon.mainhandkeep working. - Team and waypoint colors must be lowercase names such as
dark_purple;resetis its own literal. schedule cleartakes a namespaced function ID.- Sign click events and commands only run when the sign sets
allow_op_features:true. giveover the item limit,tick step, and agameruleset to its current value now fail, which changes whatexecute store successrecords.
Text components in item components and entity NBT are SNBT (formerly
stringified JSON), so custom_name='{"text":"Sword"}' becomes
custom_name={"text":"Sword"}. The enchantments component is a plain map
(enchantments={"minecraft:sharpness":5}). Both changed in 1.21.5; the 0.10
examples were updated.
Cobble Behavior Changes
See the 0.10.0 changelog for the full list. The ones most likely to change a working pack:
- Execute blocks with more than one command run their body in a helper function once per entity, so conditions are no longer re-checked between body commands.
- Expressions folded into scores use Minecraft’s integer division and floor
modulus (
-7 / 2is-4). not (a and b)now compiles with De Morgan’s laws.- Recursive function calls are rejected at compile time.
- Plain
cobble buildandcobble clean --outputrefuse non-empty output directories that do not carry this project’s ownership marker. --validaterejects commands above the function permission level (2 by default; setCOBBLE_FUNCTION_PERMISSION_LEVELfor a server that raised it) and macro lines without$(...)variables.- Merged function tags are sorted, which can reorder
minecraft:loadandminecraft:tickhandlers.
Library users: the serde AST gained Assignment.target_expr, the
ExecuteModifier::If/Unless(Expression) variants and the CobbleError enum
were removed, and transpiler functions return a typed TranspileError.
0.8 To 0.9
Cobble 0.9.0 targets Minecraft Java Edition 26.1.2 and requires pack format
101.1. The migration path is report-first: normal build, check,
and watch commands never rewrite project files.
Recommended Flow
- Review the project with the migration report:
cobble migrate --from 0.8 --to 0.9 --json
- Update
cobble.tomlonly after reviewing the report:
cobble migrate --from 0.8 --to 0.9 --apply
- Re-run project checks and a validated build:
cobble check --json --experimental-python-compat
cobble build --validate
In 0.9.0, --apply only updates supported config settings such as
[project].pack_format to 101.1; it does not rewrite source files or enable
experimental features. Before changing
cobble.toml, Cobble writes a timestamped backup next to the original file and
reports that path as config.backup_path.
The JSON report is designed for CI review. It includes:
config.changes[]with before/after values for supported config updates.actions[]entries with before/after values for pack-format changes.source.file_details[]with per-file counts and manual-review locations.suggested_cobble_alternativehints for unsupported Python-like constructs.
Items To Review Manually
pack_format: update old values such as81to101.1.resource_pack.*: keep[experimental] resource_pack = trueor pass--experimental-resource-packwhen using resource-pack helpers.- Python-like source: use
cobble check --experimental-python-compatto see the supported Python-inspired surface and unsupported constructs that remain errors. - Plugins:
plugins/*.tomlmanifests are experimental and diagnostics-only in 0.9.0. Cobble can evaluate built-in declarative rules, but it does not run project plugin code.
Testing And Releases
This page lists the checks to run when changing Cobble itself.
Test Suite
cargo fmt --check
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
Most runtime-behavior tests compile a pack and run it in the command simulator
in tests/common/mcsim.rs, which executes the generated functions and checks
scores and storage.
Generated pack snapshots live in tests/snapshots/. After an intended change
to generated output, update and re-run them:
INSTA_UPDATE=always cargo test --locked --test generated_snapshots_test
cargo test --locked --test generated_snapshots_test
Review the changed .snap files before committing, and check that local
paths and package versions are redacted.
Release Gate
The release gate runs everything below except the live command-tree check and the post-release smoke:
scripts/qa_10_release_gate.sh
It runs the Rust checks, the example and fixture checks, the focused QA
scripts, validated builds of the release fixtures, doctor/inspect/
migrate JSON checks, the web gate, the server gate when the EULA variable is
set, and cargo package and cargo publish --dry-run. Run it on a clean
worktree for a release. COBBLE_QA_ALLOW_DIRTY=1 lets it run against
uncommitted changes for a rehearsal.
examples/ holds the user-facing example packs. The release fixtures that the
gates build and snapshot live in tests/fixtures/projects/.
scripts/check_examples.sh checks each example and fixture on its own;
cobble check examples would treat the whole folder as one project and report
duplicate names such as on_load.
Focused Scripts
Each script creates temporary projects under /tmp and deletes them on
success. Set COBBLE_QA_KEEP=1 to keep them.
| Script | Covers |
|---|---|
qa_07_templates.sh | Every cobble init template through fmt --check, check, build --validate, inspect, and cleanup. |
qa_07_link_clean_safety.sh | link, doctor --json, watch --link, and clean --linked against unmarked, tampered, forged, and symlinked outputs. |
qa_07_watch_smoke.sh | A bounded watch --link --validate run: a failed rebuild keeps the last pack, a later edit recovers, output noise does not trigger a rebuild. |
qa_security_regressions.sh | Output path safety, link and clean ownership, execute guard translation, and unroll limits. Run after touching any of these. |
qa_08_stdlib_v2.sh | Per-module stdlib imports, missing-stdlib-module, the version = 1 warning, and the manifest stdlib fields. |
qa_08_resource_authoring.sh | Tag merging, dedup, and ordering, schema diagnostics, and path suggestions. |
qa_08_unrolling.sh | Literal loop unrolling, its limits and diagnostics, and the Unrolled source-map kind. |
qa_08_resource_pack.sh | The resource-pack opt-in, generated assets, ZIP contents, and manifest counts. |
check_resource_snapshots.sh | Regenerates the resource fixture snapshots and fails on unintended diffs. |
check_resource_schemas.sh | Validates generated tag JSON against the tag schema. |
Web Gate
Run this when web/, the WASM wrapper, or the documentation changed:
cd web
npm run test:web
It runs the WASM tests and cargo check, the ZIP helper test, the metadata
check and typecheck, the GitHub Pages build, the Playwright tests, and the link
check. Install Chromium once with npx playwright install chromium. The build
includes this documentation, so it also needs mdBook (see
Documentation).
web/lib/compilerMetadata.ts is generated from Cargo.toml and
src/pack_format.rs. After a version or target change, regenerate it with
npm --prefix web run sync:metadata; the web gate fails when it is stale.
Documentation
This documentation is an mdBook book.
docs/book.toml configures it, docs/src/SUMMARY.md lists the pages, and
docs/theme/cobble.css holds the theme overrides. The Pages workflow builds it
with mdBook 0.5.4. Install the same version with Cargo, or download a binary
from the mdBook releases:
cargo install mdbook --version 0.5.4 --locked
mdbook serve previews the book at http://localhost:3000 and rebuilds it on
every save:
mdbook serve docs
npm run build in web/ runs mdbook build and writes the book to
web/public/docs/, so the site serves it under /docs/. The link check in the
web gate covers every page in docs/src/, including links to headings.
Command Tree Check
--validate needs data/commands.json, which Cobble generates by downloading
the Minecraft server jar and running java --reports. Tests that need the tree
skip when data/commands.json is missing, so cargo test also runs on a fresh
clone without Java. CI also sets COBBLE_COMMANDS_JSON_AUTO_DOWNLOAD=0, so a
--validate run without the tree fails instead of downloading the jar. Before a
release, check the live download path from an empty directory:
REPO=/path/to/cobble
mkdir -p /tmp/cobble-command-tree-e2e && cd /tmp/cobble-command-tree-e2e
cargo run --locked --manifest-path "$REPO/Cargo.toml" -- build "$REPO/tests/fixtures/projects/inventory.cbl" --validate --output output
cargo run --locked --manifest-path "$REPO/Cargo.toml" -- doctor --commands-json data/commands.json
Validation should pass, and doctor should report a target match.
Server Gate
The server gate runs a Purpur 26.3 server. It needs Java, network or cache access, and EULA acceptance:
COBBLE_MINECRAFT_EULA_ACCEPTED=1 scripts/test_minecraft_server.sh
It loads a smoke pack and the showcase fixture and runs
/function cobble_showcase:selftest, which prints PASS <check> or
FAIL <check> expected <x> got <y> for each check. The gate fails on any
FAIL line or when the pass count does not match. It also loads two packs
built from the same source under different namespaces and checks that their
scores stay separate, and that after /reload a module variable resets while
a persist variable keeps its value.
If a release skips this gate, say so in the release notes.
After A Release
After publishing to crates.io, creating the GitHub release, and letting GitHub Pages deploy:
scripts/qa_post_release_smoke.sh
It installs the released crate into a temporary Cargo root, builds and
validates a smoke pack with it, checks that the GitHub release is published,
and checks that the website, /try/, the WASM file, and the docs are served.
COBBLE_POST_RELEASE_VERSION, COBBLE_POST_RELEASE_SITE_URL,
COBBLE_QA_SKIP_GITHUB=1, and COBBLE_QA_SKIP_WEB=1 override its defaults.
CI
GitHub Actions runs cargo fmt --check, cargo test, cargo clippy,
cargo package, and cargo publish --dry-run on every push and pull request,
plus the markdown link check. The Pages workflow runs the web gate on pull
requests that touch the compiler, the web app, or the docs, and deploys on
pushes to main.