API

Interface

Forms the player fills in and on-screen elements: boss bars and a sidebar. All of it works for any client, whether or not it has Crafting installed.

Forms

A form has a title, a body text, fields and buttons. You declare it in a file under ui/ and show it wherever you need (a command, an event, when an item is used):

ui/shop.craftCraftScript
import { give_rubies } from "/lib/rewards"

const SHOP = form("Ruby shop", {
    body: "Rubies cost 10 emeralds each.",
    fields: [
        { slider: "amount", label: "Rubies", min: 1, max: 16, default: 1 },
        { toggle: "gift", label: "Wrap as a gift" },
        { choice: "pouch", label: "Pouch", options: ["leather", "silk"] },
        { text: "note", label: "Note" }
    ],
    buttons: [{ id: "buy", label: "Buy" }, "cancel"]
})

fun open_shop(p: Player) {
    p.show_form(SHOP, fun(r) {
        if r.button != "buy" { return }                  // closed with Esc or cancelled
        let n = int(r.number("amount") ?? 1)
        if p.inventory.count("minecraft:emerald") < n * 10 { p.send("&cNot enough emeralds"); return }
        give_rubies(p, n)
        if r.toggle("gift") { p.send("&dWrapped in {r.choice("pouch")}: {r.text("note")}") }
    })
}
commands/shop.craftCraftScript
import { open_shop } from "/ui/shop"

command shop describe "Buy rubies" {
    if player == null { return "Players only" }
    open_shop(player)
}
Field Written as Read with
Text { text: "note", label: "Note", default: "" } r.text("note")
Toggle { toggle: "gift", label: "It's a gift", default: false } r.toggle("gift")
Slider { slider: "amount", label: "Amount", min: 1, max: 64, step: 1, default: 8 } r.number("amount")
Choice { choice: "color", label: "Color", options: ["red", "blue"], default: "red" } r.choice("color")
  • Each field key uses lowercase letters, digits and _. label is what the player sees; if it's missing, the key is used.
  • buttons is a list of { id, label } or just ids. A form without buttons gets an “OK” button.
  • In the response, r.button is the id of the button pressed, or null if the player closed the form (Esc). r.values is a map with all the fields.
  • The response arrives in the function you pass to show_form; the rest of the server keeps running while the player decides.

In Kotlin

ui/ShopForm.ktKotlin
package com.example.rubies.ui

object ShopForm {
    private val form = Form("Ruby shop").apply {
        body = "Rubies cost 10 emeralds each."
        slider("amount", "Rubies", 1.0, 16.0, default = 1.0)
        toggle("gift", "Wrap as a gift")
        choice("pouch", "Pouch", listOf("leather", "silk"))
        text("note", "Note")
        button("buy", "Buy")
        button("cancel", "Cancel")
    }

    fun show(player: Player) = player.showForm(form) { r ->
        if (r.button != "buy") return@showForm                 // closed with Esc or cancelled
        val n = (r.number("amount") ?: 1.0).toInt()
        if (player.inventory.count(Identifier.parse("minecraft:emerald")) < n * 10) { player.send("&cNot enough emeralds"); return@showForm }
        player.give(ItemStack.of(Items.RUBY, n))
        if (r.toggle("gift") == true) player.send("&dWrapped in ${r.choice("pouch")}: ${r.text("note")}")
    }
}

Form(title) with body, text(key, label, default, maxLength), toggle(key, label, default), slider(key, label, min, max, step, default), choice(key, label, options, default) and button(id, label). player.showForm(form) { r -> … } receives a FormResponse with button, values and text, toggle, number, choice.

Boss bars

A bar at the top of the screen, with a title, color, progress and style. Use it for a timer, a server event or something's health:

events/countdown.craftCraftScript
const TIMER = server.boss_bar("rubies:countdown", "&cRuby rush", "red", "segments_10")
let left = 60

on join { TIMER.show(player) }

every 1 second {
    left = max(0, left - 1)
    TIMER.progress = left / 60
    TIMER.title = "&cRuby rush: {left}s"
    if left == 0 { TIMER.remove() }
}
Member What it does
server.boss_bar(id, title?, color?, style?) Creates the bar or returns the existing one with that id. One id per bar: reloading the mod doesn't duplicate it.
bar.progress From 0 to 1.
bar.title The text, with & colors.
bar.color pink, blue, red, green, yellow, purple, white.
bar.style solid, segments_6, segments_10, segments_12, segments_20.
bar.show(player), bar.hide(player) Who sees it. bar.players lists them.
bar.remove() Removes it for everyone and frees the id.

In Kotlin: Crafting.server?.bossBar("rubies:countdown", "Time", BarColor.RED, BarStyle.SEGMENTS_10), with the same properties in camelCase.

A list of up to 15 lines on the right side of the screen, different for each player. Call it again to update it:

events/welcome.craftCraftScript
import { visits } from "/lib/storage"

on join {
    player.set_sidebar("&6Rubies", [
        "&fVisits: &e{visits(player)}",
        "&fRubies: &c{player.inventory.count("rubies:ruby")}"
    ])
}
  • player.set_sidebar(title, lines) shows or updates it; only that player sees it. Colors with &.
  • player.clear_sidebar() removes it.
  • In Kotlin: player.setSidebar(title, lines) and player.clearSidebar().

Keys of a mod

A mod can add keys of its own. Declare them with key and react with on key, which runs on the server for the player who pressed it. e.key is the id with the mod's namespace (dashmod:dash).

content/keys.craftCraftScript
key "dash" { label: "Dash", default: "R" }
key "heal" { label: "Second wind", default: "G" }

on key(e) {
    let p = e.player
    if e.key == "dashmod:dash" && !p.has_cooldown("minecraft:feather") {
        let d = p.direction
        p.velocity = vector(d.x * 1.6, 0.35, d.z * 1.6)
        p.set_cooldown("minecraft:feather", 2)
    }
    if e.key == "dashmod:heal" { p.heal(6) }
}

The keys show in Options > Controls > Key Binds, in a section named after the mod. Players can change them, and their choice is kept. default is a key name such as "R", "G", "F6" or "LEFT_ALT".

Players need Crafting installed on their game to have the keys. In Kotlin: ContentBuilder.key("dash", label = "Dash", default = "R") and Crafting.events.on<PlayerKeyEvent> { e -> … }.

Good to know

  • Forms, bars and the sidebar are visible to any Minecraft Java 26.3 client, with or without Crafting. A server mod can use them without anyone installing anything.
  • Titles, the action bar and chat messages are covered in The world and players.
  • Custom screens with your own layout (menus, machine inventories) don't exist yet; see the roadmap.