# 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 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.