ServerOps.ggbeta
Server OwnersMinecraft (Paper)

Minecraft (Paper)

Integrate ServerOps with your Paper Minecraft server for logging and media uploads.

Minecraft (Paper): Getting started

This guide walks you through creating a simple Paper plugin that sends player events to ServerOps. You will need basic Java knowledge, but no advanced programming experience.

Before you start

  • Complete Getting your API token. You need logs:write and/or media:write scope.
  • Your server must be running Paper 1.20+ (Java 21 required).
  • You will need a way to compile a Java plugin (IntelliJ IDEA Community is free and works well).

Plugin setup

1. Create a new Paper plugin project

The quickest way is to use the Paper plugin generator. Fill in:

  • Group: your domain reversed, e.g. gg.serverops
  • Artifact: ServerOpsPlugin
  • Paper version: select the latest

Download the generated project and open it in IntelliJ IDEA.

2. Add your token to config.yml

Open src/main/resources/config.yml and add:

serverops:
  api-token: "so_live_..."

3. Create the API client class

Create src/main/java/gg/serverops/plugin/ServerOpsClient.java:

package gg.serverops.plugin;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Map;
import com.google.gson.Gson;
import org.bukkit.plugin.java.JavaPlugin;

public class ServerOpsClient {
    private final HttpClient httpClient = HttpClient.newHttpClient();
    private final Gson gson = new Gson();
    private final JavaPlugin plugin;

    public ServerOpsClient(JavaPlugin plugin) {
        this.plugin = plugin;
    }

    private String getToken() {
        return plugin.getConfig().getString("serverops.api-token", "");
    }

    public void sendLog(String level, String message, Map<String, Object> meta) {
        String token = getToken();
        if (token.isEmpty()) {
            plugin.getLogger().warning("[ServerOps] api-token is not set in config.yml");
            return;
        }

        String body = gson.toJson(Map.of(
            "level", level,
            "message", message,
            "meta", meta != null ? meta : Map.of()
        ));

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://api.serverops.gg/v1/logs"))
            .header("Authorization", "Bearer " + token)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenAccept(response -> {
                if (response.statusCode() != 201) {
                    plugin.getLogger().warning(
                        "[ServerOps] Log failed (" + response.statusCode() + "): " + response.body()
                    );
                }
            });
    }
}

4. Wire up your main plugin class

Open your main plugin class (the one that extends JavaPlugin) and update it:

package gg.serverops.plugin;

import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Map;

public class ServerOpsPlugin extends JavaPlugin implements Listener {
    private ServerOpsClient client;

    @Override
    public void onEnable() {
        saveDefaultConfig();
        client = new ServerOpsClient(this);
        getServer().getPluginManager().registerEvents(this, this);
        getLogger().info("ServerOps integration enabled.");
    }

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        var player = event.getPlayer();
        client.sendLog("info", "Player joined", Map.of(
            "name", player.getName(),
            "uuid", player.getUniqueId().toString(),
            "address", player.getAddress() != null ? player.getAddress().getHostString() : "unknown"
        ));
    }

    @EventHandler
    public void onPlayerQuit(PlayerQuitEvent event) {
        var player = event.getPlayer();
        client.sendLog("info", "Player left", Map.of(
            "name", player.getName(),
            "uuid", player.getUniqueId().toString()
        ));
    }
}

5. Build and install

Run ./gradlew build (or mvn package if using Maven). Copy the generated .jar from build/libs/ into your server's plugins/ folder. Restart the server.

Edit plugins/ServerOpsPlugin/config.yml to set your API token:

serverops:
  api-token: "so_live_..."

Reload the config with /reload confirm or restart the server.

Adding ban logs

Hook into the PlayerKickEvent or use a ban plugin's API to log bans:

@EventHandler
public void onPlayerKick(PlayerKickEvent event) {
    client.sendLog("warn", "Player kicked", Map.of(
        "name", event.getPlayer().getName(),
        "reason", event.reason().toString()
    ));
}

Viewing your logs

Logs appear in your ServerOps dashboard as they arrive.

Troubleshooting

"api-token is not set": Open plugins/ServerOpsPlugin/config.yml and add your token under serverops.api-token.

401 Unauthorized: Your token is invalid or missing the logs:write scope. Generate a new token from the dashboard.

Plugin does not load: Check your Paper version. This plugin requires Paper 1.20+ with Java 21. Run java -version on your server to verify.

On this page