Compile JavaScript-style MDL language or Python API into a Minecraft datapack (1.21+ ready). Features variables, control flow, error handling, and VS Code extension.
Project description
Minecraft Datapack Language (MDL) v10
A powerful compiler that lets you write Minecraft datapacks in a modern JavaScript-style language (.mdl) or via a clean Python API, and then compiles to the correct 1.21+ datapack folder layout automatically.
๐ View Full Documentation - Complete guides, examples, and API reference
๐ฆ View on PyPI - Download and install from PyPI
๐ง VS Code Extension - Syntax highlighting, IntelliSense, and snippets
๐ Version 10 - JavaScript-Style MDL Language
Version 10 introduces a completely new JavaScript-style MDL language format with modern programming features:
โจ New Features in v10
- ๐ฏ JavaScript-style syntax with curly braces
{}and semicolons; - ๐ Modern comments using
//and/* */ - ๐ข Variable system with
num,str, andlisttypes - ๐ Advanced control flow including
switch,try-catch,break,continue - ๐ฆ Import system for modular code organization
- ๐จ VS Code extension with full IntelliSense and snippets
- ๐งช Comprehensive testing with E2E validation
- ๐ Extensive documentation with examples for every feature
๐ Migration from v9
- Version 9 and below used a different MDL format with indentation-based blocks
- Legacy documentation is available for users still using the old format
- Automatic migration tools are provided for converting old projects
๐๏ธ Core Features
- โ Handles the directory renames from snapshots 24w19a (tag subfolders) and 24w21a (core registry folders)
- โ
Easy hooks into
minecraft:tickandminecraft:loadvia function tags - โ
Creates tags for
function,item,block,entity_type,fluid, andgame_event - โ Unlimited nesting support for complex control flow structures
- โ Multi-file projects with automatic merging and dependency resolution
- โ Error handling with try-catch blocks and custom error messages
- โ Type system with number, string, and list variables
- โ Function system with parameters, return values, and recursion
Note: Version 10 uses pack_format 82 by default for the new JavaScript-style syntax. Legacy projects can still use pack_format 48.
๐ Install
Option A โ from PyPI (recommended for users)
Global, isolated CLI via pipx:
python3 -m pip install --user pipx
python3 -m pipx ensurepath # reopen terminal
pipx install minecraft-datapack-language
mdl --help
Virtualenv (if you prefer):
python3 -m venv .venv
source .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
pip install minecraft-datapack-language
Option B โ from source (for contributors)
# inside the repo
python -m pip install -e .
๐ Update
- pipx:
pipx upgrade minecraft-datapack-language - pip (venv):
pip install -U minecraft-datapack-language - Pin a version:
pipx install "minecraft-datapack-language==10.0.0"
๐ป CLI
New JavaScript-style MDL (v10)
# Create a new v10 project
mdl new my_pack --name "My Pack" --pack-format 82
# Build JavaScript-style MDL files
mdl build --mdl my_pack/mypack.mdl -o dist --wrapper mypack
mdl check my_pack/mypack.mdl
# Multi-file projects
mdl build --mdl my_pack/ -o dist # Build entire directory
mdl build --mdl "file1.mdl file2.mdl" -o dist # Build specific files
Legacy MDL (v9 and below)
# For legacy projects, use pack-format 48
mdl build --mdl legacy_pack.mdl -o dist --pack-format 48
Build a whole folder of .mdl files
mdl build --mdl src/ -o dist
# Recursively parses src/**/*.mdl, merges into one pack (errors on duplicate functions).
# Only the first file should have a pack declaration - all others are modules.
Build multiple specific .mdl files
mdl build --mdl "src/core.mdl src/features.mdl src/ui.mdl" -o dist
# Parses multiple specific files and merges them into one datapack.
# Only the first file should have a pack declaration - all others are modules.
Validate a folder (JSON diagnostics)
mdl check --json src/
๐ Quick Start - JavaScript-style MDL (v10)
Create your first JavaScript-style MDL project:
// my_pack.mdl
pack "My First Pack" description "A simple example" pack_format 82;
namespace "example";
// Global variables
var num counter = 0;
var str message = "Hello World";
function "init" {
say Initializing...;
counter = 0;
message = "Ready!";
}
function "tick" {
counter = counter + 1;
if "score @s counter matches 10" {
say Counter reached 10!;
counter = 0;
}
for player in @a {
effect give @s minecraft:speed 1 0;
}
}
// Lifecycle hooks
on_load "example:init";
on_tick "example:tick";
Build and test:
mdl build --mdl my_pack.mdl -o dist
# โ dist/my_pack/... and dist/my_pack.zip
๐ Multi-file Support
MDL supports building datapacks from multiple .mdl files. This is useful for organizing large projects into logical modules.
How it works
- Directory scanning: When you pass a directory to
--mdl, MDL recursively finds all.mdlfiles - File merging: Each file is parsed into a
Packobject, then merged into a single datapack - Conflict resolution: Duplicate function names within the same namespace will cause an error
- Pack metadata: Only the first file should have a pack declaration (name, description, format)
- Module files: Subsequent files should not have pack declarations - they are treated as modules
- Single file compilation: When compiling a single file, it must have a pack declaration
Best practices
- One pack declaration per project: Only the first file should have a pack declaration
- Module files: All other files should not have pack declarations - they are treated as modules
- Single file requirement: When compiling a single file, it must have a pack declaration
- Organize by namespace: Consider splitting files by namespace or feature
- Use descriptive filenames:
core.mdl,combat.mdl,ui.mdletc. - Avoid conflicts: Ensure function names are unique within each namespace
Example project structure
my_datapack/
โโโ core.mdl # โ
HAS pack declaration
โโโ combat/
โ โโโ weapons.mdl # โ NO pack declaration (module)
โ โโโ armor.mdl # โ NO pack declaration (module)
โโโ ui/
โ โโโ hud.mdl # โ NO pack declaration (module)
โโโ data/
โโโ recipes.mdl # โ NO pack declaration (module)
Important: Only core.mdl should have a pack "Name" declaration. All other files are modules that merge into the main pack.
Usage Examples
Build from directory:
mdl build --mdl my_datapack/ -o dist
Build from specific files:
mdl build --mdl "core.mdl combat.mdl ui.mdl" -o dist
Check entire project:
mdl check my_datapack/
Check with verbose output:
mdl build --mdl my_datapack/ -o dist --verbose
Complete Multi-File Example
Here's a complete example showing how to organize a datapack across multiple files:
core.mdl (main file with pack declaration):
# core.mdl - Main pack and core systems
pack "Adventure Pack" description "Multi-file example datapack" pack_format 48
namespace "core"
function "init":
say [core:init] Initializing Adventure Pack...
tellraw @a {"text":"Adventure Pack loaded!","color":"green"}
function "tick":
say [core:tick] Core systems running...
execute as @a run particle minecraft:end_rod ~ ~ ~ 0.1 0.1 0.1 0.01 1
# Hook into vanilla lifecycle
on_load "core:init"
on_tick "core:tick"
combat/weapons.mdl (combat module):
# combat/weapons.mdl - Weapon-related functions
namespace "combat"
function "weapon_effects":
say [combat:weapon_effects] Applying weapon effects...
execute as @a[nbt={SelectedItem:{id:'minecraft:diamond_sword'}}] run effect give @s minecraft:strength 1 0 true
function "update_combat":
function core:tick
function combat:weapon_effects
combat/armor.mdl (armor module):
# combat/armor.mdl - Armor-related functions
namespace "combat"
function "armor_bonus":
say [combat:armor_bonus] Checking armor bonuses...
execute as @a[nbt={Inventory:[{Slot:103b,id:"minecraft:diamond_helmet"}]}] run effect give @s minecraft:resistance 1 0 true
function "update_armor":
function combat:armor_bonus
ui/hud.mdl (UI module):
# ui/hud.mdl - User interface functions
namespace "ui"
function "show_hud":
say [ui:show_hud] Updating HUD...
title @a actionbar {"text":"Adventure Pack Active","color":"gold"}
function "update_ui":
function ui:show_hud
function combat:update_combat
function combat:update_armor
data/recipes.mdl (data module):
# data/recipes.mdl - Custom recipes
namespace "data"
# Custom recipe for a special item
recipe "special_sword":
{
"type": "minecraft:crafting",
"pattern": [
" D ",
" D ",
" S "
],
"key": {
"D": {"item": "minecraft:diamond"},
"S": {"item": "minecraft:stick"}
},
"result": {
"item": "minecraft:diamond_sword",
"count": 1
}
}
# Function tag to run UI updates
tag function "minecraft:tick":
add "ui:update_ui"
Project structure:
adventure_pack/
โโโ core.mdl # โ
HAS pack declaration
โโโ combat/
โ โโโ weapons.mdl # โ NO pack declaration (module)
โ โโโ armor.mdl # โ NO pack declaration (module)
โโโ ui/
โ โโโ hud.mdl # โ NO pack declaration (module)
โโโ data/
โโโ recipes.mdl # โ NO pack declaration (module)
Build the project:
mdl build --mdl adventure_pack/ -o dist --verbose
This will create a datapack with:
- Core systems (initialization and tick functions)
- Combat features (weapon and armor effects)
- UI elements (HUD display)
- Custom data (recipes and tags)
- Cross-module calls (UI calls combat functions)
CLI Options for Multi-file Builds
--mdl <path>: Path to.mdlfile, directory, or space-separated file list--src <path>: Alias for--mdl(same functionality)-o, --out <dir>: Output directory for the built datapack--wrapper <name>: Custom wrapper folder/zip name (default: first namespace or pack name slug)--pack-format <N>: Minecraft pack format (default: 48 for 1.21+)-v, --verbose: Show detailed processing information including file merging--py-module <path>: Alternative: build from Python module withcreate_pack()function
Error Handling
- Missing pack declaration: Single files must have a pack declaration
- Duplicate pack declarations: Only the first file in a multi-file project should have a pack declaration
- Function conflicts: Duplicate function names within the same namespace will cause an error
- Clear error messages: Errors include file paths and line numbers for easy debugging
๐ The .mdl language
Grammar you can rely on (based on the parser)
- pack header (required once):
pack "Name" [description "Desc"] [pack_format N]
- namespace (selects a namespace for following blocks):
namespace "example"
- function (colon + indented commands, 4-space indents only):
function "hello": say hi tellraw @a {"text":"ok","color":"green"} - conditional blocks (if/else if/else statements):
function "conditional": if "entity @s[type=minecraft:player]": say Player detected! effect give @s minecraft:glowing 5 1 else if "entity @s[type=minecraft:zombie]": say Zombie detected! effect give @s minecraft:poison 5 1 else: say Unknown entity - while loops (repetitive execution):
function "countdown": scoreboard players set @s counter 5 while "score @s counter matches 1..": say Counter: @s counter scoreboard players remove @s counter 1 - for loops (entity iteration):
function "player_effects": for player in @e[type=minecraft:player]: say Processing player: @s effect give @s minecraft:speed 10 1 - function calls (one function invoking another with fully qualified ID):
function "outer": say I will call another function function example:hello - hooks (namespaced ids required):
on_load "example:hello" on_tick "example:hello"
- tags (supported registries:
function,item,block,entity_type,fluid,game_event):tag function "minecraft:tick": add "example:hello"The parser accepts an optionalreplaceflag on the header (e.g.tag function "minecraft:tick" replace:) but replacement behavior is controlled by the pack writer. - comments start with
#. Hashes inside quoted strings are preserved. - whitespace: empty lines are ignored; indentation must be multiples of four spaces (tabs are invalid).
Inside a function block, every non-empty line is emitted almost verbatim as a Minecraft command. Comments are stripped out and multi-line commands are automatically wrapped. See below for details.
Comments
MDL supports comments in a way that matches how Minecraft actually interprets them:
- Full-line comments (a line starting with
#) are ignored by the parser. - Inline
#characters are preserved inside function bodies, so you can still use them the waymcfunctionnormally allows.
Example:
pack "Comment Demo" description "Testing comments"
namespace "demo"
function "comments":
# This whole line is ignored by MDL
say Hello # This inline comment is preserved
tellraw @a {"text":"World","color":"blue"} # Inline too!
When compiled, the resulting function looks like:
say Hello # This inline comment is preserved
tellraw @a {"text":"World","color":"blue"} # Inline too!
Notice how the full-line # never makes it into the .mcfunction, but the inline ones do.
Control Flow
MDL supports conditional blocks and loops for advanced control flow.
Conditional Blocks
MDL supports if/else if/else statements for conditional execution:
function "conditional_example":
if "entity @s[type=minecraft:player]":
say Player detected!
effect give @s minecraft:glowing 5 1
else if "entity @s[type=minecraft:zombie]":
say Zombie detected!
effect give @s minecraft:poison 5 1
else if "entity @s[type=minecraft:creeper]":
say Creeper detected!
effect give @s minecraft:resistance 5 1
else:
say Unknown entity
effect give @s minecraft:slowness 5 1
Rules:
- Conditions must be valid Minecraft selector syntax
- Commands inside conditional blocks must be indented with 4 spaces
- You can have multiple
else ifblocks - The
elseblock is optional - Conditional blocks are compiled to separate functions and called with
executecommands - Proper logic:
else ifblocks only execute if previous conditions were false
While Loops
MDL supports while loops for repetitive execution:
function "while_example":
scoreboard players set @s counter 5
while "score @s counter matches 1..":
say Counter: @s counter
scoreboard players remove @s counter 1
say Decremented counter
Rules:
- Conditions must be valid Minecraft selector syntax
- Commands inside while blocks must be indented with 4 spaces
- While loops continue until the condition becomes false
- Important: Ensure your loop body modifies the condition to avoid infinite loops
For Loops
MDL supports for loops for iterating over entity collections:
function "for_example":
tag @e[type=minecraft:player] add players
for player in @e[tag=players]:
say Processing player: @s
effect give @s minecraft:speed 10 1
tellraw @s {"text":"You got speed!","color":"green"}
Rules:
- Collection must be a valid Minecraft entity selector
- Commands inside for blocks must be indented with 4 spaces
- For loops iterate over each entity in the collection
- Efficient execution: Each conditional block becomes a separate function for optimal performance
Multi-line Commands
Long JSON commands can be split across multiple lines with a trailing backslash \.
MDL will join them back together before writing the final .mcfunction.
Example:
pack "Multi-line Demo"
namespace "demo"
function "multiline":
tellraw @a \
{"text":"This text is really, really long so we split it",\
"color":"gold"}
When compiled, the function is a single line:
tellraw @a {"text":"This text is really, really long so we split it","color":"gold"}
๐ฏ FULL example (nested calls + multi-namespace)
# mypack.mdl - minimal example for Minecraft Datapack Language
pack "Minecraft Datapack Language" description "Example datapack" pack_format 48
namespace "example"
function "inner":
say [example:inner] This is the inner function
tellraw @a {"text":"Running inner","color":"yellow"}
function "hello":
say [example:hello] Outer says hi
function example:inner
tellraw @a {"text":"Back in hello","color":"aqua"}
# Hook the function into load and tick
on_load "example:hello"
on_tick "example:hello"
# Second namespace with a cross-namespace call
namespace "util"
function "helper":
say [util:helper] Helping out...
function "boss":
say [util:boss] Calling example:hello then util:helper
function example:hello
function util:helper
# Run boss every tick as well
on_tick "util:boss"
# Function tag examples
tag function "minecraft:load":
add "example:hello"
tag function "minecraft:tick":
add "example:hello"
add "util:boss"
# Data tag examples across registries
tag item "example:swords":
add "minecraft:diamond_sword"
add "minecraft:netherite_sword"
tag block "example:glassy":
add "minecraft:glass"
add "minecraft:tinted_glass"
What this demonstrates
- Nested-like function composition (
function example:innerinsidefunction "hello"). - Multiple namespaces (
example,util) calling each other with fully-qualified IDs. - Lifecycle hooks (
on_load,on_tick) on bothexample:helloandutil:boss. - Function tags to participate in vanilla tags (
minecraft:load,minecraft:tick). - Data tags (
item,block) in addition to function tags.
๐ Python API equivalent
from minecraft_datapack_language import Pack
def build_pack():
p = Pack(name="Minecraft Datapack Language",
description="Example datapack",
pack_format=48)
ex = p.namespace("example")
ex.function("inner",
'say [example:inner] This is the inner function',
'tellraw @a {"text":"Running inner","color":"yellow"}'
)
ex.function("hello",
'say [example:hello] Outer says hi',
'function example:inner',
'tellraw @a {"text":"Back in hello","color":"aqua"}'
)
# Hooks for example namespace
p.on_load("example:hello")
p.on_tick("example:hello")
util = p.namespace("util")
util.function("helper",
'say [util:helper] Helping out...'
)
util.function("boss",
'say [util:boss] Calling example:hello then util:helper',
'function example:hello',
'function util:helper'
)
# Tick hook for util namespace
p.on_tick("util:boss")
# Function tags
p.tag("function", "minecraft:load", values=["example:hello"])
p.tag("function", "minecraft:tick", values=["example:hello", "util:boss"])
# Data tags
p.tag("item", "example:swords", values=["minecraft:diamond_sword", "minecraft:netherite_sword"])
p.tag("block", "example:glassy", values=["minecraft:glass", "minecraft:tinted_glass"])
return p
Build it:
python - <<'PY'
from my_pack_module import build_pack
from minecraft_datapack_language.cli import main as M
# write to dist/ with a wrapper folder name 'mypack'
p = build_pack()
M(['build', '--py-object', 'my_pack_module:build_pack', '-o', 'dist', '--wrapper', 'mypack', '--pack-format', '48'])
PY
๐ง VS Code Extension
Get syntax highlighting, linting, and build commands for .mdl files in VS Code, Cursor, and other VS Code-based editors.
Quick Install
- Download from GitHub Releases
- Install the
.vsixfile:- Open VS Code/Cursor
- Go to Extensions (Ctrl+Shift+X)
- Click "..." โ "Install from VSIX..."
- Choose the downloaded
.vsixfile
Features
- Syntax highlighting for
.mdlfiles - Real-time linting with error detection
- Build commands:
MDL: Build current fileandMDL: Check Workspace - Workspace validation for multi-file projects
Development Setup
cd vscode-extension/
npm i
# Press F5 to launch the Extension Dev Host
๐ CI & Releases
- CI runs on push/PR across Linux/macOS/Windows and uploads artifacts.
- Release is triggered by pushing a tag like
v1.0.0or via the Release workflow manually. - Versions are derived from git tags via setuptools-scm; tag
vX.Y.Zโ package versionX.Y.Z.
Local release helper
# requires GitHub CLI: gh auth login
./scripts/release.sh patch "Fixes"
./scripts/release.sh minor "Features"
./scripts/release.sh major "Breaking"
./scripts/release.sh v1.2.3 "Exact version"
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file minecraft_datapack_language-10.1.1.tar.gz.
File metadata
- Download URL: minecraft_datapack_language-10.1.1.tar.gz
- Upload date:
- Size: 7.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9695ce8404d204877544ee6117b221a3bfd1892417161dd1a21d9e5db138e8a
|
|
| MD5 |
a6e8fa56f81b93f8b0f3426c42bc3c46
|
|
| BLAKE2b-256 |
8ec21d99cb01c92081b1989ddce77f852a1863912009cda9c34e24510435190c
|
File details
Details for the file minecraft_datapack_language-10.1.1-py3-none-any.whl.
File metadata
- Download URL: minecraft_datapack_language-10.1.1-py3-none-any.whl
- Upload date:
- Size: 51.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ea647b1831e88d0756c86e61dc67a7f3a32df5a917041631c8be82c77a66f86
|
|
| MD5 |
0884f4e0850e5a315fc5f56ba05def49
|
|
| BLAKE2b-256 |
6469b4633068f24d3f67122355f92daddb45fe620b262526a041e0b0000b9c10
|