Events, properties & funnels
Events say what happened. Properties say how servers are set up. Funnels and retention are built from both, automatically.
Events
Call track wherever something meaningful happens. It's cheap enough for hot paths: it only increments a counter.
analytics.track("arena_created"); // the server did something
analytics.track("game_started", player); // a player did something
analytics.track("kit_selected", player, "kit", "archer"); // with properties
analytics.track("reload", "trigger", "command"); // server event with properties
Commands are counted for you: /arena join becomes the event command:arena.join. Subcommands only count when your command's tab completion offers them, so typed names and free text never become events.
Server or player?
Pass the player whenever a person did it. Player events power player funnels and the reach column (the share of players that ever did it). Server events describe what admins do: setup, reloads, configuration.
Naming rules
- Lowercase letters, digits and
_ . : -, up to 48 characters. Invalid names are ignored. - Use
object_verbin the past tense:game_started,shop_opened. - Up to 5 properties per event; values are cut at 48 characters. Keep values low-cardinality (a mode, not a player name).
- Up to 250 different event names per plugin.
Config usage (automatic)
Your plugin's config.yml is read automatically once an hour and shown in Config usage: for every option, the most common value, its default, and how many servers changed it. An option most servers change is a sign its default is wrong; one nobody changes may not need to exist.
Only plain choices are sent: true/false, numbers and short words like sqlite. Lists, texts, messages and anything that looks like a URL, IP, email or ID are skipped, and so is every key whose name suggests something private (password, token, key, host, user, URL, webhook, database, port, world, name…).
analytics.excludeConfig("discord", "storage.mysql"); // leave paths out
analytics.trackConfig(false); // don't read config.yml at all
Values that aren't in config.yml
Report anything else about how a server is set up with a property. It's read once an hour from the analytics thread and shown next to the config options.
analytics.property("economy", () -> vaultHooked ? "vault" : "none");
Suppliers run off the main thread: only read values that are safe to read asynchronously.
Funnels
Every first time a player (or server) does an event is remembered. A funnel counts how many of those who did step 1 went on to do step 2, then step 3, in order, within a window you choose (1 to 90 days).
- Player funnels start from any player event, or from
Player joined, which the SDK tracks for you. - Server funnels start from
Plugin installedor any server event. Example: installed →arena_createdshows how many servers finish setting up. - The dashboard highlights the step with the biggest drop: start there.
Retention
Servers are grouped by the week (or day) they installed your plugin. Each cell shows the share still running it in each following period. It needs no code.
Errors
Exceptions whose stack trace goes through your plugin's package are caught automatically, from both logging systems Spigot and Paper use. They're grouped by type and the frames of your code, so the same bug on a thousand servers is one issue. You can also report errors you catch yourself:
try {
database.save(arena);
} catch (SQLException e) {
analytics.captureError(e);
getLogger().warning("Couldn't save the arena: " + e.getMessage());
}
Player names, IP addresses and UUIDs are replaced in messages and stack traces before anything is sent. Mark issues as resolved: if they come back, they're flagged as regressed.
Performance
Your event handlers and commands are timed automatically on every server (two clock reads per call, nothing more). The dashboard shows how much of each 50 ms tick your plugin uses, the most expensive code first, with average, p95 and max, and compares versions so a slow release stands out. Time anything else yourself:
analytics.time("load_arenas", () -> loadArenas());
try (PluginAnalytics.Span span = analytics.time("arena_tick")) {
tickArenas();
}
analytics.autoPerformance(false); // don't time handlers and commands
On Paper and its forks, the server's average tick time (MSPT) is reported too, for context. On Folia, handlers aren't timed automatically; spans still work.
Logs
Lines your plugin writes with getLogger() are collected from every server and merged by message: numbers become #, so “Loaded 14 arenas” and “Loaded 9 arenas” are one line with a count. Search them and filter by level, plugin version and platform in the dashboard. Player names, IPs and UUIDs are replaced before sending.
analytics.logLevel(Level.WARNING); // only warnings and errors (default: INFO)
analytics.logLevel(Level.OFF); // collect no log lines
Update notices
In Updates, set the latest version (or read it from your SpigotMC or Modrinth listing), a download link and the message. Servers on an older version see it in the console, and admins see it in chat when they join (operators, or anyone with <yourplugin>.notify). Versions below a “critical” version get a second, stronger message. It travels in the reply to the hourly report: no extra requests.
analytics.updateNotices(false); // never show update notices
analytics.noticePermission("arena.admin"); // who sees notices on join
Feedback
Turn on the prompt in Feedback: after a few days, admins get your question with a link to a short form (a 1–5 rating and a comment). You can also send feedback from your own command:
// /arena feedback 5 Great plugin!
analytics.feedback(sender, rating, message); // rating 1–5, or 0 for none
analytics.feedbackPrompts(false); // never show the prompt
Server owners can turn off every notice and prompt with notify-admins: false in plugins/PluginAnalytics/config.yml.
API reference
| Method | What it does |
|---|---|
PluginAnalytics.start(plugin, key) | Starts collecting. Never throws, never blocks. |
track(event[, player][, key, value…]) | Counts an event. Also track(event, player, Map). |
property(name, supplier) | Reports a per-server value once an hour. |
trackConfig(bool) · excludeConfig(paths…) | Turn off or narrow the automatic config.yml reading. |
captureError(throwable) | Reports an exception you handled. |
time(name) · time(name, runnable) | Times a piece of your code for Performance. |
logLevel(level) | Lowest level of your log lines that is collected. |
feedback(sender, rating, message) | Sends feedback to you from your own command. |
updateNotices(bool) · feedbackPrompts(bool) | Turn the dashboard's notices on or off in code. |
isEnabled() | False if the server owner turned analytics off. |
stop() | Stops collecting. Called for you on disable. |