Turn Counter-Strike 2 demos and live broadcasts into typed match data.
cs2parser parses CS2 .dem files and live HTTP GOTV broadcasts for match analysis, replay viewers, and broadcast tooling. Read typed game events, inspect players and entities, or subscribe to low-level network messages in Node.js, Bun, and modern browsers.
| Match data | Playback and inputs | Integration |
|---|---|---|
| Players, teams, and game rules | Demo files, byte buffers, and streams | Typed game and parser events |
| Kills, rounds, and bomb events | Pause, seek, and resume on seekable inputs | Node.js, Bun, and browser exports |
| Entity state and smoke simulation | Live HTTP GOTV broadcasts | Embedded WASM; no production native addons |
CS2 demo or GOTV relay ──► DemoReader ──► typed events + match state
Before you start: choose
EntityMode.ALLfor player helpers and full entity state. The default,EntityMode.NONE, skips entities. Use a newDemoReaderfor each demo or broadcast.
Requires Node.js 22 or newer, Bun, or a modern browser with WebAssembly support. The package uses ES modules and includes TypeScript declarations. Both exports embed the WASM Snappy decoder, so there are no separate WASM assets to serve.
npm install cs2parserSave as parse-demo.mjs:
import { DemoReader, EntityMode } from 'cs2parser';
const parser = new DemoReader();
parser.gameEvents.on('player_death', event => {
const attacker = event.attackerPlayer;
const victim = event.player;
if (attacker && victim) {
console.log(`${attacker.name} killed ${victim.name} with ${event.weapon}`);
}
});
try {
const outcome = await parser.parseDemo(process.argv[2] ?? 'demo.dem', {
entities: EntityMode.ALL
});
console.log('Parsing finished:', outcome.status);
for (const player of parser.playerControllers) {
console.log(player.name, player.kills, player.deaths, player.position);
}
} catch (error) {
console.error('Parsing failed:', error);
process.exitCode = 1;
}Pass the path to a CS2 demo:
node parse-demo.mjs path/to/demo.demFile paths are read on demand. You can also pass a Node stream, File / Blob, Uint8Array / Buffer, or web stream. The returned status is complete, incomplete, or cancelled; parsing failures reject the promise.
Listeners run synchronously; promises returned by async listeners are not awaited. Player helpers expose live state, so copy the values you need when recording history. See Parsing demos for input types, completion handling, and cancellation.
| Mode | Available data | Use it for |
|---|---|---|
EntityMode.NONE (default) |
Messages, game events, and basic player info; no entities | Event logs and rosters without full entity parsing |
EntityMode.ONLY_GAME_RULES |
Game rules and derived round events | Round tracking without player entity state |
EntityMode.ALL |
Full entity state, player helpers, teams, and smokes | Player statistics, positions, and replay analysis |
event.player and event.attackerPlayer require EntityMode.ALL. With NONE, use parser.players for basic names and Steam IDs. See Players and pawns for lookups and Game and parser events for round-event behavior.
Import from cs2parser/browser in your browser application:
import { DemoReader, EntityMode } from 'cs2parser/browser';
// file is a File from an <input type="file"> or drag-and-drop.
const parser = new DemoReader();
parser.gameEvents.on('player_death', event => console.log(event.weapon));
parser.on('progress', bytesParsed => console.log(bytesParsed / file.size));
const outcome = await parser.parseDemo(file, { entities: EntityMode.ALL });
console.log(outcome.status);Pass a File directly to enable pause, seek, and resume, or a web stream for sequential parsing. See Browser usage for fetch examples, runtime requirements, and WASM details.
Connect to a CS2 GOTV HTTP relay and use the same events as demo parsing:
import { DemoReader, EntityMode } from 'cs2parser';
const parser = new DemoReader();
parser.gameEvents.on('round_end', event => {
console.log('Round winner:', event.winner);
});
const outcome = await parser.parseHttpBroadcast('https://relay.example.com/match-id/', {
entities: EntityMode.ALL
});
console.log(outcome.status); // 'complete', 'timeout', or 'cancelled'Replace the example URL with your relay address. See Live HTTP broadcasts for relay options, mid-stream joins, and cancellation.
import { DemoReader } from 'cs2parser';
const header = await DemoReader.parseHeaderAsync('demo.dem');
console.log(header?.map_name, header?.server_name);Metadata helpers read only the relevant parts of the demo. The browser export accepts bytes or a File / Blob instead of a filesystem path. See Reading metadata for server info, file info, and synchronous alternatives.
| Read | What you will find |
|---|---|
| Parsing demos | Inputs, entity modes, settings, reader state, and cancellation |
| Pausing and seeking | Tick-boundary playback controls, FullPackets, and seekable sources |
| Reading metadata | Header, server info, and file info without a full parse |
| Browser usage | Files, web streams, metadata, and WASM Snappy |
| Live HTTP broadcasts | GOTV relays, options, descriptors, and wire format |
| Players and pawns | Rosters, lookups, bot analysis, and helper properties |
| Entities and match state | Teams, game rules, and typed entity access |
| Smokes | Seed voxels, density simulation, and visual comparison |
| Game and parser events | Typed game events, equipment and grenade lifecycles, parser events |
| Network messages | Subscriptions, user commands, and message discovery |
| Encrypted demo chat | Match keys, public chat decryption, and browser support |
| Development | Entity types, protobuf bindings, and generated registries |
| Performance | Benchmark commands, methodology, and custom cases |
| Examples | Runnable scripts and command-line usage |
| Changelog | Released changes |
Upgrading from 1.x? See the migration guide.
With Node.js and Bun installed:
npm ci
npm run typecheck
npm test
npm run buildSee Development for code generation and Performance for benchmarks.
This library builds on the work of:
- LaihoE, creator of demoparser
- Saul, creator of demofile-net
- markus-wa, creator of demoinfocs-golang
Huge thanks to all of them for their help over the years.