Skip to main content
events.on(name, callback) adds a listener and returns an id. events.off(id) removes it again.
A script’s listeners are cleared for it every time the file reloads, so there is nothing to tidy up by hand. A module stays loaded for the life of the client, so anything it registers conditionally is its own to remove in disable. See Scripts and modules. Listening needs no permission, but the plugin has to be enabled: a listener belonging to a disabled plugin is skipped when the event fires. An unrecognised event name is accepted and warned about in the log, together with the list of names that do exist, since nothing would ever fire it.

The catalogue

title and anticheatFlag hand your callback two and three separate arguments rather than one object. Every other event passes a single value.

Payload shapes

A few of them are easier to misread than they look. ChatData gives you the line three ways round: message has the colour codes stripped, formatted is the line exactly as it was sent, and isSystem is true for a message shown above the hotbar rather than in chat. ActionBarData is the text above your hotbar, which on Hypixel carries timers, counters and pickup messages: it fires many times a second in a game, so keep the listener cheap. Returning false hides that line from you. ServerModData is what a server pushed to Lunar or Badlion for one of that client’s own mods. mod is the wire name the client uses (waypoints, tntTime, teamMarker on Badlion; waypoint, server_rule, mod_setting on Lunar) and payload is the JSON it carried, as text. Only the channel belonging to the client you are running is read, so a plugin on Forge never sees this event, and there is nothing to send back: it is what the server said, not a way to say anything.
MovementData is the position the server has put you at, not the one you walked to, so it fires on teleports and rubber-banding rather than on every step. PlayerConnectionData follows the tab list rather than the world, which on Hypixel is what you want: it fires as players enter and leave your lobby. name may be null for an entry the server sent without a profile. EntityVelocityData is already converted to blocks per tick, so you can compare it against movement.getMotionX() directly. InteractionData currently only ever reports "SLEEP", and ScoreboardObjectiveData uses mode 0 for created, 1 for removed and 2 for updated. title is the odd one out and hands your callback two arguments. type is one of TITLE, SUBTITLE, TIMES, CLEAR or RESET, and message has its colour codes stripped. The last three carry no text, so expect an empty string for those:
anticheatFlag hands you three: the flagged player’s UUID, the check’s id and its display name. anticheat.onFlag is the supported way to receive the same thing, and reads better alongside a check you registered yourself.

Cancelling

Returning false from a chat, actionBar or packet listener stops that message, line or packet reaching the rest of the client, which is how a plugin hides a line or drops a packet:
Every other event ignores what a listener returns, because by the time it runs the packet behind it has already been handled. Returning false from a tick or serverMod listener does nothing.
Cancelling chat hides the line from you, not from the server. Cancelling packet drops a packet the client was about to process, so dropping the wrong one desynchronises you from the server. Narrow with events.isPacket before you return false.

When your listener runs

chat, packet and renderOverlay run inline, on the thread that produced them, because each of them can be acted on before the client sees it. chat and packet therefore run on the network thread, and renderOverlay on the render thread. Everything else is scheduled onto the client thread and arrives a moment later, which is why those events cannot be cancelled. tick only fires while you are actually in a world, and renderOverlay only while no screen is open, so an overlay disappears while the inventory or a menu is up. Whatever the event, your listener has two seconds. A listener that passes that budget stops that plugin and leaves the rest of the client running, so keep the body short and hand anything slow to a timer.

Packets

packet fires for every packet the server sends and hands you the object Minecraft built rather than a copy of it, so read it through its Java accessors: getX(), not .x. events.isPacket(packet, name) narrows one by its simple class name, which is also what tells your editor the real signatures:
Every packet Seraph types is listed under ServerPacketMap in seraph.d.ts, from S00PacketKeepAlive through to S49PacketUpdateEntityNBT. A packet Seraph has no type for still arrives; it simply has no completion behind it. This is the busiest event there is, several hundred times a second in a full lobby, so check the type first and do as little as possible in the branch. If a named event above covers what you want, use that instead: windowOpen is cheaper than watching for S2DPacketOpenWindow yourself.

Java values you will meet

A packet’s accessors return Java objects, not plain JavaScript ones. They are typed in seraph.d.ts and behave the way Java does. toArray() is the quickest way out of Java and into ordinary JavaScript:
Compare an enum with name() rather than against the value itself, since two wrappers around the same constant are not === equal:

Next

API reference

Every global object a plugin can reach.

Examples

Complete plugins you can drop straight into the folder.