# Configuration Source: https://docs.seraph.si/docs/configuration Where Seraph stores its files and how to manage your settings. Seraph keeps a single per-user data folder shared across every supported client (Lunar, Badlion, Forge). Switch clients and your settings come with you. ## File location | OS | Path | | ------- | --------------------------------------------------------------- | | Windows | `%AppData%\Seraph\` | | macOS | `~/.Seraph/` | | Linux | `$XDG_DATA_HOME/Seraph/` (defaults to `~/.local/share/Seraph/`) | Inside: * `config.json`, your settings. * `token.json`, Seraph auth token. **Don't share this.** * `nicknames.json`, saved nick to real-name mappings (only entries you marked permanent). Blacklist data isn't stored locally, it lives on the Seraph backend and is fetched on demand. ## In-game settings menu Run `/seraph config` (or `/sconfig`). The menu has six tabs: API, Overlay, Anticheat, Misc, Tags, Experiments. ### Search The search bar at the top indexes every entry from every tab. Type a keyword (`fkdr`, `cape`, `flag`, `auto gg`) and it'll find every matching setting. ### Conditional entries Some settings only show in their tab when a parent toggle is on (e.g. the alert sound only appears when alerts are enabled). Search ignores those gates and shows them anyway, with a note explaining what to enable first. ## Manual edits Don't edit `config.json` while the game is running, Seraph rewrites the file when you change settings, so your edits will be lost. You can edit `config.json` directly while the game is closed. Seraph picks up changes on the next launch. If you imported an older config and Seraph complains it's out of date: ``` /seraph migrateconfig ``` ## Reset to defaults Delete `config.json` while the game is closed. On next launch, Seraph rewrites it with defaults. ## Sharing configs Make sure to remove your API Keys saved in the configuration file. `config.json` is portable. Copy it to a friend (or a different machine) and they get your exact setup. **Don't include `token.json`**: it's bound to your account and only works for you. # Anticheat Source: https://docs.seraph.si/docs/features/anticheat Settings on the Anticheat tab. The Anticheat tab has five entries. Each is a single toggle, except for **Flag Sound** which is a sound picker. ## Settings * **Auto Block**: toggle. * **Legit Scaffold**: toggle. * **No Break Delay**: toggle. * **Flag Play Sound**: toggle. * **Flag Sound**: sound picker. Only visible when **Flag Play Sound** is enabled. The UI doesn't ship descriptions for these entries, so the labels above are all you get on screen. ## Reviewing flags `/seraph check ` (alias `/sc`) shows whether a player is on your blacklist. Manual entries can be added with `/seraph blacklist ...` (alias `/sblacklist`). # Commands Source: https://docs.seraph.si/docs/features/commands Every chat command Seraph registers. All Seraph commands are case-insensitive, `/seraph`, `/Seraph`, `/SERAPH` all work. Most have a short alias. ## Discoverability ``` /seraph help [page] ``` Lists every visible command with its description, paginated. Hidden commands (debug, dev tools) don't show but still work and tab-complete. ## Stats & lookup | Command | Alias | What it does | | ------------------------------- | ---------- | -------------------------------------------------------------------------------------------------- | | `/seraph stats [mode]` | `/ss` | Print Hypixel stats for a player. Mode filters output. | | `/seraph check ` | `/sc` | Check if a player is blacklisted. | | `/seraph apikey [key]` | `/sapikey` | Save a Hypixel API key to your Seraph account. With no key, opens the Hypixel Developer Dashboard. | ## Overlay management | Command | Alias | What it does | | ------------------------- | ---------- | ------------------------------------- | | `/seraph add ` | `/sadd` | Add a player to the overlay manually. | | `/seraph remove ` | `/sremove` | Remove a manually-added player. | | `/seraph clear` | `/sclear` | Clear all manually-added players. | ## Blacklist | Command | Alias | What it does | | ------------------------------------------------------ | ------------- | ------------------------------ | | `/seraph blacklist ...` | `/sblacklist` | Add a player to the blacklist. | ## Nicknames | Command | Alias | What it does | | -------------------------------------------- | -------- | ------------------------------------------------------------------- | | `/seraph remap [permanent?]` | `/snick` | Map a nick to a real player. `permanent` saves to `nicknames.json`. | ## Account | Command | What it does | | --------------------------------- | ------------------------------------------------------------ | | `/seraph login` | Sign this game in to your Seraph account with a device code. | | `/seraph logout` | Sign this game out, revoking its session. | | `/seraph sessions list` | List every device signed in to your account. | | `/seraph sessions signout-others` | Sign out everything except this game. | | `/seraph account` | Open your account page, where devices and keys are managed. | `/seraph sessions` on its own does the same as `list`. See [Accounts & linked devices](/docs/getting-started/linking) for what a session is and how signing in works. ## Plugins | Command | Alias | What it does | | ---------------- | ---------- | ------------------------------ | | `/seraph plugin` | `/splugin` | Reload every plugin from disk. | ## Settings | Command | Alias | What it does | | ----------------------- | ---------- | ------------------------------- | | `/seraph config` | `/sconfig` | Open the in-game settings menu. | | `/seraph migrateconfig` | | Migrate a legacy config file. | ## No default keybind There's no keybind for opening the menu, it's command-only. Use `/seraph config` or the alias `/sconfig`. # Experiments Source: https://docs.seraph.si/docs/features/experiments Settings on the Experiments tab. The Experiments tab holds toggles for features still being tested. ## Settings ### Player Alert Implementation Toggle. Enable or disable the player alert implementation experimental feature. ### No Break Delay Implementation Toggle. Enable or disable the no break delay implementation experimental feature. # Misc utilities Source: https://docs.seraph.si/docs/features/misc Every entry on the Misc tab, grouped by section. The Misc tab is grouped into sections. Each entry below uses the label and description as they appear in the UI. ## Colors * **Primary Color**: main color for Seraph branding. Picks from Minecraft color codes. * **Accent Color**: secondary color for Seraph branding. ## Automation * **Randomised Auto GG**: send "GG" with random capitalisation at game end. * **Auto Safelist Final Kills**: add final kills to safelist automatically. * **Auto Who Command**: run `/who` when Bedwars games start. ## Game Alerts & Timers * **Trap Replacement Reminder**: remind to buy a new trap 30s after activation. * **Magic Milk Duration Timer**: show on-screen timer for magic milk effect. * **Magic Milk Alert Types**: pick which notification channels fire (Chat, Title, Sound). * **Magic Milk Alert Sound**: sound for the milk alert. Visible when Sound is selected in Alert Types. ## HUD * **Trade Indicator**: show HP difference between you and your opponent during fights. ## Visuals & Rendering * **Team-Colored Hitboxes**: color hitboxes by team in Bedwars. * **Hide Own Hitbox**: hide your hitbox in F3+B debug mode. * **Show Cape When Nicked**: display Mojang cape with nick skins. * **Display Nickname with Real Name**: show nicknames alongside real names. ## Interface * **Middle Click for Bedwars Menus**: use middle click for quick purchasing in shops. * **Clean Chat Messages**: hide repetitive Hypixel system messages. ## Performance & Audio * **Duels Glyph Filter**: remove glyph particles in Duels. * **Mute Portal Sounds in Lobbies**: silence portal sounds in lobbies. ## Network & Data * **Enhanced Ping Display**: use Seraph API for accurate ping data. * **Legacy Network Compatibility**: use legacy network compatibility mode for restricted internet service providers. ## Replay Mod * **Fix Player Name Autocomplete**: fix name suggestions in Replay Mod commands. * **Enable /who in Replays**: allow `/who` command in replays. ## Keybinds * **Pat Pat Mod**: enable or disable Pat Pat Mod. * **Pat Action**: keybind that triggers the pat action. ## walter7addons, Alert Settings * **Jump Boost Expiration Alert**: pick which channels fire (Chat, Title, Sound). * **Jump Boost Alert Sound**: visible when Sound is selected. * **Mining Fatigue Applied Alert**: pick which channels fire. * **Mining Fatigue Alert Sound**: visible when Sound is selected. * **No Arrows Remaining Alert**: pick which channels fire. * **No Arrows Alert Sound**: visible when Sound is selected. ## walter7addons, Gameplay Enhancements * **Prevent Bow Dropping**: block accidental bow drops. * **Arrow Distance Indicator**: show distance when arrows hit players. ## walter7addons, Visual & Audio * **Hide Glyph & Sponge Particles**: remove dream defender and sponge particles. * **Simplified Tab List**: remove header and footer from tab menu. * **Mute Own Footsteps**: silence your footstep sounds. ## walter7addons, Keybinds * **Random Nick Generator**: keybind that generates a random nickname. # Getting started Source: https://docs.seraph.si/docs/features/plugin-api/basics Write your first Seraph plugin in JavaScript, then enable and reload it. Seraph runs plugins written in JavaScript inside the client. A plugin can read chat, register commands, draw on your screen, call an HTTP API, add anti-cheat checks and more. Nothing is granted automatically, every plugin is off until you turn it on and states up front what it intends to use. ## Where plugins live Drop `.js` files straight into the `plugins` folder next to the rest of Seraph's data: | Platform | Folder | | -------- | ------------------------------------------------------------------- | | Windows | `%AppData%\Seraph\plugins` | | macOS | `~/Seraph/plugins` | | Linux | `$XDG_DATA_HOME/Seraph/plugins`, or `~/.local/share/Seraph/plugins` | Only `.js` files sitting directly in that folder are loaded. Subfolders are not searched, though a plugin can still `require` a file out of one. The folder is created for you the first time Seraph starts. ## Your first plugin Save this as `hello.js` in the plugins folder: ```js theme={null} declareModule({ name: "hello", displayName: "Hello", description: "Says hello when you type !hi.", permissions: ["chat"], }); events.on("chat", (event) => { if (event.isSystem) return; if (event.message.includes("!hi")) { chat.addChatMessage("§ahello world"); } }); ``` Every global used here, `declareModule`, `events` and `chat`, is provided by Seraph. There is no `import` to write and nothing to install. ## Turn it on Plugins are disabled by default, and there are two switches. Run `/seraph config` (alias `/sconfig`) and go to the **Plugins** tab. The master switch controls the plugin system as a whole. With it off, nothing runs. Each plugin has its own toggle. Expand **Show what this plugin uses** to see exactly which permissions it asked for and what each one allows, before you enable it. A plugin that is listed but disabled is never run, and any gated call it tries to make is refused and logged rather than silently ignored. ## Reloading The plugins folder is watched, so adding, editing or deleting a `.js` file reloads it a moment later without restarting the client. You can also reload by hand: ``` /seraph plugin ``` Alias: `/splugin`. Modules, files named `.mod.js`, are the exception. They load once per session and are not swapped out; if you edit one, Seraph tells you in chat that a restart is needed. See [Scripts and modules](/docs/features/plugin-api/modules). ## Editor support Every time plugins load, Seraph writes the full type definitions to `plugins/seraph.d.ts`. Any editor that understands TypeScript will pick that file up from the same folder and give you completion and inline documentation for the whole API, in plain JavaScript, with no build step. If you want the checking to be stricter, add a `jsconfig.json` beside your plugins: ```json theme={null} { "compilerOptions": { "checkJs": true, "target": "ES2022", "lib": ["ES2022"] }, "include": ["*.js", "seraph.d.ts"] } ``` ## What the engine supports Plugins run on Rhino at ES6 with Seraph's own transpiler and polyfills on top, so modern syntax such as classes, `let`/`const`, template literals, arrow functions, destructuring, private class fields, optional chaining and `async`/`await` all work. A plugin that never yields is stopped rather than allowed to hang the client. Loading a plugin has ten seconds, and anything Seraph dispatches into a plugin, an event, a command or a timer, has two. Passing the budget stops that plugin and leaves the rest of the client running. ## Next Declare your plugin, ask for permissions and expose settings. When to use `.mod.js`, lifecycle hooks and depending on other plugins. Every event, what it hands you and when it runs. Every global object a plugin can reach. Complete plugins you can drop straight into the folder. Why a plugin is not loading, running or drawing. # Events Source: https://docs.seraph.si/docs/features/plugin-api/events Every event a plugin can listen for, what it hands you, when it runs and how to cancel one. `events.on(name, callback)` adds a listener and returns an id. `events.off(id)` removes it again. ```js theme={null} declareModule({ name: "greeter", permissions: ["chat"] }); const listening = events.on("playerJoin", (data) => { chat.addChatMessage("§7welcome " + data.name); }); ``` 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](/docs/features/plugin-api/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 | Event | Your callback receives | Fires when | | --------------------- | ------------------------- | -------------------------------------------------------------------- | | `chat` | `ChatData` | A chat message arrives from the server | | `actionBar` | `ActionBarData` | The server writes a line above your hotbar | | `serverMod` | `ServerModData` | The server sends Lunar or Badlion data for one of that client's mods | | `packet` | The packet itself | Any packet arrives from the server | | `tick` | — | Every client tick, while you are in a world | | `renderOverlay` | — | Every frame, while no screen is open | | `worldUnload` | — | You leave a world | | `playerMove` | `MovementData` | The server moves you, teleports you or corrects your position | | `blockUpdate` | `BlockUpdateData` | A block changes, once per block in a bulk change | | `playerJoin` | `PlayerConnectionData` | Somebody is added to the tab list | | `playerLeave` | `PlayerConnectionData` | Somebody is removed from the tab list | | `playerDeath` | A UUID string | A player dies | | `playerRespawn` | A UUID string | You respawn or change dimension | | `playerInteract` | `InteractionData` | A player gets into a bed | | `entityVelocity` | `EntityVelocityData` | The server pushes an entity, including knockback on you | | `soundPlay` | `SoundData` | The server plays a sound | | `particleSpawn` | `ParticleData` | The server spawns particles | | `windowOpen` | `WindowData` | A chest, menu or other container opens | | `windowClose` | — | The server closes the open container | | `scoreboardObjective` | `ScoreboardObjectiveData` | A scoreboard objective is created, removed or renamed | | `title` | `(type, message)` | The server sends a title, subtitle or title command | | `anticheatFlag` | `(uuid, checkId, name)` | Any anti-cheat check flags a player | `title` and `anticheatFlag` hand your callback **two and three separate arguments** rather than one object. Every other event passes a single value. ## Payload shapes | Payload | Fields | | ------------------------- | ------------------------------------------- | | `ChatData` | `message`, `formatted`, `isSystem` | | `ActionBarData` | `message`, `formatted` | | `ServerModData` | `client`, `mod`, `payload` | | `MovementData` | `x`, `y`, `z`, `yaw`, `pitch` | | `BlockUpdateData` | `x`, `y`, `z`, `blockRegistryName` | | `PlayerConnectionData` | `uuid`, `name` | | `InteractionData` | `entityId`, `action` | | `EntityVelocityData` | `entityId`, `motionX`, `motionY`, `motionZ` | | `SoundData` | `name`, `x`, `y`, `z`, `volume`, `pitch` | | `ParticleData` | `name`, `x`, `y`, `z`, `count` | | `WindowData` | `windowId`, `title`, `slots` | | `ScoreboardObjectiveData` | `name`, `value`, `type`, `mode` | 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. ```js theme={null} declareModule({ name: "mod-watch" }); events.on("serverMod", (data) => { if (data.mod !== "waypoints") return; const payload = JSON.parse(data.payload); console.info(data.client + " sent " + payload.waypoints.length + " waypoints"); }); ``` `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: ```js theme={null} events.on("title", (type, message) => { if (type === "TITLE") console.info("title: " + message); }); ``` `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: ```js theme={null} events.on("chat", (event) => { if (event.message.includes("has joined the lobby")) return false; }); ``` 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: ```js theme={null} declareModule({ name: "sniffer" }); events.on("packet", (packet) => { if (events.isPacket(packet, "S29PacketSoundEffect")) { console.info(packet.getSoundName() + " at " + packet.getVolume()); } }); ``` 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. | Type | Read it with | | ---------------- | ---------------------------------------------------------------------- | | `JavaList` | `size()`, `get(i)`, `isEmpty()`, `contains(v)`, `toArray()` | | `JavaMap` | `size()`, `get(k)`, `containsKey(k)`, `keySet()`, `values()` | | `JavaEnum` | `name()` for the constant, `ordinal()` for its index | | `JavaUUID` | `toString()` | | `IChatComponent` | `getUnformattedText()`, `getFormattedText()`, `getSiblings()` | | `ItemStack` | `getDisplayName()`, `stackSize`, `getItemDamage()`, `getTagCompound()` | | `NBTTagCompound` | `hasKey(k)`, `getString(k)`, `getInteger(k)`, `getBoolean(k)` | | `BlockPos` | `getX()`, `getY()`, `getZ()` | | `GameProfile` | `getId()`, `getName()` | `toArray()` is the quickest way out of Java and into ordinary JavaScript: ```js theme={null} events.on("packet", (packet) => { if (!events.isPacket(packet, "S02PacketChat")) return; const component = packet.getChatComponent(); const siblings = component.getSiblings().toArray(); console.info(component.getUnformattedText() + " (" + siblings.length + " parts)"); }); ``` Compare an enum with `name()` rather than against the value itself, since two wrappers around the same constant are not `===` equal: ```js theme={null} if (packet.getGameType().name() === "SPECTATOR") { hud.actionBar("§7spectating"); } ``` ## Next Every global object a plugin can reach. Complete plugins you can drop straight into the folder. # Examples Source: https://docs.seraph.si/docs/features/plugin-api/examples Complete plugins you can drop into the folder and adapt. Every plugin below is a whole file. Save it into your plugins folder, enable it in `/seraph config` under **Plugins**, and it runs. Nothing here needs a build step or an install. ## Lobby greeter Watches the tab list and welcomes people as they arrive, with the greeting and a quiet mode exposed as settings. ```js theme={null} declareModule({ name: "lobby-greeter", displayName: "Lobby Greeter", version: "1.0.0", description: "Greets players as they join your lobby.", permissions: ["chat"], config: { greeting: "welcome", onlyInParty: false, }, }); events.on("playerJoin", (data) => { if (!data.name) return; if (config.onlyInParty && !party.isInParty()) return; chat.addChatMessage("§8[§bGreeter§8] §7" + config.greeting + " §f" + data.name); }); ``` `config` is live, so a change made in game takes effect on the next join without a reload. ## Health overlay Draws a small readout in the corner of the screen, toggled with a key. ```js theme={null} declareModule({ name: "health-hud", displayName: "Health HUD", permissions: ["render", "player"], config: { visible: true }, }); events.on("renderOverlay", () => { if (!config.visible) return; const line = "§c" + player.getHealth().toFixed(1) + "§7/§c" + player.getMaxHealth().toFixed(1); const width = render.getTextWidth(line); const x = render.getScreenWidth() - width - 8; const y = render.getScreenHeight() - 20; render.drawRect(x - 3, y - 3, width + 6, 14, 0x80000000); render.drawText(line, x, y, 0xFFFFFF, true); }); let wasDown = false; events.on("tick", () => { const down = input.isKeyDown("H"); if (down && !wasDown) { config.visible = !config.visible; savePluginConfig(); } wasDown = down; }); ``` `renderOverlay` runs every frame and only while no screen is open, so the readout hides itself whenever you open your inventory. Keep the body cheap: work out anything expensive in `tick` and draw the result here. ## Webhook relay Forwards matching chat lines to a Discord webhook, with the URL hidden in the menu. ```js theme={null} declareModule({ name: "webhook-relay", displayName: "Webhook Relay", permissions: ["network", "chat"], config: { webhook: "", match: "has joined", }, secrets: ["webhook"], }); events.on("chat", (event) => { if (!config.webhook) return; if (!event.message.includes(config.match)) return; http.fetch(config.webhook, "POST", { content: event.message }, { "Content-Type": "application/json", }) .then((response) => { if (response.responseCode >= 400) { console.warn("Webhook refused: " + response.responseCode); } }) .catch((reason) => console.error("Webhook failed: " + reason)); }); ``` Naming `webhook` in `secrets` draws it as dots with a reveal button, so it is not read off the screen during a share. See [Manifest and permissions](/docs/features/plugin-api/manifest). The `network` permission is limited to 30 requests a minute per plugin. A chat listener can fire far faster than that in a busy lobby, so match narrowly, or collect lines and send them on a timer rather than one request per message. ## A custom anti-cheat check Flags a player whose view snaps further in a tick than a person could turn. Seraph's own flag message and report pipeline takes over from there. ```js theme={null} declareModule({ name: "snap-check", displayName: "Snap Aim", permissions: ["anticheat"], config: { threshold: 90 }, }); const lastYaw = {}; anticheat.registerCheck("snap-aim", (data) => { const previous = lastYaw[data.uuid]; lastYaw[data.uuid] = data.rotationYaw; if (previous === undefined) return false; let delta = Math.abs(data.rotationYaw - previous) % 360; if (delta > 180) delta = 360 - delta; return delta > config.threshold && data.isSwingInProgress; }, "Snap Aim"); anticheat.onFlag((uuid, checkId, name) => { console.info(uuid + " flagged for " + name + " (" + checkId + ")"); }); ``` The tick function runs once per tick for every observed player, so it is the hottest code a plugin can write. Keep it to arithmetic, and never call the network from inside it. ## A stats command Registers `/whois`, resolves a name through Seraph's own API layer and tags the player in the world. ```js theme={null} declareModule({ name: "whois", displayName: "Who Is", permissions: ["commands", "chat", "network", "render"], }); events.registerCommand( "whois", (args) => { const target = args[0]; if (!target) { chat.addChatMessage("§cusage: /whois "); return; } stats.getPlayer(target) .then((profile) => { if (!profile) { chat.addChatMessage("§7no such player: §f" + target); return; } chat.addChatMessage( chat.createBuilder() .text("[whois] ") .color("b") .text(profile.name) .color("f") .hoverText("§7" + profile.uuid) .clickSuggest("/whois " + profile.name), ); nametags.set(profile.name, "§b[?] "); }) .catch((reason) => chat.addChatMessage("§clookup failed: " + reason)); }, (args) => events.completePlayers(args), ); ``` `stats.getPlayer` shares the mod's caching and proxy handling, so repeated lookups of the same player in a lobby cost nothing extra. `nametags` only ever clears decorations your own plugin added. ## A module with a schedule Modules are compiled, stay loaded for the session and get `enable` and `disable` hooks, which is what anything with a cron schedule wants. Save this as `standup.mod.js`. ```js theme={null} declareModule({ name: "standup", displayName: "Standup", permissions: ["chat", "notify"], config: { hourly: true }, }); const listeners = []; const schedules = []; let seen = 0; exports.enable = () => { listeners.push(events.on("playerJoin", () => { seen += 1; })); if (config.hourly) { schedules.push(cron("0 * * * *", () => { hud.actionBar("§7" + seen + " players seen this hour"); seen = 0; })); } }; exports.disable = () => { for (const id of listeners) events.off(id); for (const id of schedules) clearCron(id); listeners.length = 0; schedules.length = 0; }; ``` Editing a `.mod.js` file does not hot reload it. Seraph tells you in chat that a restart is needed instead, and ordinary scripts carry on reloading around it. See [Scripts and modules](/docs/features/plugin-api/modules). ## Sharing code between plugins Put the shared half in its own plugin, name it in `dependsOn` and `require` it: ```js theme={null} declareModule({ name: "colours" }); exports.tag = (text) => "§8[§b" + text + "§8] §7"; ``` ```js theme={null} declareModule({ name: "greeter", dependsOn: ["colours"], permissions: ["chat"] }); const colours = require("colours"); chat.addChatMessage(colours.tag("greeter") + "ready"); ``` Anything that is not the name of a loaded plugin is treated as a path relative to the requiring file, so `require("./lib/helpers")` keeps shared code in a subfolder even though only top level `.js` files are discovered. ## Next Every event, what it hands you and when it runs. Why a plugin is not loading, running or drawing. # Manifest & permissions Source: https://docs.seraph.si/docs/features/plugin-api/manifest Declare a plugin, ask for the permissions it needs and expose its settings. ## declareModule Call `declareModule` once, at the top of the file, before anything else. It is what gives your plugin a name, puts it in the Plugins tab and tells Seraph what it intends to use. ```js theme={null} declareModule({ name: "party-greeter", displayName: "Party Greeter", version: "1.0.0", author: "you", description: "Greets people as they join your party.", permissions: ["chat", "commands"], dependsOn: [], config: { greeting: "welcome!", onlyInParty: true, }, }); ``` | Field | Required | What it does | | ------------- | -------- | --------------------------------------------------------------------------- | | `name` | Yes | The identifier. Used by `dependsOn`, by `require` and as the storage key. | | `displayName` | No | The name shown in the Plugins tab. Falls back to `name`. | | `version` | No | Shown alongside the plugin. | | `author` | No | Shown alongside the plugin. | | `description` | No | Shown in the Plugins tab, so write it for the person deciding to enable it. | | `permissions` | No | What the plugin is allowed to do. Anything not listed here is refused. | | `dependsOn` | No | Other plugins that must load first. | | `config` | No | Default settings, editable in game and readable through `config`. | | `secrets` | No | Config keys the menu hides behind a reveal button. | Without `declareModule` the file still runs, but it is named after itself and holds no permissions, so every gated call it makes is refused. ## Permissions A gated call only goes through when all three of these are true: the master switch is on, that plugin is enabled, and the plugin declared the matching permission. Ask for the least you need, users see the whole list before they enable anything. | Permission | Allows | Reached through | | ----------- | ------------------------------------------------ | ------------------------ | | `chat` | Read and send chat messages | `chat` | | `commands` | Register and run chat commands | `events.registerCommand` | | `player` | Read and control the local player | `player`, `movement` | | `world` | Read and interact with the world and entities | `world` | | `network` | Make network requests | `http`, `stats` | | `storage` | Read and write its own persistent storage | `storage` | | `render` | Draw overlays on your screen | `render`, `nametags` | | `anticheat` | Register custom checks and receive flag events | `anticheat` | | `tray` | Show system tray icons and toast notifications | `tray` | | `notify` | Show titles and action bar text, and play sounds | `hud` | `console`, `scheduler`, `input`, `text`, `party` and the event listeners themselves are ungated. A refused call is written to the log once per plugin and permission, naming what was attempted, so a plugin that quietly does nothing is usually a missing entry in `permissions`. ### Network rate limit The `network` permission is additionally rate limited to **30 requests per minute** per plugin, counted across `http.fetch` and `stats`, so a plugin cannot spam an endpoint even once you have granted it access. ## Config Anything you put in `config` becomes a default setting. Users edit it from the plugin's own menu in the Plugins tab, and your plugin reads it through the global `config` object: ```js theme={null} declareModule({ name: "party-greeter", permissions: ["chat"], config: { greeting: "welcome!", onlyInParty: true, }, }); events.on("chat", (event) => { if (config.onlyInParty && !party.isInParty()) return; chat.addChatMessage(config.greeting); }); ``` The object is live: read it when you need a value rather than copying it into a variable at load time, and a change made in game takes effect straight away. Writing to `config` is allowed, and `savePluginConfig()` writes the changes to disk so they survive a restart: ```js theme={null} config.greeting = "hello again"; savePluginConfig(); ``` Config is stored per plugin under `plugins/config`. ### Hiding a value Name a config key in `secrets` and the menu draws it as dots with an eye button beside it, the same as the API key field, so a webhook or a token is not read off the screen during a share: ```js theme={null} declareModule({ name: "webhook-relay", permissions: ["network"], config: { webhook: "", delay: 20, }, secrets: ["webhook"], }); ``` Only the drawing changes. The value is stored in `plugins/config` and read through `config` like any other setting, so treat this as a guard against an accident rather than a way of keeping a secret from whoever is at the keyboard. A hidden field is still editable while it is masked, and a key that is not part of `config` is ignored. ## Dependencies List other plugins in `dependsOn` and Seraph loads them first, so their exports are ready by the time your file runs: ```js theme={null} declareModule({ name: "stats-hud", dependsOn: ["stats-core"] }); const core = require("stats-core"); ``` If something you depend on is missing or disabled your plugin is skipped entirely, with the reason logged, rather than failing half way through. Plugins that depend on each other in a circle are all still loaded, but the order between them is arbitrary and the circle is reported. # Scripts & modules Source: https://docs.seraph.si/docs/features/plugin-api/modules When to use a .mod.js module, lifecycle hooks, require and sharing code. Seraph loads two kinds of file out of the plugins folder, and the only difference is the name. | | Script | Module | | ------------------- | --------------------------- | ----------------------- | | File name | `name.js` | `name.mod.js` | | Run as | Interpreted | Compiled to bytecode | | Reloading | Hot swapped on every change | Loaded once per session | | Lifecycle hooks | No | `enable` / `disable` | | `import` / `export` | No | Yes | Reach for a script by default. Reach for a module when the plugin does work every tick or every frame, where compiled bytecode is markedly quicker, or when it needs to clean something up on shutdown. ## Scripts A script is just a file. It runs top to bottom when it loads, and everything it registered, its listeners, commands and timers, is cleared and re-registered whenever it reloads. There is nothing to tear down by hand. ```js theme={null} declareModule({ name: "ping", permissions: ["chat", "commands"] }); events.registerCommand("ping", () => { chat.addChatMessage("§apong"); }); ``` ## Modules Name a file `.mod.js` and it is treated as a module. It is compiled rather than interpreted, and it stays loaded for the life of the client: editing it does not reload it, and neither the watcher nor `/seraph plugin` swaps it out. Both tell you a restart is needed instead. Ordinary scripts carry on reloading around any running modules, which stay up. Because a module outlives a reload, anything it registers conditionally is its own to remove: ```js theme={null} declareModule({ name: "watcher", permissions: ["chat"] }); let listening = -1; let ticking = -1; exports.enable = () => { listening = events.on("chat", onChat); ticking = cron("*/5 * * * *", () => chat.addChatMessage("still here")); }; exports.disable = () => { events.off(listening); clearCron(ticking); }; ``` `enable` runs once the file has been evaluated. `disable` runs when the client shuts down, which is where anything outliving the game, a written file or an open connection, should be closed. The runaway guard applies to modules exactly as it does to scripts, so a loop that never ends is stopped rather than left to hang the client. ### ES module syntax Modules may use `import` and `export`, which Seraph rewrites to its own loader: ```js theme={null} declareModule({ name: "stats-core" }); export const loadedAt = Date.now(); export function describe() { return "stats-core reporting in"; } export class Counter { #value = 0; bump(by = 1) { this.#value += by; return this.#value; } } ``` ## require `require` loads either another plugin or another file. ```js theme={null} const core = require("stats-core"); const helpers = require("./lib/helpers"); ``` A specifier matching the `name` of a loaded module returns that module's exports. Declare it in `dependsOn` so it is guaranteed to have loaded first. Anything else is treated as a path relative to the requiring file, with or without the `.js` suffix, which is how you keep shared code in a subfolder even though only top level `.js` files are discovered. ## Timers Timers are available both as globals and on the `scheduler` object, and both return an id you can stop later. ```js theme={null} const once = setTimeout(() => chat.addChatMessage("later"), 5000); const every = setInterval(() => chat.addChatMessage("tick"), 60000); const hourly = cron("0 * * * *", () => chat.addChatMessage("on the hour")); clearTimeout(once); clearInterval(every); clearCron(hourly); ``` `cron` takes the usual five fields, minute, hour, day of month, month and day of week, aligned to the wall clock. Each accepts a number, `*`, a `first-last` range, a comma separated list and a `/step` suffix. Months and days may be named, `jan` or `mon`, and both `0` and `7` mean Sunday. When both the day of month and the day of week are restricted the callback runs when either matches, as cron traditionally behaves. An expression that cannot be read returns `-1`. A script's timers are cleared for it on reload. A module's are not, which is what `disable` is for. ## Commands `events.registerCommand` adds a real chat command, with tab completion: ```js theme={null} declareModule({ name: "friends", permissions: ["commands", "chat"] }); events.registerCommand( "greet", (args) => chat.addChatMessage("hello " + (args[0] ?? "nobody")), (args) => events.completePlayers(args), ); ``` `events.completePlayers` narrows the players currently in tab down to those matching the argument being typed, the same set Seraph's own commands complete against. Subcommands are a map rather than a function: ```js theme={null} events.registerCommand("party", { invite: (args) => chat.sendChatMessage("/p invite " + args[0]), leave: () => chat.sendChatMessage("/p leave"), }); ``` # API reference Source: https://docs.seraph.si/docs/features/plugin-api/reference Every global object a Seraph plugin can reach. These globals are in scope in every plugin. The authoritative version of this, with full types and inline documentation, is written to `plugins/seraph.d.ts` every time plugins load, so your editor will always describe the build you are actually running. | Global | Permission | What it is | | ----------- | ----------- | -------------------------------------------- | | `config` | — | This plugin's live settings | | `console` | — | Logging to Seraph's log file | | `events` | — | Event listeners and command registration | | `scheduler` | — | Timeouts, intervals and cron | | `input` | — | Keyboard state | | `text` | — | Colour codes and number formatting | | `party` | — | The party you are in | | `platform` | — | Which client Seraph is running inside | | `chat` | `chat` | Reading and writing chat | | `player` | `player` | The local player and their inventory | | `movement` | `player` | Motion, rotation and movement state | | `world` | `world` | The world, entities and the scoreboard | | `http` | `network` | HTTP requests | | `stats` | `network` | Hypixel lookups through Seraph's API layer | | `storage` | `storage` | Persistent key/value storage for this plugin | | `render` | `render` | Drawing on screen | | `nametags` | `render` | Decorating player nametags | | `overlay` | `render` | Columns of your own on the stats overlay | | `waypoints` | `render` | Waypoints Seraph draws on the HUD | | `hud` | `notify` | Titles, action bar and sounds | | `anticheat` | `anticheat` | Custom anti-cheat checks | | `tray` | `tray` | System tray icon and OS notifications | ## events `events.on(name, callback)` returns an id, and `events.off(id)` removes it. A script's listeners are cleared for it on reload; a module's are not. | Event | Payload | | --------------------- | -------------------------------------------------- | | `chat` | `{ message, formatted, isSystem }` | | `packet` | The packet itself, narrowed with `events.isPacket` | | `tick` | — | | `renderOverlay` | — | | `worldUnload` | — | | `playerMove` | `{ x, y, z, yaw, pitch }` | | `blockUpdate` | `{ x, y, z, blockRegistryName }` | | `playerJoin` | `{ uuid, name }` | | `playerLeave` | `{ uuid, name }` | | `playerDeath` | The player's UUID | | `playerRespawn` | The player's UUID | | `playerInteract` | `{ entityId, action }` | | `entityVelocity` | `{ entityId, motionX, motionY, motionZ }` | | `soundPlay` | `{ name, x, y, z, volume, pitch }` | | `particleSpawn` | `{ name, x, y, z, count }` | | `windowOpen` | `{ windowId, title, slots }` | | `windowClose` | — | | `scoreboardObjective` | `{ name, value, type, mode }` | | `title` | `(type, message)`, as two arguments | An unrecognised event name is accepted but warned about in the log, since nothing would ever fire it. [Events](/docs/features/plugin-api/events) describes every payload, when each one runs and what can be cancelled. ### Cancelling Returning `false` from a `chat` or `packet` listener stops that message or packet reaching the rest of the client. Every other event ignores what a listener returns. ### Packets `packet` fires for every packet the server sends, on the network thread, 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: ```js theme={null} events.on("packet", (packet) => { if (events.isPacket(packet, "S02PacketChat")) { console.info(packet.getChatComponent().getUnformattedText()); } }); ``` Every packet Seraph types is listed under `ServerPacketMap` in `seraph.d.ts`, from `S00PacketKeepAlive` through to `S49PacketUpdateEntityNBT`. This is the busiest event there is, so keep the body short and leave anything heavy to `tick`. Also on `events`: `registerCommand(name, callback, tabComplete?)`, `registerCommand(name, submenus, tabComplete?)` and `completePlayers(args)`. See [Scripts and modules](/docs/features/plugin-api/modules). ## chat `addChatMessage(msg)` prints locally, `sendChatMessage(msg)` sends to the server, and `createBuilder()` returns a builder for anything richer: ```js theme={null} const line = chat.createBuilder() .text("click me") .color("a") .bold() .hoverText("§7opens the docs") .clickUrl("https://docs.seraph.si"); chat.addChatMessage(line); ``` The builder also has `italic()`, `underline()`, `strikethrough()`, `clickCommand(command)` and `clickSuggest(text)`. ## player and movement `player` covers identity (`getName`, `getDisplayName`, `getUUID`), vitals (`getHealth`, `getMaxHealth`, `getFoodLevel`, `getAir`, `getExperienceProgress`, `getExperienceLevel`), position (`getPosX`, `getPosY`, `getPosZ`), state (`isSprinting`, `isSneaking`, `isOnGround` and `isBlocking`, which is true while a sword is raised), actions (`swingItem`, `respawn`, `lookAt`, `dropHeldItem`, `closeContainer`, `clickWindow`) and the inventory (`getInventoryItems`, `getArmorItems`, `getItemInSlot`, `isSlotEmpty`, `getHeldItemSlot`, `setHeldItemSlot`, `getCurrentItemName`). `movement` reads `getYaw`, `getPitch`, `getMotionX/Y/Z`, `getSpeed` (blocks per tick), `isMoving`, `isInWater` and the same sprint, sneak and ground flags, and writes `setRotation`, `setMotion`, `setSprinting` and `setSneaking`. ## world `getName`, `getTime`, `isRaining`, `isThundering`, `getDifficulty`, `getBlockAt(x, y, z)`, `getPlayersInWorld()`, `getPlayerNames()`, `getEntities()`, `getClosestEntity(range)`, `getEntityById(id)`, `attackEntity(id)`, `interactEntity(id)` and `getScoreboard()`, which returns `{ getTitle(), getLines() }`. `getPlayerNames()` is the tab list with NPCs and yourself removed, the same set Seraph's own commands complete against. ## http and stats `http.fetch` returns a promise, or takes a callback if you would rather not use one: ```js theme={null} http.fetch("https://api.example.com/thing") .then((response) => console.info(response.responseCode + " " + JSON.stringify(response.body))) .catch((reason) => console.error(reason)); ``` The full signature is `fetch(url, method?, body?, properties?, callback?)`, where `method` is `GET`, `POST`, `PUT` or `DELETE` and `properties` is a map of request headers. A response is `{ responseCode, responseMessage, body }`. `stats.getUuid(nameOrId)` resolves a name or id to `{ name, uuid }`, or `null` if there is no such account. `stats.getPlayer(nameOrId)` fetches a Hypixel profile through Seraph's own API layer, so the lookup shares the mod's caching and proxy handling. Both take a callback instead of returning a promise if you prefer, and both count against the same rate limit as `http.fetch`. ## storage Key/value storage scoped to your plugin: `get(key)`, `set(key, value)`, `has(key)`, `remove(key)`, `keys()`, `clear()` and `save()`. Use `storage` for data your plugin manages itself and `config` for settings the user is meant to change. ## render and nametags `render` gives you `getScreenWidth`, `getScreenHeight`, `getTextWidth(text)`, `drawText(text, x, y, color, shadow?)` and `drawRect(x, y, width, height, color)`. Call them from a `renderOverlay` listener. `nametags` decorates players by name or UUID: `set(nameOrId, prefix?, suffix?)`, `setPrefix`, `setSuffix`, `clear(nameOrId)` and `clearAll()`. Only your own plugin's decorations are cleared. ## overlay Columns of your own, alongside the ones the overlay draws itself: ```js theme={null} declareModule({ name: "party-marker", permissions: ["render"] }); overlay.addColumn("Party"); events.on("tick", () => { for (const member of party.getMembers()) { overlay.setColumn("Party", member.uuid, "§b*"); } }); ``` `addColumn(label)` adds one and is `false` if this plugin already added it. `setColumn(label, nameOrId, text?)` fills one player's cell, taking a name or a UUID, and is `false` when that player is not in the tab list or the column was never added; `null` or an empty string blanks the cell. `getColumn(label, nameOrId)` reads back what **this** plugin last set there. `clearColumn(label)` blanks every cell but keeps the column, `removeColumn(label)` takes the column away with everything in it, `clearColumns()` removes every column this plugin added, and `columns()` lists this plugin's headings alphabetically. ## waypoints Waypoints Seraph draws itself, listed nearest first with the distance and compass direction to each. Unlike Lunar's and Badlion's own waypoints these render the same on every client, so a plugin does not have to care which one it is running under: ```js theme={null} declareModule({ name: "diamonds", permissions: ["render"] }); waypoints.set("Diamonds", 214, 12, -388, 0xFF55FFFF); events.on("tick", () => { if (waypoints.distanceTo("Diamonds") < 8) waypoints.remove("Diamonds"); }); ``` `set(name, x, y, z, colour?)` adds or moves one, taking an ARGB colour that defaults to white. `remove(name)`, `clear()` and `list()` cover the rest, `distanceTo(name)` is in blocks and `-1` for a waypoint that is not set, and `directionTo(name)` is `"N"`, `"NE"`, `"E"` and so on, or `null`. `setPosition(x, y)` moves the on-screen list, which is shared by every plugin, so the last call wins. A plugin's waypoints go when it unloads. ## platform Seraph injects into Lunar, Badlion and Forge alike, so anything client specific should ask here rather than assume: ```js theme={null} if (platform.isBadlion()) chat.addChatMessage("§7badlion detected"); ``` `getClient()` returns `"Lunar"`, `"Badlion"`, `"Forge"`, or `"Unknown"` before Seraph has finished starting. `isLunar()`, `isBadlion()`, `isForge()` and `isClient(name)` are the shorthands, and `isClient` compares without regard to case. ## hud `title(title, subtitle?, stay?, fadeIn?, fadeOut?)`, `actionBar(text)` and `sound(name, volume?, pitch?)`. Times are in ticks; twenty ticks is a second. ## party `isInParty()`, `getLeader()`, `getMembers()`, `getSize()`, `getRole(nameOrId)` and `isLeader(nameOrId)`. `refresh()` asks Hypixel for fresh party information, which arrives asynchronously, so the getters reflect it a moment later. ## anticheat ```js theme={null} anticheat.registerCheck("fast-turn", (data) => { return Math.abs(data.rotationYaw) > 180; }, "Fast Turn"); anticheat.onFlag((uuid, checkId, name) => { console.info(uuid + " flagged " + name); }); ``` `registerCheck(name, tickFn, userFriendlyName?, blacklistCode?)` runs `tickFn` once per tick for every observed player; return `true` to flag them and the built-in flag message and report pipeline takes over. `unregisterCheck(name)` removes it, and `onFlag` fires for every check, built-in or plugin-registered. ## tray `isSupported` says whether the platform has a system tray. `show(tooltip?)`, `hide()`, `setTooltip(tooltip)` and `notify(title, message, type?)`, where `type` is `info`, `warning`, `error` or `none`. ## text and input `text.colour(text)` (and `text.color`) turns `&` codes into the section signs Minecraft renders, `text.strip(text)` removes them, and `text.formatNumber(value)` groups a number the way the overlay does. `input.isKeyDown(keyName)` reports whether a key is held, for example `"F"`, `"LSHIFT"` or `"SPACE"`. ## console `log`, `info`, `warn` and `error`, all written to Seraph's log file. Use these rather than chat for anything diagnostic, they need no permission and do not spam the player. # Troubleshooting Source: https://docs.seraph.si/docs/features/plugin-api/troubleshooting Why a plugin is not loading, running, drawing or saving, and what the log says about it. Almost everything a plugin does wrong is written to the client log rather than to chat, so that a misbehaving plugin cannot spam you. Open `logs/latest.log` in your game folder and search for `[Seraph]`. ## The plugin does nothing at all There are two switches and both have to be on. Run `/seraph config`, open the **Plugins** tab, and check the master switch as well as the plugin's own toggle. While a plugin is disabled it is never run: its listeners are skipped when an event fires, its commands are not registered and every gated call it makes is refused. Enabling it takes effect at once, though a plugin that registers things at load time needs a reload with `/seraph plugin` before you see them. ## The plugin is not in the list Seraph only discovers `.js` files sitting **directly** in the plugins folder. A file in a subfolder is not loaded, though a loaded plugin can still `require` one. Check the path: | Platform | Folder | | -------- | ------------------------------------------------------------------- | | Windows | `%AppData%\Seraph\plugins` | | macOS | `~/Seraph/plugins` | | Linux | `$XDG_DATA_HOME/Seraph/plugins`, or `~/.local/share/Seraph/plugins` | If the file is in the right place, it threw while loading. The log names the file and the error. ## One call does nothing, the rest works That call needed a permission the plugin never asked for. The log says so once per plugin and permission: ``` Plugin 'x' attempted to use 'chat' without being enabled or without declaring that permission. ``` Add it to `permissions` in `declareModule` and reload. Because the warning is printed only once per permission for the life of the client, restart if you want to see it again. `console`, `scheduler`, `input`, `text`, `party` and the event listeners themselves are ungated, so a plugin that logs happily but cannot send chat is almost always missing `"chat"`. ## The plugin stops after a while Seraph stops a plugin that will not yield rather than letting it hang the client. Loading a plugin has **ten seconds**, and anything dispatched into it, an event, a command or a timer, has **two**. Passing the budget stops that plugin and leaves the rest of the client running. The usual causes are a loop with no exit, a synchronous wait, or heavy work in `tick`, `renderOverlay` or an anti-cheat check. Move slow work onto a timer, and keep hot callbacks to arithmetic. ## Edits to a module are ignored A file named `.mod.js` is a module. It is compiled once and stays loaded for the life of the client, so neither the folder watcher nor `/seraph plugin` swaps it out; Seraph tells you in chat that a restart is needed. Ordinary scripts carry on reloading around it. If you are iterating on something, work in a plain `.js` script and rename it once it settles. ## The plugin is skipped with a reason in the log Everything in `dependsOn` has to be present and enabled. If one of them is missing or switched off, the plugin is skipped entirely rather than failing part way through, and the reason is logged. Plugins that depend on each other in a circle are all still loaded, but the order between them is arbitrary and the circle is reported. ## A command did not register Two things stop a command appearing: * The plugin was disabled when it tried to register, which is logged. * The name is already taken. Seraph refuses to overwrite an existing command and logs `command '/x' already occupies registry space`, so pick another name. Commands need the `commands` permission, and anything the command prints needs `chat`. ## A listener never fires Check the spelling first. An unrecognised event name is accepted, but the log warns that nothing will ever fire it and prints the full list of names that do exist. If the name is right, the event may not mean what you expect. `playerMove` is the position the *server* puts you at rather than every step you take, `playerJoin` follows the tab list rather than the world, and `tick` only fires while you are in a world. The full list is in [Events](/docs/features/plugin-api/events). ## Nothing is drawn on screen `renderOverlay` only fires while you are in a world and **no screen is open**, so an overlay is hidden whenever the inventory, chat or a menu is up. Drawing also needs the `render` permission, and the coordinates are in scaled screen pixels, so use `render.getScreenWidth()` and `render.getScreenHeight()` rather than hard-coded numbers. ## Requests start failing The `network` permission is rate limited to **30 requests per minute per plugin**, counted across `http.fetch` and `stats`. Past that, calls are refused until the window rolls on. A chat listener in a busy lobby can pass that in seconds, so match narrowly, or collect what you want to send and flush it on a timer. ## A cron schedule never runs `cron` returns `-1` when the expression cannot be read. Check the return value: ```js theme={null} const id = cron("*/5 * * * *", tick); if (id === -1) console.error("bad cron expression"); ``` It takes the usual five fields, minute, hour, day of month, month and day of week, aligned to the wall clock rather than to game time. ## Settings do not survive a restart Writing to `config` changes the live value, but only `savePluginConfig()` writes it to disk: ```js theme={null} config.greeting = "hello again"; savePluginConfig(); ``` `storage` is separate and has its own `save()`. Use `config` for settings the user is meant to change in the menu and `storage` for data your plugin manages itself. ## The editor does not know the API `plugins/seraph.d.ts` is rewritten every time plugins load, so load them at least once and make sure your editor has the plugins folder open as its project root. A `jsconfig.json` beside your plugins makes it stricter, as described in [Getting started](/docs/features/plugin-api/basics). If completion is stale after a Seraph update, reload plugins so the file is regenerated against the build you are actually running. # Stats overlay Source: https://docs.seraph.si/docs/features/stats-overlay The tab-list replacement and everything you can configure on the Overlay tab. The stats overlay replaces Minecraft's vanilla tab list with a configurable grid of Hypixel statistics for the lobby or game you're in. ## How it works When you press Tab in a supported Hypixel game, Seraph cancels the vanilla render and draws its own table. Players are picked up from the live tab list as well as scoreboard scans, so the overlay stays accurate as people join and leave. ## Game modes The overlay swaps its columns based on the Hypixel game you're in: * **Bedwars** * **Skywars** * **Duels** Each mode has its own enable toggle, compact-mode toggle, and column list. ## The Overlay tab In the settings menu, the **Overlay** tab is split into three sub-tabs. ### General * **Custom Cubelify API URLs**: list of full API paths used as additional tag sources. Use the dashed entry to add a new URL; clearing one removes it. Format: `https://api.seraph.si/{{id}}/cubelify/blacklist`. * **Highlight Snipers**: highlight players with one or more tags. * When on, a **Sniper Highlight Color** picker appears with 16 Minecraft colors. * **Highlight Players**: highlight players based on the highlight rules you define in the Modes sub-tab. Required for those rules to do anything. * **Background Opacity**: slider from 0 to 255 controlling the overlay's background opacity. ### Modes For each of Bedwars, Skywars, and Duels: * **Enabled**: turns the overlay on for that mode. * **Compact Mode** (visible when enabled): displays the overlay in compact mode. * **Display Timers** (Bedwars only, when enabled): shows Bedwars resource timers in the overlay header. * **Compact Timers** (Bedwars only, when enabled): displays the timers in compact form. * **Highlight rules editor**: define rules that color rows based on stat conditions. Rules use stat names with operators `+ - * / ( ) <= >= < > = && ||`. Each rule has a color from a 9-color preset palette and can be enabled/disabled or reordered. ### Columns Pick a mode (Bedwars, Skywars, Duels) using the tabs at the top. Each tab shows its current column count. * If **Compact Mode** is on for that mode: a **Compact Stat** dropdown lets you pick the single statistic shown next to player names. * Otherwise: a column editor lets you pick which columns appear, in what order. Available columns vary per mode. #### Available columns per mode **Bedwars**: Stars, Name, HP, Finals, FKDR, Wins, WLR, Beds, BBLR, KDR, WS, Ping, Tags, Custom Tags, Client, Guild, Network Level. **Skywars**: Level, Name, HP, Wins, Kills, KDR, WLR, WS, Ping, Tags, Custom Tags, Client, Guild, Network Level. **Duels**: Name, HP, Wins, Kills, KDR, WLR, WS, Ping, Tags, Custom Tags, Client, Guild, Network Level. ## Sorting Players are grouped by team first, then sorted within each group: * **Bedwars**: by stars, descending. * **Skywars**: by Skywars EXP, descending. * **Duels**: no secondary sort. ## Custom players `/seraph add ` adds someone to the overlay manually. They render at the bottom of the table and stay until you `/seraph clear` or change worlds. ## Nicked players When someone joins under a nick, the overlay shows them as `?` until Seraph can resolve them through skin lookups, your saved nick map, or a manual `/seraph remap`. Mappings marked permanent are saved to `nicknames.json`. # Tags Source: https://docs.seraph.si/docs/features/tags Settings on the Tags tab. The Tags tab controls when alerts fire for reported and nicknamed players, and how tags render in the overlay. ## Settings ### Blacklist Alerts Alerts you in chat when a reported player joins your game. ### Only Alert on Verified Reports Only trigger alerts for players with verified reports. ### Play Sound on Report Alerts Play an audio notification when a reported player is detected. ### Report Alert Sound Sound picker. Only visible when **Play Sound on Report Alerts** is enabled. ### Report Alerts for Teammates Show alerts when reported players are on your team. ### Report Alerts in Lobbies Show alerts when reported players are in your lobby. ### Nick Alerts in Lobbies Show alerts when nicknamed players are in your lobby. ### Render MDI Icons in Tab Display Material Design Icons in the tags tab. ## Adding to the blacklist ``` /seraph blacklist ... ``` Alias: `/sblacklist`. ## Mapping a nick ``` /seraph remap [permanent?] ``` Alias: `/snick`. Pass `permanent` to save the mapping to `nicknames.json`. # API keys & login Source: https://docs.seraph.si/docs/getting-started/api-keys Sign in to Seraph, get your Seraph API key from Discord, and hook up your Hypixel key. Seraph pulls stats from two official sources, configured under the **API** tab in the settings menu: 1. **Hypixel API**: for player stats, guild data, and most overlay columns. 2. **Seraph API**: for tags, reports, ping data, and the live socket. ## Seraph login `token.json` is your authorisation token, you'll NEVER be asked for it. Signing in happens automatically the first time you run the launcher. It prompts you for Discord, and after you authorise, it writes your auth token to `token.json` in your Seraph folder. A game can also sign itself in without the launcher, with `/seraph login`, and every device signed in to your account can be listed and revoked. See [Accounts & linked devices](/docs/getting-started/linking). If the mod ever throws an authentication error on startup, re-run the launcher and sign in again, or run `/seraph login` in game. Your config and other data stay intact. ## Seraph API key You'll only need this on external applications such as Cubelify. The Seraph API key is a separate value from the login token. To get one: Open the Seraph Discord server. The invite link is on [seraph.si](https://seraph.si) or in the launcher. Type `/generate-key` in any channel where the Seraph bot is present. The bot DMs you a fresh key. Open `/seraph config`, go to the **API** tab, and paste the key into **Seraph API Key**. The key field is masked. Click the eye icon to reveal it for editing or copying. If you ever need to change your key, Open a ticket and an Administrator will guide you through the process. ## Hypixel API key Sharing this key with untrusted sources is risky and if abused, will permanently ban you from the Hypixel API A Hypixel API key is **required** for the overlay to fill in stats. Without one, columns like FKDR, wins, and ratios stay blank. Run `/seraph apikey` (alias `/sapikey`). It opens [developer.hypixel.net](https://developer.hypixel.net/) so you can claim or view your key. Sign in with your Minecraft account and copy your API key from the dashboard. Run `/seraph apikey `, or paste it into **Hypixel API Key** on the **API** tab of `/seraph config`. The key is saved to your Seraph account rather than to one install, so every client you sign in picks it up. Clearing it on [your account page](https://seraph.si/account) removes it everywhere. ## API Merger The API tab also has **Enable API Merger (port 3005)**. When on, Seraph runs a local HTTP server on `localhost:3005` that aggregates tag data from every Custom Cubelify API URL you've configured (under the Overlay tab, General sub-tab) and serves the merged result at `/api/merger?name=&id=`. Leave it off unless an external tool you're using needs to consume that combined feed. ## Verifying Open any Hypixel lobby or game and bring up the tab list. Players should populate with stats within a second or two. If everything stays blank, double-check: * Your Hypixel key is valid (rerun `/seraph apikey`). * Your Seraph API key is set (rerun `/generate-key` if needed). * `token.json` exists in your Seraph folder (re-run the launcher if not). * Your network can reach Hypixel and Seraph. # Downloads & updates Source: https://docs.seraph.si/docs/getting-started/downloads Where every Seraph build is published, and how the launcher and the mod keep themselves current. Everything Seraph publishes is served from `dl.seraph.si`. Nothing else is official. Our domains always end in `seraph.si`. A launcher from anywhere else is not ours, whatever the file is called. ## The launcher [Download for Windows x64](https://dl.seraph.si/launcher/0.3.0/windows/x64/seraph-launcher.exe) An installer. Windows may warn about an unrecognised app the first time you run it. [Download for Apple Silicon](https://dl.seraph.si/launcher/0.3.0/darwin/arm64/seraph-launcher.dmg) A disk image for M1 and later. Intel Macs are not published yet. [Download for Linux x64](https://dl.seraph.si/launcher/0.3.0/linux/x64/seraph-launcher.AppImage) An AppImage. Make it executable with `chmod +x seraph-launcher.AppImage` before running it. ## How the download URLs are built Every build is published under its own version, so a link never changes what it points at: ``` https://dl.seraph.si/launcher//// ``` | Platform | Path | | ------------------- | ---------------------------------------------- | | Windows x64 | `/windows/x64/seraph-launcher.exe` | | macOS Apple Silicon | `/darwin/arm64/seraph-launcher.dmg` | | Linux x64 | `/linux/x64/seraph-launcher.AppImage` | The current release is **0.3.0**. Alongside each build sits the bundle the updater installs (`seraph-launcher.app.tar.gz` on macOS) and a `.sig` file, which is the signature the launcher checks before it installs anything. ## How the launcher updates itself The launcher reads a manifest for its channel on startup: ``` https://dl.seraph.si/launcher/stable/latest.json ``` It names the newest version, one build per platform, and the signature proving that build came from Seraph's release pipeline. A build that is not signed by that key is refused, so an update cannot be substituted in transit. You do not need to download the launcher again by hand. Releases are cut on three channels: `stable`, `beta` and `alpha`. Ordinary installs follow `stable`. ## How the mod updates The mod is not something you download. The launcher fetches it for you and installs or injects it each time you launch, so the mod is current whenever the launcher is. While you are in game, Seraph is also told about new releases over its stats socket, and says so in chat once per version: ``` An update is available: 0.79.0 - restart your client to apply it ``` Restarting through the launcher is all that is needed. Your settings live on your account and in your Seraph folder, so nothing is lost. The versions currently published, per channel, are readable at [mod-socket.seraph.si/update](https://mod-socket.seraph.si/update) if you want to check what you should be running. ## Verifying what you downloaded The address bar must read `dl.seraph.si`. Not a lookalike, not a mirror, not a shortener. `seraph-launcher.exe`, `seraph-launcher.dmg` or `seraph-launcher.AppImage`. Anything ending in `.scr`, `.bat`, `.jar` or `.zip` claiming to be the launcher is not. Once installed, the launcher only ever installs signed builds from its own manifest. Taking updates from it, rather than from a link someone sent you, is the safest route. See [How to stay safe](/how-to-stay-safe/official-sources) for the full list of official sources. # Installation Source: https://docs.seraph.si/docs/getting-started/installation Install Seraph through the official launcher. Seraph installs through the **Seraph Launcher**. It handles everything: signing you in, picking your client, and injecting or installing the mod for you. ## Download the launcher Only download from official links, other downloads may compromise your own security. Our official domains will always end in `seraph.si`. Scammers use similar looking domains to compromise your account. [Download for Windows x64](https://dl.seraph.si/launcher/0.3.0/windows/x64/seraph-launcher.exe) An installer. Windows may warn about an unrecognised app the first time you run it. [Download for Apple Silicon](https://dl.seraph.si/launcher/0.3.0/darwin/arm64/seraph-launcher.dmg) A disk image for M1 and later. Intel Macs are not published yet. [Download for Linux x64](https://dl.seraph.si/launcher/0.3.0/linux/x64/seraph-launcher.AppImage) An AppImage. Make it executable with `chmod +x seraph-launcher.AppImage` before running it. Every published build, the URL layout and how updating works are on [Downloads & updates](/docs/getting-started/downloads). ## First launch Double-click `seraph-launcher.exe`. The first launch may take a few seconds while it sets itself up. On startup the launcher prompts you to sign in with your Discord account. This creates your Seraph account and writes your auth token to disk so the mod can talk to the Seraph backend. A game can sign itself in too, with `/seraph login`: see [Accounts & linked devices](/docs/getting-started/linking). Once you're signed in, you'll see three options: * **Download Forge**: installs Minecraft Forge 1.8.9 and drops the Seraph jar into your `mods/` folder. * **Inject for Lunar Client**: launches Lunar and injects Seraph into the running process. * **Inject for Badlion Client**: launches Badlion and injects Seraph into the running process. Pick whichever client you actually play on. In any world, run `/seraph help`. If the help menu appears, Seraph is loaded. ## Where files live The launcher creates a per-user data folder used by every Seraph install on your machine, no matter which client: | OS | Path | | ------- | --------------------------------------------------------------- | | Windows | `%AppData%\Seraph\` | | macOS | `~/.Seraph/` | | Linux | `$XDG_DATA_HOME/Seraph/` (defaults to `~/.local/share/Seraph/`) | Inside the folder you'll see: * `config.json`, your settings. * `token.json`, Seraph auth token. Don't share this. * `nicknames.json`, saved nick to real-name mappings (only "permanent" ones). Because the folder is shared, your settings carry over automatically when you switch between Lunar, Badlion, and Forge. ## Updating Re-run the launcher. It checks for new versions of the mod and re-installs or re-injects as needed. Your config is preserved across updates. ## Switching clients Quit Minecraft, run the launcher again, and pick a different option. You can have Seraph running on Lunar one day and Forge the next without losing any settings. # Accounts & linked devices Source: https://docs.seraph.si/docs/getting-started/linking How a launcher, a game and the website all end up signed in to the same Seraph account. Everything Seraph knows about you hangs off one **Seraph account**, and that account is your Discord identity. Nothing else is a login: there is no Seraph password to forget, and no separate account per install. Each thing you sign in becomes a **session** on that account. The launcher is one, every game you sign in is another, and the website is a third. Sessions are listed, labelled and revocable, so signing out one device never disturbs the rest. ## Linking the launcher The launcher signs in through your browser the first time you run it: It sends you to `auth.seraph.si/v4/oauth/authorize`, which hands you on to Discord's own consent screen. Discord asks whether Seraph may see who you are. Seraph never sees your Discord password and never asks for one. Discord returns you to Seraph, the launcher exchanges the result for a token, and writes it to `token.json` in your Seraph folder. Because the token is written to the shared Seraph folder, every client on that machine, Lunar, Badlion or Forge, picks up the same session without signing in again. ## Linking a game A game cannot catch a browser redirect the way the launcher can, so it uses a **device code** instead: it shows you a short code, you approve it wherever it is convenient, and the game waits. ``` /seraph login ``` Seraph prints a short code and a link. Seraph opens [auth.seraph.si/device](https://auth.seraph.si/device) for you, and clicking the code in chat opens it with the code already filled in. Sign in with Discord if you are not already, and confirm the code matches the one on your screen. The code is good for **15 minutes**. The game is polling in the background and says `Signed in. Seraph is connected to this game.` as soon as you approve. Nothing needs restarting. A code only ever grants what you approve while looking at it. If a code appears that you did not ask for, or someone asks you to approve one "for support", do not: approving it links **their** game to **your** account. Seraph staff will never ask you to approve a device code. Until you approve, the code buys nothing, so a code read aloud on a stream is worthless by the time anyone acts on it. Running `/seraph login` again while one is waiting repeats the same code rather than starting a rival sign-in, and signing a game in that was already signed in retires the session it replaces. ## Seeing what is linked ``` /seraph sessions ``` lists every device signed in to your account, with the client that signed it in and the label it was given, and marks the one you are playing on. The same list, with more detail, is on your account page. ``` /seraph account ``` opens [seraph.si/account](https://seraph.si/account), where each session shows when it was created, when it was last seen and when it expires. ## Unlinking | What you want | How | | ------------------------------------ | -------------------------------------------------------------------------- | | Sign this game out | `/seraph logout`, which revokes the session rather than just forgetting it | | Sign out everything except this game | `/seraph sessions signout-others` | | Sign out one specific device | Your account page | | Sign out everything | Your account page | Revoking a session takes effect immediately: the device holding it cannot renew, and its next request fails. Anything signed out has to go through the flow again to come back. `token.json` **is** your session. Seraph will never ask you for it, and no genuine support process involves sending it to anyone. If you have shared it, or run a "Seraph" build from somewhere that was not `seraph.si`, sign out every device from your account page straight away. ## Your Hypixel key is linked too Your Hypixel API key is held on your Seraph account rather than in one install's config: ``` /seraph apikey ``` saves it to the account, so every install you sign in picks it up automatically. Run it with no key to be walked through getting one. See [API keys & login](/docs/getting-started/api-keys) for where the key comes from and what it is used for. Clearing it on your account page removes it everywhere. It is only ever sent to Hypixel and to Seraph's own proxy, and if you have set your own key or proxy, Seraph's proxy is not used at all. ## What each credential is | Credential | Where it lives | What it is for | | -------------------- | ---------------------------- | ------------------------------------------------------------- | | Seraph session token | `token.json` | Proves this device is signed in to your account. Never share. | | Hypixel API key | Your Seraph account | Reading stats from Hypixel. | | Seraph API key | Given out by the Discord bot | External tools such as Cubelify. Not a login. | A Seraph API key is not a way to sign in, and a session token is not an API key. Neither is your Discord account: revoking Seraph's access from Discord's own settings stops new sign-ins, but existing sessions are ended from your account page. # Quickstart Source: https://docs.seraph.si/docs/getting-started/quickstart First five minutes with Seraph, open the menu, find a setting, run a command. Once Seraph is installed and your Hypixel key is in, here's the fastest path to making it useful. ## Open the settings menu ``` /seraph config ``` Alias: `/sconfig`. Both work case-insensitively (`/Seraph`, `/SCONFIG`, etc). The menu has six tabs: | Tab | What's in it | | --------------- | ------------------------------------------------------------------- | | **API** | Hypixel + Seraph keys, API merger toggle | | **Overlay** | General settings, per-mode toggles, column editor, highlight rules | | **Anticheat** | Auto Block, Legit Scaffold, No Break Delay, flag sound | | **Misc** | Auto GG, trade indicator, hitbox tweaks, audio, walter7addons, more | | **Tags** | Blacklist & nick alert settings, MDI icon rendering | | **Experiments** | Opt-in experimental implementations | ## Search Hit the search bar at the top. It indexes every entry from every tab, type a keyword like `cape`, `auto gg`, or `flag` and it'll find every related setting. ## Run a stats lookup ``` /seraph stats [mode] ``` Alias: `/ss`. Prints a stats summary in chat. Add a gamemode to focus the output (e.g. `/ss notch bedwars`). ## Add a player to your lobby manually If a friend just joined and isn't in tab yet: ``` /seraph add ``` Alias: `/sadd`. They'll show up in the overlay until you `/seraph clear` or leave the world. ## Other day-to-day commands | Command | What it does | | ----------------------------------------------- | ---------------------------------- | | `/seraph check ` (`/sc`) | Show blacklist status for a player | | `/seraph blacklist ...` | Add to blacklist | | `/seraph remap ` | Map a nick to a real player | | `/seraph apikey` (`/sapikey`) | Open Hypixel Developer Dashboard | See the [full command reference](/docs/features/commands) for everything. # Introduction Source: https://docs.seraph.si/docs/introduction A Minecraft 1.8.9 mod with a Hypixel stats overlay, anticheat, and a clean settings UI. Seraph Seraph is a Minecraft 1.8.9 mod focused on Hypixel. It replaces the tab list with a detailed stats overlay, flags suspicious players, tags known cheaters and threats, and bundles dozens of quality-of-life features into a clean in-game settings menu. It runs on **Lunar Client**, **Badlion Client**, and **Forge**, and shares a single config across all three. Install Seraph in a few minutes through the launcher. Join the community for support, announcements, and `/generate-key`. The tab-list replacement and everything you can configure. Every chat command Seraph registers. ## Highlights Live tab list with per-mode columns, sorting, and configurable colors. Flags scaffold and break-delay cheaters in real time, with optional auto-block. Surfaces known cheaters and threats with colored tags in tab and on nametags. Searchable settings menu for everything the mod does. ## Supported clients | Client | Version | How it installs | | ------------ | ------- | --------------------------- | | Lunar Client | 1.8.9 | Through the Seraph Launcher | | Badlion | 1.8.9 | Through the Seraph Launcher | | Forge | 1.8.9 | Through the Seraph Launcher | Your settings and saved nicknames carry over between clients automatically. # Introduction Source: https://docs.seraph.si/how-to-stay-safe/introduction ## Staying Safe with Seraph Security is a paramount concern when using third party modifications. To ensure your account and personal data remain protected, please adhere to the following safety protocols. *** ### Official Sources Only The most common vector for account compromise is the use of "cracked" or unofficial versions of software. * **Verify the Domain:** Always ensure you are downloading from `seraph.si`. * **Avoid Third Party Reuploads:** Never download the launcher from Discord attachments, file sharing sites (like MediaFire or Mega), or YouTube descriptions. * **Checksums:** Verify the SHA-256 hash of your download to ensure the file hasn't been tampered with. * **Certification:** On Windows, Right click the file, Click properties and Check the file has a valid signature. ### Protecting Your Authentication Token **Never share your `token.json` file.** If someone gains access to this file, they can impersonate your account on the Seraph backend. We will never ask you to send us this file for support. When you sign in via Discord, the launcher generates a `token.json` file. This token is a **credential** that allows the client to communicate with our servers as "you." ### Understanding Process Injection Seraph utilises process injection for clients like Lunar Client and Badlion Client (BLC). While this is a standard technique for sideloading mods, it can sometimes trigger "False Positives" in antivirus software. * **Heuristic Scanning:** Some antivirus tools may flag the launcher because it interacts with other running applications (Minecraft). * **Whitelisting:** If the official launcher is blocked, add an exception for `%AppData%\Seraph\` rather than disabling your firewall entirely. ### Social Safety Beyond technical security, be wary of "Social Engineering" within the community. 1. **Staff Identification:** Official staff members will have unique roles in the Discord. Anyone DMing you claiming to be "Support" and asking for files or passwords is a bad actor. 2. **Plugin Safety:** If you use Seraph alongside other Forge mods, ensure those mods are also from reputable sources (CurseForge, Modrinth). A malicious mod in your `mods` folder can read your Seraph configuration. # Official sources Source: https://docs.seraph.si/how-to-stay-safe/official-sources Scammers use similar domains to trick you in to giving your personal information, such as your Email Address or Password. ### Seraph Resources These links represent the only verified domains for Seraph software and documentation. * **Official Website:** [seraph.si](https://seraph.si) * **Documentation:** [docs.seraph.si](https://docs.seraph.si) * **Downloads:** [dl.seraph.si](https://dl.seraph.si) * **Sign in and device approval:** [auth.seraph.si](https://auth.seraph.si) * **Your account:** [seraph.si/account](https://seraph.si/account) * **Community Discord:** [discord.gg/seraph](https://discord.gg/seraph) Every launcher build lives under `dl.seraph.si/launcher/`, and the launcher only installs updates signed by Seraph's release key. A build offered to you anywhere else is not ours. See [Downloads & updates](/docs/getting-started/downloads). Seraph will never ask you for `token.json`, and never asks you to approve a device code that you did not start yourself. ### Hypixel Resources Official resources for the Hypixel Network and support. * **Hypixel Website:** [hypixel.net](https://hypixel.net) * **Hypixel Support:** [support.hypixel.net](https://support.hypixel.net) * **Server Address:** `mc.hypixel.net` ### Discord Resources Official resources for Discord * **Discord App:** [discord.com](https://discord.com) * **Discord Authorisation:** [discord.com/oauth2/authorize](https://discord.com/oauth2/authorize) # Privacy Policy Source: https://docs.seraph.si/legal/privacy How Seraph handles your data. Your privacy matters to us. Our full Privacy Policy covers the website, dashboard, and APIs. Read the complete, up-to-date Privacy Policy at **seraph.si/privacy**. We keep the official version of this policy at [seraph.si/privacy](https://seraph.si/privacy). If anything here ever disagrees with that page, the seraph.si version is the one that counts. *** ## What the mod collects The Seraph mod itself is intentionally minimal. It only ever sends **one piece of information**: * Your **Discord Snowflake ID**, used during the initial login from the launcher Nothing else is collected or transmitted. If you choose to use the **Player Cache** feature, the mod will fetch Hypixel player data from our backend. ## Crash reports For crash reports we use [Sentry](https://sentry.io). These reports only contain: * A **randomly generated ID** * The **stack trace** associated with the crash No personally identifiable information is intentionally collected through crash reports. # Terms of Service Source: https://docs.seraph.si/legal/terms The terms you agree to when you use Seraph When you use Seraph - the website, launcher, mod, stats dashboard, and our APIs and Discord integrations - our Terms of Service apply. Read the complete, up-to-date Terms of Service at **seraph.si/terms**. We keep the official version of the Terms at [seraph.si/terms](https://seraph.si/terms). If anything here ever disagrees with that page, the seraph.si version is the one that counts.