Skip to content

1. Your first command

Create config/ignis/mycommands.lua and run /ignis reload after each change to see your commands in-game.

register("ping", function(ctx)
ctx.player:sendMessage("Pong!")
end)

The handler receives a ctx table with ctx.player. Call methods on it to interact with the player.

Add a <target:player> argument — the value arrives as a positional parameter:

register("heal <target:player>", function(ctx, target)
target:heal(20)
ctx.player:sendMessage("Healed " .. target.name)
end)

Wrap an argument in [...] to make it optional. Missing optional args are nil:

register("heal [<target:player>]", function(ctx, target)
local t = target or ctx.player
t:heal(20)
end)

See Registering Commands for all argument types.

A choice argument limits input to predefined options and provides tab completions:

register("gamemode <mode:choice=survival,creative,adventure,spectator> [<target:player>]", function(ctx, mode, target)
local t = target or ctx.player
t:sendMessage("Set " .. t.name .. " to " .. mode)
end)

Schedule a one-shot action with a delay in ticks (20 ticks = 1 second):

register("callback", function(ctx)
ctx.player:sendMessage("You'll be healed in 5 seconds...")
mc.schedule(100, function()
ctx.player:heal(20)
ctx.player:sendMessage("Healed!")
end)
end)

Repeating timers with mc.scheduleRepeating(delay, interval, callback):

register("countdown <seconds:int>", function(ctx, seconds)
local remaining = seconds
local id -- declare first so the callback can see it
id = mc.scheduleRepeating(0, 20, function()
remaining = remaining - 1
if remaining <= 0 then
mc.broadcast("Go!")
mc.cancelTask(id)
else
mc.broadcast(remaining .. "...")
end
end)
end)

See the mc.* API for scheduler options.

Async helpers live in the async module. Command handlers, event handlers and mc.schedule callbacks run as coroutines, so they can call async.sleep directly.

async.sleep(ticks) pauses execution for the given number of ticks. See Async API for details:

local async = require "async"
register("delayedheal", function(ctx)
ctx.player:sendMessage("Healing in 3...")
async.sleep(20)
ctx.player:sendMessage("2...")
async.sleep(20)
ctx.player:sendMessage("1...")
async.sleep(20)
ctx.player:heal(20)
ctx.player:sendMessage("Healed!")
end)

async.fetch(url) sends an HTTP GET request and waits for the response. See Async API for POST config and response fields:

local async = require "async"
register("playerinfo", function(ctx)
local res = async.fetch("https://api.github.com/users/" .. ctx.player.name)
if res.ok then
local data = res.json
ctx.player:sendMessage("GitHub account: " .. (data.login or "not found"))
else
ctx.player:sendMessage("API error: " .. (res.error or res.status))
end
end)

Pass a permission node as the third argument to register. Players without it can’t use the command:

register("kick <target:player> [<reason:text>]", function(ctx, target, reason)
target:kick(reason or "Kicked by an operator")
end, "admin.kick")

See World API and Vector API for particle, sound, and vec options:

register("fart", function(ctx)
local p = ctx.player
local pos = p.pos
p.world:playSound("minecraft:entity.player.burp", pos, 1.0, 1.0)
p.world:particle("minecraft:heart", vec(pos.x, pos.y + 1, pos.z), {
count = 5,
delta = { 0.5, 0.5, 0.5 }
})
p:sendMessage("You farted!")
end)
  • Events and storage — part 2 of this guide: reacting to events and saving data
  • Events — react to player joins, block breaks, chat, and more
  • Async API — detailed async.fetch and async.sleep reference
  • Storage — persist data across reloads with mc.data and player.data
  • Libraries — formatting templates, simple registrations, chest GUIs
  • Language extensions — \{ ... } lambda syntax