Skip to content

Items & NBT

An Item is the single source of truth for an item across your whole pack, and Nbt is the typed SNBT builder that turns a JS object into correct data. Both hide a version split you'd otherwise have to carry in your head: items lower to data components on 1.20.5+ and to NBT before it, and typed numbers get the right SNBT suffix automatically.

Items define once, render everywhere

Build the item once and hand the same object to every consumer. It lowers itself per target version and per context - an item-stack string for give, an item_predicate JSON body for a match_tool check - so a granted item matches its own predicate by construction. You never re-encode it.

MethodSets
.named(name)custom_name (string or a styled component)
.lore(...lines)lore lines
.enchant(id, level)one enchantment (repeatable)
.count(n)stack size
.model(ref)the item_model component (from dp.model(...))
.modelData(n)raw custom_model_data escape hatch
.component(name, value)a raw component the typed builders don't model yet
.data(raw)verbatim [components] / {nbt} escape hatch
ts
import { Datapack, v26_2, Selector, Item, Enchantment } from "helix";

const dp = new Datapack("loot", v26_2);

const fn = dp.createFunction("grant");
fn.build((ctx) => {
  const excalibur = Item.DIAMOND_SWORD
    .named({ text: "Excalibur", color: "aqua", italic: false })
    .enchant(Enchantment.SHARPNESS, 5)
    .enchant(Enchantment.UNBREAKING, 3)
    .lore("Pulled from the stone");

  ctx.playerGive(Selector.nearest(), excalibur);
});

▶ Open in playground

Compiled output - the real files helix emits for the code above:

txt
give @p minecraft:diamond_sword[custom_name={"text":"Excalibur","color":"aqua","italic":false},enchantments={"minecraft:sharpness":5,"minecraft:unbreaking":3},lore=[{"text":"Pulled from the stone"}]] 1

Bare ids and #tags work too (Item("diamond"), Item("#planks")), and a typed member (Item.DIAMOND_SWORD) autocompletes the vanilla ids.

NBT and typed numbers

Nbt(obj) serialises a JS value to SNBT at codegen. JS has one number type but SNBT has several, so wrap fractional/tagged numbers with the helpers or they round-trip wrong:

HelperSNBTUse
Float(n)1.0f32-bit float (motion, rotations)
Double(n)1.0d64-bit double (positions)
Byte(n)1bbyte - and how booleans are usually written
Short(n) / Long(n)1s / 1lshort / long

A plain JS number stays an int, strings are quoted, and any value object (a Block, say) embedded in the tree renders itself version-aware. That's why you build the tree instead of writing the string: {Pos:[Double(0.5), …]} gets the d suffixes for free.

ts
import { Datapack, v26_2, Pos, Nbt, Byte, Float, EntityType } from "helix";

const dp = new Datapack("mobs", v26_2);

const fn = dp.createFunction("spawn_guard");
fn.build((ctx) => {
  ctx.summon(
    EntityType.ARMOR_STAND,
    Pos.here(),
    Nbt({
      NoGravity: Byte(1),
      Invisible: Byte(1),
      Rotation: [Float(90), Float(0)],
      CustomName: '"Guard"',
    }),
  );
});

▶ Open in playground

Compiled output - the real files helix emits for the code above:

txt
summon minecraft:armor_stand ~ ~ ~ {NoGravity:1b,Invisible:1b,Rotation:[90.0f,0.0f],CustomName:"\"Guard\""}

Going further

An Item also flows into predicates and loot - Item.toPredicate(...) and componentsJson(...) are the same definition rendered for a match_tool condition or a set_components loot function; see Data resources. Nbt values slot into Selector.nbt(...) and any command that takes a data tag.

Released under the MIT License · Credits