Appearance
Data resources
Not everything a pack needs is a command. Predicates, loot tables, advancements, recipes, and item modifiers are JSON files the game reads. Helix builds them from typed value trees and registers them on the Datapack, handing back a reference you pass around - you never write the file path or the id string twice.
Every registrar follows the same shape: dp.<kind>(name, def) writes the file and returns a typed handle.
| Registrar | Builds | Returns |
|---|---|---|
dp.predicate(name, def) | a Predicate tree | PredicateRef |
dp.lootTable(name, def) | a loot table | LootTableRef |
dp.advancement(name, def) | an advancement | Advancement |
dp.recipe(name, def) | a recipe | RecipeRef |
dp.itemModifier(name, def) | an item modifier | ItemModifierRef |
Predicates: the "over NBT" workhorse
A Predicate is a composable condition tree. Its reason to exist: express an entity-state check - including NBT - once, as a typed, engine-evaluated, referenceable file, instead of inlining nbt={…} into every selector.
Leaf conditions are static factories (Predicate.entity, .scores, .blockState, .location, .matchTool, .holding, .weather, .randomChance, .reference), and they compose with .and() / .or() / .not() (or Predicate.all/any/not):
ts
import { Datapack, v26_2, Selector, Predicate, Nbt, Short } from "helix";
const dp = new Datapack("checks", v26_2);
// Register once…
const focused = dp.predicate(
"focused",
Predicate.entity({ flags: { is_sneaking: true }, nbt: Nbt({ SleepTimer: Short(0) }) })
.and(Predicate.scores({ combo: { min: 3 } })),
);
const fn = dp.createFunction("reward");
fn.build((ctx) => {
// …reference by handle anywhere a predicate is accepted.
ctx.tellraw(Selector.allPlayers().predicate(focused), "focused bonus!");
});Compiled output - the real files helix emits for the code above:
txt
tellraw @a[predicate=checks:focused] {"text":"focused bonus!"}json
{
"condition": "minecraft:all_of",
"terms": [
{
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"nbt": "{SleepTimer:0s}",
"flags": {
"is_sneaking": true
}
}
},
{
"condition": "minecraft:entity_scores",
"entity": "this",
"scores": {
"combo": {
"min": 3
}
}
}
]
}The PredicateRef (focused) flows into two places, both by handle rather than id string:
Selector.predicate(ref)-@a[predicate=checks:focused], checked engine-side.predicateCheck(ref)forctx.if(...)-execute if predicate checks:focused run ….
Items are their own predicate
Because an Item lowers itself to both a give-stack and an item_predicate, a check built from an item matches the item you granted by construction:
ts
import { Datapack, v26_2, Selector, Item, Enchantment, Predicate } from "helix";
const dp = new Datapack("checks", v26_2);
const wand = Item.STICK.named("Wand").enchant(Enchantment.KNOCKBACK, 1);
const holdingWand = dp.predicate("holding_wand", Predicate.holding(wand));
const fn = dp.createFunction("cast");
fn.build((ctx) => {
ctx.tellraw(Selector.allPlayers().predicate(holdingWand), "you feel a spark");
});Compiled output - the real files helix emits for the code above:
txt
tellraw @a[predicate=checks:holding_wand] {"text":"you feel a spark"}json
{
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"equipment": {
"mainhand": {
"items": "minecraft:stick",
"components": {
"minecraft:custom_name": {
"text": "Wand"
},
"minecraft:enchantments": {
"minecraft:knockback": 1
}
}
}
}
}
}Predicate.matchTool(item) is the loot/mining-context sibling; Predicate.holding(item, slot) matches an equipment slot on an entity.
Going further
The loot/recipe/advancement builders follow the same "build a typed *Def, register, get a ref" pattern - see them under Data resources in the API reference. For raw JSON the typed builders don't model yet, dp.registryFile(...) is the one sanctioned escape hatch, and validateDatapack checks emitted JSON against the vanilla schema for your target version.