For servers

Fern Server Link

Make your server feel at home in Fern: recognize Fern players, keep matches fair and talk to players, with a few small JSON messages over plugin channels.

Know who plays Fern

A tiny handshake tells your plugin which players use Fern, only when they allow it.

Fair matches

Switch off and lock specific Fern features on your server, with a reason players can read.

Notifications

Short, rate-limited toasts for announcements, rewards or game events.

On-screen timers

Countdowns at the top of the screen for game starts, events and cooldowns.

Status: protocol 1, client support in Fern 0.1. A ready-made Paper plugin is planned; until then you can talk to Fern directly with plugin messages (examples below).

How it works

Fern listens on a few plugin-message channels that all start with fern:. Every message is a small UTF-8 JSON object (max 8 KB).

The server always starts the conversation: Fern never announces itself on servers that don't ask.

text
Player joins
   server ──► fern:welcome   {"protocol": 1}
   client ──► fern:hello     {"client": "fern", "version": "0.1.0", ...}   (only if the player allows it)
   server ──► fern:rules / fern:notify / fern:timer   (any time)
Player leaves → everything your server set is undone on the client

Players who don't use Fern simply ignore these channels — sending them is always safe.

Channels

ChannelDirectionWhat it does
fern:welcomeserver → client"This server supports Fern Server Link." Starts the handshake.
fern:helloclient → serverFern's answer: version, mod loader, enabled Fern mods.
fern:rulesserver → clientTurn off and lock Fern mods on your server.
fern:notifyserver → clientShow a notification (toast).
fern:timerserver → clientShow, update or remove a countdown at the top of the screen.
fern:waypointserver → clientPlanned: markers in the world.
fern:presenceserver → clientPlanned: what the player's Discord status says while on your server.

fern:welcome

json
{ "protocol": 1 }

Send it once, shortly after the player joined (for example 1 tick after PlayerJoinEvent, so the client has registered its channels).

fern:hello (from the player)

json
{
  "protocol": 1,
  "client": "fern",
  "version": "0.1.0",
  "loader": "fabric",
  "minecraft": "26.3",
  "modules": ["fps", "zoom", "keystrokes"]
}

Players can turn this off (Features → Server Features → Tell servers I use Fern). If you never receive a hello, treat the player as a normal player.

fern:rules

json
{ "disable": ["zoom", "fullbright"], "reason": "Not allowed in ranked matches" }
  • The listed mods are switched off and locked until the player leaves your server. The player sees the reason when they try to turn one back on.
  • Rules only restrict. You can't turn mods on, change settings or unlock anything.
  • Rules apply even when the player turned *Server Features* off — fair play comes first.
  • Send fern:rules again to lock more mods. Max 64 ids per message; unknown ids are ignored.

fern:notify

json
{ "title": "Welcome to Mossy Network", "body": "Season 3 starts today!" }
  • title max 48 characters, body max 160. Formatting codes (§) are removed.
  • Max 5 notifications per 10 seconds per player; extra ones are dropped. Players can turn notifications off.

fern:timer

json
{ "id": "start", "label": "Game starts in", "seconds": 10, "color": "#4ADE80" }
  • Shows a countdown at the top centre of the screen, e.g. Game starts in 10s.
  • Same id again = update it. "seconds": 0 = remove it. Max 3 timers, max 3600 seconds.
  • color is optional (hex). Players can turn timers off.

Module ids

IdFern modIdFern mod
fpsFPSzoomZoom
pingPingtoggle_sprintToggle Sprint
coordinatesCoordinatestoggle_sneakToggle Sneak
clockClockfullbrightFullbright
keystrokesKeystrokescrosshairCustom Crosshair
cpsCPSarmor_statusArmor Status
potion_effectsPotion EffectsdirectionDirection
memoryMemory

New Fern mods get new ids; unknown ids are always ignored, so your rules keep working.

Example: Paper plugin

java
public final class FernLinkExample extends JavaPlugin implements Listener, PluginMessageListener {
    @Override
    public void onEnable() {
        var messenger = getServer().getMessenger();
        for (String ch : List.of("fern:welcome", "fern:rules", "fern:notify", "fern:timer")) {
            messenger.registerOutgoingPluginChannel(this, ch);
        }
        messenger.registerIncomingPluginChannel(this, "fern:hello", this);
        getServer().getPluginManager().registerEvents(this, this);
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        // One tick later, so the client has told the server which channels it listens on.
        getServer().getScheduler().runTaskLater(this, () -> send(player, "fern:welcome", "{\"protocol\":1}"), 1L);
    }

    @Override
    public void onPluginMessageReceived(String channel, Player player, byte[] message) {
        // Read and validate the JSON before trusting it — it comes from the player.
        String hello = readString(message);
        getLogger().info(player.getName() + " uses Fern: " + hello);

        send(player, "fern:rules", "{\"disable\":[\"zoom\"],\"reason\":\"No zoom in ranked\"}");
        send(player, "fern:notify", "{\"title\":\"Fern detected\",\"body\":\"Have fun!\"}");
    }

    private void send(Player player, String channel, String json) {
        player.sendPluginMessage(this, channel, writeString(json));
    }

    // Minecraft strings: VarInt length + UTF-8 bytes.
    private static byte[] writeString(String s) {
        byte[] utf8 = s.getBytes(StandardCharsets.UTF_8);
        var out = new ByteArrayOutputStream();
        int len = utf8.length;
        while ((len & ~0x7F) != 0) { out.write((len & 0x7F) | 0x80); len >>>= 7; }
        out.write(len);
        out.writeBytes(utf8);
        return out.toByteArray();
    }

    private static String readString(byte[] data) {
        int len = 0, shift = 0, i = 0, b;
        do { b = data[i++]; len |= (b & 0x7F) << shift; shift += 7; } while ((b & 0x80) != 0 && shift < 35);
        return new String(data, i, Math.min(len, data.length - i), StandardCharsets.UTF_8);
    }
}

Rules for servers

  • Don't trust `fern:hello` for security decisions. Any client can claim to be Fern. Use it for comfort features only, never for anti-cheat or permissions.
  • Use rules for fair play, not to punish Fern players. Fern players should never be at a disadvantage compared to vanilla players.
  • Keep notifications useful and rare; players who find them spammy will turn them off.
  • Respect the Minecraft EULA and Usage Guidelines.

Privacy

fern:hello only contains the Fern version, mod loader, Minecraft version and enabled Fern mod ids — no account data, hardware or files. Players can turn it off. Servers receive nothing from Fern unless they send fern:welcome first.

Changelog

ProtocolFernChanges
10.1welcome, hello, rules, notify, timer