Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

You can also try Cobble without installing anything in the browser compiler.

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 curl for command validation (cobble build --validate). The first validated build downloads the Minecraft server jar and extracts its command tree into data/commands.json. Builds without --validate need 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 under datapacks/ and contains pack.mcmeta, and that the world runs Minecraft 26.3.
  • A function is not found. Run /reload after building, and call it with the project namespace, such as my_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>.vars objective: /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:

ExampleWhat it is
lobbyServer 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.
parkourA 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_itemsThree craftable right-click items with cooldowns: a Blink Rod, a Healing Staff, and a Frost Wand.
hello_world.cblThe 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) iterates 0..n.
  • range(start, stop, step) uses Python-style half-open bounds.
  • for i in range(n) by step: remains supported for compatibility.
  • Negative by over range(n) starts at n - 1 and counts down.
  • Runtime range(...) supports dynamic count/start/stop/step expressions.
  • A statically known step of 0 is rejected. If a dynamic step expression evaluates to 0 at 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 by steps support positive, negative, and dynamic integer expressions. A statically known step of 0 is rejected; a dynamic step that evaluates to 0 executes 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:

KindSeverityReported when
unroll-limit-exceedederrora loop expands past 1024 iterations, nested loops past 65,536 iterations, or one outer loop past 65,536 commands
unroll-large-expansionwarninga loop expands past 256 iterations
unroll-non-literalerrorthe iterable is none of: a literal array, a literal or runtime range, a runtime literal array, or a storage-backed list
unroll-bad-steperrora range step is zero or non-integer
unroll-bad-rangeerrora range stops below its start with a positive step, or the array is empty
runtime-for-unsupportederrora runtime range has a non-integer bound or step, or a runtime list loop iterates something that is not a list
runtime-for-bad-steperrora 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:

  1. ^ (power), right to left
  2. *, /, %, left to right
  3. +, -, left to right
  4. ==, !=, <, <=, >, >=

How Expressions Compile

  • Constant operands go through helper fake players such as #multiplier, #divisor, and #modulus, because scoreboard players operation only takes scores.
  • Intermediate results use fake players such as #expr_temp_<n>.
  • A power with a constant exponent is expanded at compile time: x ^ 3 becomes x * 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 += y compiles as x = 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.LOAD runs when the pack loads: on /reload and on server start.
  • event.TICK runs 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, and nonlocal;
  • 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 as config[key].
  • Functions return integers only. Use storage or a module variable for other results.
  • A bare expression such as score + 1 produces no command and is rejected. Use an assignment, a call, a raw command, or pass.
  • for loops iterate range(...), literal arrays, and storage-backed lists.
  • All of a pack’s variables share one scoreboard objective, so there is no local scope; see The global Keyword.
  • while loops and runtime for loops 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
ImportEffect
from stdlib import textActivates the text module.
from stdlib import text, scoreActivates text and score.
from stdlib import score.objectiveActivates score.objective, which from stdlib import score leaves inactive.
import stdlibActivates 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 (formerly condition), including inside all_of, any_of, and inverted terms;
  • item modifiers use type (formerly function) and a single condition (formerly a conditions list);
  • loot tables use modifier (formerly functions) and a single condition (formerly conditions) on tables, pools, and entries, and a minecraft:tag entry names its tag in items ("#minecraft:logs");
  • a root advancement with display sets display.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:

KindReported when
resource-pack-not-enableda resource_pack.* helper is called without the opt-in
resource-pack-bad-ida resource ID or locale name is malformed
resource-pack-non-objecta model or lang payload is not a JSON object
resource-pack-duplicatetwo 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, or plugin-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 file
  • src/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 uses build.source and build.entry_points from cobble.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 be 121.0; 121 is 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 experimental resource_pack.* output and project assets/ passthrough
  • --validate - Validate generated .mcfunction files after building
  • --dry-run - Compile and print the build summary without writing final output
  • --commands-json <PATH> - Path to commands.json for 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 with schema_version, ok, source, files_checked, files, diagnostics, and error_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 uses build.source from cobble.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 experimental resource_pack.* output and project assets/ passthrough
  • --link - Build into the pack path configured by cobble link
  • --validate - Validate generated .mcfunction files after each successful build
  • --commands-json <PATH> - Path to commands.json for 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:

  1. Perform an initial build
  2. Watch for changes in .cbl, .cobble, and cobble.toml files
  3. Coalesce rapid editor save events into one rebuild
  4. Ignore generated output, .cobble/, staging directories, ZIP files, and common editor temporary files
  5. Reload valid cobble.toml changes and update the watched source directory
  6. Show timestamped build status and any errors
  7. 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 uses build.source from cobble.toml. Formatting a directory formats all .cbl and .cobble files 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 to commands.json generated 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 with schema_version, ok, status, manifest, and source_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.

Configure a local Minecraft datapacks/ target for watch workflows.

cobble link [PROJECT_PATH] [OPTIONS]

Arguments:

  • PROJECT_PATH - Project directory or file used to find cobble.toml (default: current directory)

Options:

  • --datapacks <DIR> - Direct path to a world datapacks/ 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 find cobble.toml (default: current directory)

Options:

  • -o, --output <DIR> - Output directory to clean. When omitted, Cobble uses build.output from cobble.toml.
  • --dry-run - Print what would be removed without deleting files
  • --linked - Clean the pack path configured by cobble 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 with schema_version, top-level status, 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, or 0.10 (default: 0.10)
  • --to <VERSION> - Cobble version to migrate to: 0.10 or 0.11 (default: 0.11)
  • --json - Print a machine-readable report with schema_version, ok, changed, from, to, apply, project_path, config, source, diagnostics, and actions
  • --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
KeyMeaning
project.nameProject name.
project.descriptionPack description written to pack.mcmeta.
project.namespaceNamespace for functions, resources, the <namespace>.vars objective, and the <namespace>:global storage. Lowercase, no spaces.
project.versionYour project’s version; Cobble does not use it.
project.pack_formatMust be 121.0 (or 121).
build.sourceSource directory. Default src.
build.outputOutput directory. Default output.
build.entry_pointsFiles to compile, relative to build.source. Imports are resolved from these files. Empty means every file in the source directory.
stdlib.version2 (default) requires per-module stdlib imports; 1 is deprecated. See Standard Library Modules.
experimental.resource_packEnables resource-pack output.
experimental.pluginsEnables the plugin diagnostics in cobble check. See Experimental Features.
experimental.python_compatAdds 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

MinecraftPack format
Java Edition 26.3121.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-stdlib imports 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

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:

FieldTypeMeaning
versionnumberBuild manifest schema version. Currently 1.
cobble_versionstringCobble package version that produced the build.
minecraft_versionstringSupported Minecraft Java Edition target.
pack_formatnumber or objectSerialized pack-format value.
pack_format_textstringHuman-readable pack-format value such as 121.0.
namespacestringGenerated data pack namespace.
descriptionstringPack description written to pack.mcmeta.
project_rootstringCanonical project/source root used for ownership diagnostics.
project_idstringStable SHA-1 identity derived from project_root.
generated_at_unix_epoch_msnumberGeneration timestamp in Unix epoch milliseconds.
inputobject or nullSource input summary.
generated_namespacesarrayNamespaces generated in data/.
generatedobjectGenerated output counts.
resourcesarrayGenerated JSON resource entries.
validationobject or nullValidation 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.

FieldTypeMeaning
stdlib_versionnumberStdlib version declared in cobble.toml. 1 or 2. Omitted when no stdlib helpers are used.
active_stdlib_modulesarrayStdlib modules activated by imports. Empty when import stdlib is used (all modules active).
unrolled_loopsnumberNumber 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.

FieldTypeMeaning
experimental_featuresarrayExperimental 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:

FieldTypeMeaning
sourcestringSource file or source directory.
entry_pointsarrayConfigured entry points used for the build.
compiled_filesarrayCobble files compiled as entry points.

generated fields:

FieldMeaning
functionsNumber of generated .mcfunction files.
commandsNumber of generated command lines.
source_map_entriesNumber of source-map entries written.
function_tagsTotal generated function tag JSON files.
stdlib_function_tagsFunction tags generated by stdlib event registration.
custom_function_tagsFunction tags generated by user declarations.
json_function_tagsFunction tags declared through datapack.function_tag.
advancementsGenerated advancement JSON count.
loot_tablesGenerated loot table JSON count.
recipesGenerated recipe JSON count.
predicatesGenerated predicate JSON count.
item_modifiersGenerated item modifier JSON count.
json_resourcesGeneric datapack.* JSON resources.
total_json_resourcesLegacy typed resources plus generic JSON resources.

0.9.0 Additive generated Fields

FieldMeaning
resource_pack_modelsGenerated resource-pack model JSON count. Omitted when zero.
resource_pack_langsGenerated resource-pack lang JSON count. Omitted when zero.
resource_pack_static_assetsStatic resource-pack assets copied from project assets/. Omitted when zero.

Each resources entry has:

FieldTypeMeaning
kindstringResource kind such as function_tag, predicate, resource_pack_model, resource_pack_lang, or resource_pack_static_asset.
namespacestringResource namespace.
pathstringResource 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:

FieldTypeMeaning
enabledbooleanAlways true when the object is present.
commands_jsonstringCommand-tree file used for validation.
files_checkednumber.mcfunction files checked.
commands_checkednumberRegular commands checked.
macro_commands_checkednumberMacro commands checked.
commands_skippednumberCommands skipped by validator rules.
errorsnumberCommand validation errors.
source_map_errorsnumberSource-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:

FieldTypeMeaning
versionnumberSource-map schema version. Currently 1.
entriesarrayGenerated file mappings.

Each entry has:

FieldTypeMeaning
generated_pathstringGenerated file path relative to the data pack root.
generated_linenumber1-based line number in the generated file.
commandstringGenerated command text without a leading slash, or the mapped generated JSON line text.
sourceobject or nullCobble source location when known.
kindstringCommand 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:

FieldTypeMeaning
filestringSource path, usually relative to the project/source root.
linenumber1-based Cobble source line.
columnnumber1-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.

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:

FieldTypeMeaning
versionnumberLink-state schema version. Currently 1.
target_kindstringOne of datapacks, world, or minecraft.
target_pathstringResolved target datapacks/ directory.
pack_namestringDirectory name of the linked data pack.
pack_pathstringFinal 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 1 fields 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.

FeatureFlagcobble.toml
Resource-pack output--experimental-resource-packresource_pack = true
Plugin diagnosticscobble check --experimental-pluginsplugins = true
Python compatibility reportcobble check --experimental-python-compatpython_compat = true
Editor symbolscobble 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.

  1. 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.

  1. Rebuild and re-run project checks:
cobble check
cobble build --validate
  1. 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 temp becomes /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 migrate defaults to --from 0.10 --to 0.11. Routes from 0.8 and 0.9 to 0.11 are also supported; pass --to 0.10 for the previous route.
  • The serde AST adds Assignment.persist, omitted from JSON when false.

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.

  1. Review the project with the migration report (0.9 to 0.10 is the default route; use --from 0.8 for older projects):
cobble migrate --json
  1. Update cobble.toml only after reviewing the report:
cobble migrate --apply
  1. Replace the 26.1.2 command tree. The default data/commands.json is fingerprint-checked against the 26.3 tree, so an old file is refused; delete it and let --validate regenerate it, or generate it explicitly:
rm data/commands.json
scripts/setup_commands_json.sh 26.3
  1. 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: condition is now type.
  • Item modifiers: function is now type; conditions lists are a single condition (combine several with minecraft:all_of).
  • Loot tables: functions is now modifier, conditions is a single condition, and a minecraft:tag entry’s name is now items ("#minecraft:logs").
  • Root advancements with display need display.background.
  • Cooking recipes need cookingtime (it no longer defaults to 200).

Other resource changes Cobble does not check:

  • Entity predicates (26.2): type is now minecraft:entity_type, unknown keys are rejected, and type_specific sub-predicates moved to top-level keys such as minecraft:type_specific/player.
  • Advancement trigger fields were renamed (for example recipe to recipes and block to blocks), and predicate lists are no longer all_of shorthand.
  • Number providers need an explicit type, and several were renamed (sum to add, product to mul).
  • Block state SNBT: Name/Properties are now id/properties.

Raw commands:

  • Slot arguments of item and execute if items are slot sources; existing slot strings such as container.0 and weapon.mainhand keep working.
  • Team and waypoint colors must be lowercase names such as dark_purple; reset is its own literal.
  • schedule clear takes a namespaced function ID.
  • Sign click events and commands only run when the sign sets allow_op_features:true.
  • give over the item limit, tick step, and a gamerule set to its current value now fail, which changes what execute store success records.

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 / 2 is -4).
  • not (a and b) now compiles with De Morgan’s laws.
  • Recursive function calls are rejected at compile time.
  • Plain cobble build and cobble clean --output refuse non-empty output directories that do not carry this project’s ownership marker.
  • --validate rejects commands above the function permission level (2 by default; set COBBLE_FUNCTION_PERMISSION_LEVEL for a server that raised it) and macro lines without $(...) variables.
  • Merged function tags are sorted, which can reorder minecraft:load and minecraft:tick handlers.

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.

  1. Review the project with the migration report:
cobble migrate --from 0.8 --to 0.9 --json
  1. Update cobble.toml only after reviewing the report:
cobble migrate --from 0.8 --to 0.9 --apply
  1. 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_alternative hints for unsupported Python-like constructs.

Items To Review Manually

  • pack_format: update old values such as 81 to 101.1.
  • resource_pack.*: keep [experimental] resource_pack = true or pass --experimental-resource-pack when using resource-pack helpers.
  • Python-like source: use cobble check --experimental-python-compat to see the supported Python-inspired surface and unsupported constructs that remain errors.
  • Plugins: plugins/*.toml manifests 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.

ScriptCovers
qa_07_templates.shEvery cobble init template through fmt --check, check, build --validate, inspect, and cleanup.
qa_07_link_clean_safety.shlink, doctor --json, watch --link, and clean --linked against unmarked, tampered, forged, and symlinked outputs.
qa_07_watch_smoke.shA 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.shOutput path safety, link and clean ownership, execute guard translation, and unroll limits. Run after touching any of these.
qa_08_stdlib_v2.shPer-module stdlib imports, missing-stdlib-module, the version = 1 warning, and the manifest stdlib fields.
qa_08_resource_authoring.shTag merging, dedup, and ordering, schema diagnostics, and path suggestions.
qa_08_unrolling.shLiteral loop unrolling, its limits and diagnostics, and the Unrolled source-map kind.
qa_08_resource_pack.shThe resource-pack opt-in, generated assets, ZIP contents, and manifest counts.
check_resource_snapshots.shRegenerates the resource fixture snapshots and fails on unintended diffs.
check_resource_schemas.shValidates 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.