JavaScript macros
A JavaScript macro is exactly one ES module evaluated in one persistent GraalJS context. Import the API from mint:api; the editor also declares the most common exports globally.
import { Events, EventType, Chat, Player } from "mint:api";
Events.on(EventType.TICK_PRE, () => { if (Player.health() <= 6) Chat.print("Low health");});Typed events
Use EventType constants, not event-name strings. Available constants cover tick pre/post; render pre/post/world/overlay; incoming/outgoing chat; packet receive/send; key input; attack/use/block-break interaction; and replay start/stop/finish/pause/resume pre/post. Events.on(type, handler) returns an unsubscribe function.
Macros run after plugins on the same established callbacks. Tick, render, input, and replay handlers normally run on the Minecraft main thread. Packet-derived chat and packet handlers run synchronously on the packet hook, which may be Netty, so cancellation is available before that hook continues. Graal contexts are serialized and never entered concurrently. Macros that do not register a packet/chat listener are skipped by packet dispatch rather than scanned for every packet.
Packets and chat
Chat events are derived from the real incoming/outgoing chat packet, so chat and packet cancellation affect the same underlying packet. A packet event provides:
packet: the real version-specific Java packet object;packetClass: its class name;data: a lazy read-only field view;direction, plusmessagefor packet-derived chat;cancel()andisCancelled().
Use Packets.field, fieldNames, or fields, or select the generated packet type in the editor. Fields follow the selected Minecraft mappings. Hot packet paths cache class/field metadata; avoid converting every packet to JSON.
Events.on(EventType.PACKET_RECEIVE, event => { if (event.packetClass.endsWith("ExplosionS2CPacket")) { console.log(Packets.fields(event.packet)); event.cancel(); }});Cancellation must happen before the handler returns. A later Promise continuation cannot cancel a packet that has already continued through the game.
Promises and threads
GraalJS supplies Promise, async, await, then, catch, and finally. Mint does not replace them and does not automatically move arbitrary continuations between threads. A returned Promise never blocks event dispatch; an unhandled rejected fire-and-forget async handler is logged.
Http and Java/JNI asynchronous operations use shared executors. Use MainThread.run(() => value) or MainThread.await(completionStage) when the continuation must resume through Mint’s safe main-thread queue.
Promises alone do not perform CPU work in the background. Workers.run(functionName, data) creates a worker-owned Graal context, imports the same one-file module, and calls an exported function. Worker input/output must be copied primitives, arrays, buffers, or serializable objects. Workers have no Java or Minecraft access.
export function total(values) { return values.reduce((sum, value) => sum + value, 0);}
Events.on(EventType.CHAT_OUTGOING, event => { if (event.message !== "!total") return; event.cancel(); void Workers.run("total", [1, 2, 3]).then(result => MainThread.run(() => Chat.print(`Total: ${result}`)) );});One Graal context is never entered concurrently. Do not pass Minecraft or Java host objects to workers.
Java access
Macros are trusted local code. The main macro context has injected host bindings and full Java.type() access, for example const UUID = Java.type("java.util.UUID"). Java classes and Minecraft mappings differ by version. Keep high-frequency Minecraft work in the supplied Java wrappers rather than reflecting through JavaScript every tick.
Timers and cleanup
Timers.after/every/clear use ticks. Global setTimeout/setInterval use milliseconds but are still drained at safe client points. Store unsubscribe/timer IDs when behavior should be turned off while the macro remains enabled. Disabling/unloading destroys its registrations, timers, owned input, path, and context.
See JavaScript API reference and Macro examples.