Skip to content

Custom Items

An item block declares a custom item at the top level: its identity, its look, its lore, and a tree of freeform NBT tags. The id and every tag ride the ItemStack, so the item keeps its identity through any inventory move. The block covers appearance and data only — cooldowns, abilities, and stat systems are built on top with events, tags, and addons.

swoftlang
item "aspect_of_the_end" {
    material: "DIAMOND_SWORD"
    name: "<blue>Aspect of the End"
    rarity: rare
    glint: true
    amount: 1

    lore {
        line "<gray>A legendary blade from the End."
        blank
        line "<dark_gray>Soulbound"
    }

    tags {
        damage: 100,
        meta: { tier: 3, keywords: ["end", "sword"] }
    }
}

command "sword" {
    execute {
        give item "aspect_of_the_end" to sender
    }
}

Declaration keys

KeyTypeRequiredMeaning
material:string literalone of material/skullMinecraft material, bare or minecraft:-prefixed
skull:string literalone of material/skullplayer-head texture (hash or full base64) — renders a custom skull
name:String expressionnodisplay name; MiniMessage, italics off. Colour it yourself — nothing is auto-applied
rarity:enumnoone of the four vanilla rarities; see below
glint:Boolean expressionnoenchantment shimmer without an enchantment
amount:Integer expressionnodefault stack size handed out by give item (default 1)
lore { ... }restricted DSLnothe exact lore lines — WYSIWYG; see lore
tags { ... }nested datanoarbitrary NBT: scalars, lists, compounds; see tags
attributes { ... }key: number pairsnoreal vanilla attribute modifiers, applied while held or worn
on_click(<filter>) { ... }blocknofiltered use handler for this item; see on_click

material and skull are mutually exclusive — one identity, one look:

swoftlang
item "confused" {
    material: "STICK"
    skull: "abc"
}
txt
matskull.sw:1:1: error: item "confused" cannot have both 'material' and 'skull'
item "confused" {
^

Rarity

rarity: sets the vanilla rarity component — the value the client uses to tint the item's name colour. Four values are valid:

common · uncommon · rare · epic

Rarity sets that component and nothing else: no lore, banner, or stat block is assembled. Custom tiers (legendary, mythic, a scheme of your own) are data — model them with a tag and a lore line, as the RPG item tutorial does. Any other name is rejected, with the fix in the message:

swoftlang
item "relic" {
    material: "NETHER_STAR"
    rarity: legendary
}
txt
rar.sw:3:13: error: rarity 'legendary' is not a SwoftLang rarity; only the four vanilla rarities are supported: common, uncommon, rare, epic (custom tiers are userland data — model them with tags{} + lore)
    rarity: legendary
            ^

Lore

The lore { } body is what you see is what you get — every line you write is a line in the tooltip, in order, with no sections assembled for you. It is the same restricted DSL as scoreboard lines { }: line <string-expr>, blank, if/else, and loop, evaluated once when the item is built. MiniMessage in the strings is converted at build time.

swoftlang
item "farmer_boots" {
    material: "GOLDEN_BOOTS"
    name: "<green>Farmer Boots"
    rarity: uncommon

    attributes {
        speed: 0.02
        max_health: 4
    }

    lore {
        loop 2 times {
            line "<green>Tilled and true."
        }
    }
}

Because lore is literal, templating a stat line is just interpolation — read a tag, print it. See lore templating for the pattern.

Attributes

attributes { } carries real Minestom attribute modifiers — genuine engine mechanics, applied while the item is in the main hand or a worn armor slot (a periodic equipment scan diffs and applies/removes them) and removed when it isn't. The key set is closed:

speed · max_health · attack_damage · attack_speed · armor · knockback_resistance

These are the vanilla attributes the server actually acts on — distinct from any custom "stats" you invent, which live in tags as plain data.

Tags: the NBT API

tags { } attaches arbitrary structured data to the item — scalars, lists, and nested compounds. Scalars become typed Minestom tags; compounds and lists become NBT structure. The shape round-trips: what you write is what you read back.

swoftlang
item "crypt_key" {
    skull: "1ae3855f952cd4a03c148a946e3f812a5955ad35cbcb52627ea4acd47d3081"
    name: "<gold>Crypt Key"
    rarity: epic

    tags {
        uses: 3,
        meta: { engraving: "unclaimed" }
    }

    lore {
        line "<gray>Opens the sealed crypt beneath the graveyard."
        line "<dark_gray>Consumed on use."
    }
}

Reading and writing tags at runtime

On any Item value, item.tags.<path> reaches into that tree. Paths nest (item.tags.meta.tier), reads return an optional<Any>, writes set the tag, and writing none deletes it.

FormMeaning
item.tags.usesread — optional<Any>, none when the tag is absent
item.tags.meta.tierread a nested path
set item.tags.uses to 5write a scalar
set item.tags.uses to nonedelete the tag

Because a read is optional, the no-null discipline extends to NBT: a bare tag read can't be used where a concrete value is required until you handle the missing case. Using one in arithmetic without a fallback is a compile error:

swoftlang
command "c" {
    execute {
        set it to custom_item("k")
        set n to it.tags.uses + 1
        send "${n}" to sender
    }
}

item "k" { material: "STICK" tags { uses: 3 } }
txt
tagopt.sw:4:26: error: the left operand of '+' is optional<Any> and may be missing; check it with 'if ... exists' or provide a fallback with 'otherwise'
        set n to it.tags.uses + 1
                         ^

The two fixes are the familiar ones — otherwise for a fallback, exists to narrow:

swoftlang
item "crypt_key" { material: "TRIPWIRE_HOOK" tags { uses: 3 } }

command "use-key" {
    execute {
        set it to custom_item("crypt_key")

        // fallback: absent 'uses' reads as 0
        set left to (it.tags.uses otherwise 0) - 1

        // narrow: only inside the guard is the value known present
        if it.tags.uses exists {
            send "uses left: ${it.tags.uses}" to sender
        }
    }
}

on_click

on_click(<filter>) { } is sugar for a filtered use handler bound to this item's id. The filter is left, right, or any; inside, player and item are bound, and the handler is cancellable. A tag write inside re-renders the item's lore and flushes it back to the hand.

swoftlang
item "crypt_key" {
    skull: "1ae3855f952cd4a03c148a946e3f812a5955ad35cbcb52627ea4acd47d3081"
    name: "<gold>Crypt Key"
    rarity: epic

    tags {
        uses: 3
    }

    on_click(left) {
        set item.tags.uses to (item.tags.uses otherwise 0) - 1
        send "<light_purple>The key turns..." to player
    }
}

The filter set is closed:

swoftlang
item "k" {
    material: "STICK"
    on_click(middle) {
        send "x" to player
    }
}
txt
ocf.sw:3:20: error: Unknown on_click filter 'middle'; valid filters: left, right, any
    on_click(middle) {
                   ^

Working with items at runtime

FormType / effect
give item "id" to <player> [amount N]statement — puts N copies (default the item's amount:) in the inventory
custom_item("id")Item — a fresh stack of the declared item
item("MATERIAL") / item("MATERIAL", n)Item — a plain vanilla stack
custom_id(<item>)optional<String> — the declared id, or none for vanilla stacks
<item>.materialString read/write — the material id
<item>.nameString read/write
<item>.amountInteger read/write
<item>.lorelist<String> read/write
<item>.tags.<path>the NBT namespace above

give item with a literal id is resolved at compile time — a typo can't reach the server:

swoftlang
command "kit" {
    execute {
        give item "aspect_of_the_emd" to sender
    }
}
txt
giveid.sw:3:19: error: unknown item 'aspect_of_the_emd' in 'give item'; declare it with 'item "aspect_of_the_emd" { }'
        give item "aspect_of_the_emd" to sender
                  ^

Identity survives everything

Identity survives anything the ItemStack survives: the id and every tag ride the stack itself, so a custom item dropped, stored in a chest GUI, or moved across inventories still answers to custom_id.

The PlayerUseItem event

Using any custom item fires PlayerUseItem — for every custom item, whether or not it declares an on_click. Reach for it when a single handler should span many item ids; reach for on_click when the behavior belongs to one item and you want left/right separation.

swoftlang
event PlayerUseItem {
    execute {
        if event.custom_id otherwise "" is "aspect_of_the_end" {
            send "<gray>You used the Aspect!" to event.player
        }
    }
}

event.player (Player), event.item (Item), and event.custom_id (optional<String>) are bound; the event is cancellable.

Build a gameplay system

Stats, cooldowns, and named abilities are things you build on top of this — tags for the numbers, lore templating for the display, events for the behavior. The Build an RPG Item System tutorial walks the whole pattern, and the abilities addon packages the cooldown machinery in ~50 lines of SwoftLang.