Raw Packets
Sharp edge — you are talking to the protocol
send packet and on packet are SwoftLang's NMS escape hatch. Packet names and field shapes are resolved at runtime against the pinned Minestom snapshot (minestom-snapshots:ebaa2bbf64, 1.21.4) — the compiler checks only the statement shape, so a typo in a packet name or field is a runtime error, and a Minestom upgrade can rename fields under you. A malformed-but-constructible packet can desync or disconnect clients. on packet handlers run on the connection's read thread, not the tick thread. Prefer the first-class statements (displays, sounds, particles, nametags…) whenever one exists — reach for packets when nothing else does the job.
The first-class npc construct's runtime is built entirely on this surface — player-info packets, spawn packets, rotation packets, and one interact listener — the same primitives this page documents, now assembled for you in Java.
Sending packets
event PlayerJoin {
execute {
send packet "ParticlePacket" {
particle: "minecraft:flame",
overrideLimiter: false, longDistance: false,
x: 100.5, y: 65.0, z: 20.5,
offsetX: 0.5, offsetY: 0.5, offsetZ: 0.5,
maxSpeed: 0.01, particleCount: 40
} to event.player
}
}send packet "<Name>" { field: expr, ... } to <player | list | all>. The runtime resolves <Name> as a short or fully-qualified class name under net.minestom.server.network.packet.server.** and calls the record's canonical constructor, coercing each field by the record component's type:
| Component type | What you write |
|---|---|
numbers (int, float, byte…) | any Number expression |
String / Component | String (Components parse MiniMessage) |
boolean | Boolean |
UUID | a UUID string |
| enums | the constant name as a string ("ALWAYS", "SURVIVAL") |
Point / Pos | a Location value |
| nested records | a nested { field: expr, ... } map |
| lists | [ item, item, ... ] — items may be nested maps |
Field names must match the record components exactly — Minestom's names, not the wiki's (ParticlePacket's count field is particleCount). On mismatch the runtime error lists the components the record actually has.
Nested records and lists compose, which is enough to build a fake player from scratch:
command "ghost" {
execute {
send packet "PlayerInfoUpdatePacket" {
actions: ["ADD_PLAYER", "UPDATE_LISTED"],
entries: [{
uuid: "a0000000-0000-4000-8000-000000770000",
username: "Ghost",
properties: [],
listed: true,
latency: 0,
gameMode: "SURVIVAL",
displayName: none,
chatSession: none,
listOrder: 0
}]
} to sender
send packet "SpawnEntityPacket" {
entityId: 770000,
uuid: "a0000000-0000-4000-8000-000000770000",
type: 147,
position: sender.location,
headRot: 0.0,
data: 0,
velocityX: 0, velocityY: 0, velocityZ: 0
} to sender
}
}Listening for packets
on packet "<ClientPacketName>" { execute { ... } } is a top-level declaration that hooks one inbound client packet class. player and packet are bound; fields of the packet are readable as packet.<component> (read-only); cancel packet swallows it before default handling:
on packet "ClientPlayerDiggingPacket" {
execute {
if packet.status is "STARTED_DIGGING" {
send "<red>No mining in the lobby." to player
cancel packet
}
}
}cancel packet outside a listener is a compile error:
e_cancelpkt.sw:3:9: error: 'cancel packet' is only allowed inside an 'on packet' handlerThe packet color
on packet bodies are a restricted sync color of their own. They run on the connection's read thread — before the packet reaches the tick loop — so blocking is banned harder than in normal sync code:
waitandspawnare rejected;async { }is the one way to detach work.cancel packetmust run before the handler goes async — you can't decide to swallow a packet after it has already been processed.execute asyncon the whole handler is rejected outright:
e_pktasync.sw:1:1: error: 'on packet' handlers always run sync; use an 'async { }' block inside instead of 'execute async'e_pktwait.sw:3:9: error: 'wait' is not allowed inside 'on packet' handlers; wrap the work in an 'async { }' blockKeep listener bodies short: read fields, decide, cancel or not, and push anything slow into async { }:
on packet "ClientInteractEntityPacket" {
execute {
set target to packet.targetId
async {
// off the read thread: log, look things up, message people
broadcast "<gray>someone poked entity ${target}"
}
}
}Packet-field stringification
"${packet.type}" interpolates the field's Java toString() — handy for dispatching on union-shaped packets. The pinned snapshot's ClientInteractEntityPacket.type stringifies as Attack[], Interact[hand=MAIN], or InteractAt[...] — which is exactly how the npc runtime tells left clicks from right clicks.
When packets are the right tool
| Job | Reach for |
|---|---|
| floating text, 3D markers | displays — first-class |
| sounds, particles | play sound / spawn particle — first-class |
| per-viewer name changes | nametags — first-class |
| fake players / NPCs | the npc construct — first-class — or packets directly |
| client-side-only entities, custom equipment illusions, protocol tricks Minestom has no API for | packets — this page |