TypeScript client for Rocket League's official Stats API (MatchStatsExporter_TA). Connects to the local TCP socket Rocket League opens during matches, frames the raw JSON stream, decodes the envelope, and emits fully typed events.
Heads-up: the Stats API is a raw TCP socket that streams concatenated JSON — not a WebSocket — and
Datais a JSON-encoded string, not a nested object. Both points are easy to miss; this package handles them for you.
npm install rocket-league-stats-api
# or
bun add rocket-league-stats-apiRequires Node.js 20+ or Bun. Browser environments are not supported (no raw TCP).
The exporter is off by default. Edit (or create) this file before launching Rocket League:
<Rocket League install>\TAGame\Config\DefaultStatsAPI.ini
Paste the following and save:
[TAGame.MatchStatsExporter_TA]
Port=49123
PacketSendRate=10PacketSendRateis updates per second (1–120).0disables the API.- The section header
[TAGame.MatchStatsExporter_TA]is required. - Restart Rocket League after editing the file.
- Events only stream while a match is active — saved-replay viewing does not produce data.
import { RocketLeagueStatsClient } from "rocket-league-stats-api";
const client = new RocketLeagueStatsClient({
host: "127.0.0.1", // default
port: 49123, // default; matches DefaultStatsAPI.ini Port
});
client.on("connected", () => console.log("Connected"));
client.on("disconnected", ({ reason }) => console.log(`Disconnected (${reason})`));
client.on("error", (err) => console.error(err));
client.on("UpdateState", (data) => {
console.log(`${data.Players.length} players, clock ${data.Game.TimeSeconds}s`);
});
client.on("GoalScored", (data) => {
console.log(`Goal by ${data.Scorer?.Name} (speed ${data.GoalSpeed})`);
});
await client.connect();Options:
| Option | Type | Default | Description |
|---|---|---|---|
host |
string |
"127.0.0.1" |
Host where Rocket League is listening. |
port |
number |
49123 |
Port from DefaultStatsAPI.ini's Port= directive. |
connectTimeoutMs |
number |
5000 |
Initial-connect timeout. After timeout, connect() rejects. |
autoReconnect |
boolean |
false |
If true, reconnects with exponential backoff after the socket closes. |
reconnectDelayMs |
number |
1500 |
Initial reconnect delay (only used when autoReconnect: true). |
maxReconnectDelayMs |
number |
30000 |
Cap on reconnect backoff. |
Opens the socket. Resolves once connect fires, or rejects on socket error / timeout.
Closes the socket and cancels any pending reconnect.
Subscribe to game or lifecycle events. Listeners are typed: client.on("UpdateState", data => ...) infers data as UpdateStateData.
Every event documented by Psyonix is forwarded with its parsed payload:
UpdateState, GoalScored, MatchEnded, StatfeedEvent, BallHit, ClockUpdatedSeconds, MatchCreated, MatchInitialized, MatchDestroyed, CountdownBegin, RoundStarted, GoalReplayStart, GoalReplayWillEnd, GoalReplayEnd, PodiumStart, MatchPaused, MatchUnpaused, CrossbarHit, ReplayCreated.
| Event | Payload |
|---|---|
connected |
void |
disconnected |
{ reason: "closed" | "ended" | "error" | "manual"; error?: Error } |
error |
Error |
parseError |
{ error: Error; frame: string } |
message |
{ event: StatsApiEventName; data: unknown } — fires for every message |
All types are exported from the package root, including Player, Team, GameState, UpdateStateData, GoalScoredData, StatfeedEventData, etc. The full event-name → payload-type mapping is StatsApiEventMap.
If you want to plug the framer or the envelope decoder into your own transport (e.g., piping captured fixtures back through the same parsing pipeline), import them directly:
import { JsonFrameBuffer, decodeEnvelope } from "rocket-league-stats-api";
const buf = new JsonFrameBuffer();
buf.push(socketChunk);
for (const raw of buf.drain()) {
const decoded = decodeEnvelope(raw);
// { event, data }
}- Bot matches collapse
PrimaryId. Every bot in a private match reportsPrimaryId: "Unknown|0|0". If you key your own player map byPrimaryIdalone, every bot collapses into one entry. Use(TeamNum, Shortcut, Name)as a fallback. - Boost is spectator-scoped. The
Boostfield onPlayeronly appears when the client is spectating or on that player's team. Treat it as optional and render--when absent rather than assuming0. - Saved-replay viewing emits nothing. The exporter only streams during live matches, online or private.
- No position data. The Stats API does not document continuous player or ball positions, so minimaps and heatmaps are not implementable from this feed alone.
The examples/ folder has runnable scripts. Run the live capture against your local Rocket League:
bun run example:capture
# or
npx tsx examples/capture.tsbun install
bun test
bun run typecheck
bun run buildThe framer and envelope decoder are pure-function modules with full unit-test coverage; the client has integration-style tests against a local TCP mock.
This package is not affiliated with, endorsed by, or sponsored by Psyonix, LLC or Epic Games, Inc. Rocket League is a registered trademark of Psyonix, LLC. All event names and field shapes match the public Stats API spec at the time of writing — Psyonix may add, remove, or rename fields without notice.