SDK for plugin authors

track, trackFrom, count, gauge, setPlayerProp and variant in Java, Lua, C# and Luau, plus naming rules and plugin manifests.

For plugin authorsUpdated

Plugins and scripts on a server that runs a ServerStats agent can send their own events and metrics. Calls are no-ops when ServerStats is not installed, so your plugin runs either way. The agent validates every value, signs the batch and ingests it under custom.<name>; the dashboard then charts it under Build, Custom events.

Calls

CallMeaning
track(event, player, props)Something happened (to a player or server-wide). One event.
trackFrom(pluginName, event, player, props)The same, recorded under your plugin's name so owners can chart it by source (plugin:<lowercase-name>)
count(name, n, dims)A number that only goes up, summed per 10-second window
gauge(name, value, dims)A level; the last value in each 10-second window is sent
observe(name, value, dims)A histogram observation (Lua, C# and Luau SDKs)
setPlayerProp(player, key, value)A stable fact about a player (faction, VIP tier), usable as a segment filter; nil removes it
variant(player, key, fallback)The A/B experiment variant for this player (experiments)

By game

GameTrackVariant
Paper, Purpur, Folia, Velocity, Fabric, NeoForge, Forge, Hytale (Java)ServerStats.get().track("crate_open", player, Map.of("crate", "legendary")); trackFrom("CratesPlus", "crate_open", player, props)ServerStats.get().variant(player, "key", "control")
FiveM, RedMexports.serverstats:track('heist_complete', source, { payout = 42000 }) (records the calling resource as resource:<name>)exports.serverstats:variant(source, "key", fallback)
Rust (Oxide, Carbon)ServerStats?.Call("Track", "crate_open", player, new Dictionary<string, object> { ["cost"] = 3 })ServerStats?.Call<string>("Variant", player, "key", fallback)
Garry's ModServerStats.Track("heist_complete", ply, { payout = 42000 })ServerStats.Variant(ply, "key", fallback)
RobloxSS.track("boss_defeated", player, { boss = "hydra" })SS.variant(player, "key", fallback)

Java gets the API from the cc.serverstats.api.ServerStats interface: add the ServerStats jar as a provided or compileOnly dependency and softdepend: [ServerStats] in plugin.yml. Each agent's README has the full list for its game.

Rules

  • Names are lowercase snake_case, start with a letter, up to 96 characters, and must not start with ss_. A plain name is stored as custom.<name>. A name that is a built-in catalogue key (for any of your games) feeds the built-in instead, so prefer specific names such as bosses_killed_by_party over kills.
  • Properties are flat. At most the first 20 keys are read; keys up to 64 characters; values are strings up to 64 characters, finite numbers or booleans. Nested objects and arrays are dropped. If the kept properties exceed 4,096 bytes the event is dropped.
  • No personal data. Keys such as name, ip, email, message, uuid and steamid, and values shaped like emails, IPs, UUIDs or SteamIDs, are dropped. Pass the native player object instead of an id: the agent hashes it, and ingest turns it into a keyed pseudonymous id.
  • A second player goes in pid2 (Lua, C# and Luau SDKs) and is stored as the counterparty.
  • Rates. track is limited to 100 a second with a burst of 1,000 in the FiveM, Rust and Garry's Mod agents (check the README of yours); beyond that events are sampled. Call track once, where the action succeeds. Use count or gauge for recurring numbers instead of an event per sample.
  • Limits per organisation: up to 500 custom metrics.

Player properties have at most 50 keys per player, keys ^[a-z][a-z0-9_]{0,63}$, and string values are stored encrypted.

Experiments

variant assigns deterministically in the agent, the same on every platform:

text
key     = lowercase(experimentId) + ":" + lowercase(playerId)
bucket  = fmix32(fnv1a32(utf8(key))) mod 10000
point   = floor(bucket * totalWeight / 10000)
variant = the first variant whose cumulative weight is greater than point

The first call per player records an exp_exposure event (stored as custom.exp_exposure, with exp and variant properties). Shared test vectors pin the algorithm so every platform assigns the same variant.

Plugin manifests

Declare your plugin's events and metrics once, so server owners see typed fields and can chart before data arrives. The owner (or you, from a script with a session) calls PUT /orgs/<org>/plugin-manifests/plugin:yourname with:

json
{"schema_version":1,"label":"CratesPlus","version":"1.4.0","description":"Crate openings",
 "events":[{"type":"custom.crate_open","label":"Crate opened",
            "properties":[{"key":"crate","type":"string"},{"key":"cost","type":"number","unit":"coins"}]}],
 "metrics":[{"key":"custom.open_crates","label":"Open crates","kind":"gauge"}]}

source is plugin:, resource: or script: plus 1 to 32 lowercase characters, matching what trackFrom records. Limits: 24 events and 24 metrics per manifest, 12 properties per event, 16 KB, 50 manifests per organisation. The status route compares a manifest with the last 500 events from that source and reports unexpected properties and type mismatches; it never returns values or player ids. A manifest is metadata: it does not make anything send data.