Query any game server.
Zero dependencies.
GameQuery reads player counts, map, hostname, ping and rules from one server or hundreds at once. It ships as two parallel libraries that stay in lockstep — PHP and Node/TypeScript — with the same protocols, the same API, and the same results.
Try it on a real server
Pick a game, paste an address, and this page runs the actual library against it — same code you'd install.
Built for real fleets
Everything here exists because a production Discord bot polls thousands of servers with it.
⚡ Concurrent by design
One event loop drives every query at once, so polling 25 servers takes about as long as the single slowest one — not the sum of all of them.
🛡️ Never throws when offline
Every query returns a Result. Unreachable servers come back with online = false and a typed ErrorCode — no try/catch gymnastics.
🔗 Two ports, one behaviour
The PHP and Node libraries change together in the same commit, with matching tests and an automated parity check that fails CI on drift.
🎯 Normalized results
name(), map(), players(), maxPlayers(), playerNames() read the right field regardless of protocol, and stay stable across releases.
🌊 Streaming results
processStream() yields each result the moment its server answers — an async iterator in Node, a Generator in PHP.
🧪 Fuzzed parsers
Every parser is thrown 132,000 malformed buffers in CI. A hostile or broken server reply cannot crash your process.
Quick start
Install, then query. Both ports expose the same surface.
composer require t0xicvybez/gamequery
npm install @t0xicvybez/gamequery
import { GameQuery } from '@t0xicvybez/gamequery';
// One server, by protocol
const r = await GameQuery.queryOne('source', '1.2.3.4:27015');
console.log(r.online, r.name(), r.players(), r.maxPlayers());
// Or by game id — protocol + port resolved for you
const rust = await GameQuery.queryGame('rust', 'my-server.com');
// A whole fleet, concurrently
const results = await new GameQuery(2000, 1)
.addGame('cs2', 'a.example.com')
.addGame('minecraft', 'b.example.com')
.addServer('source', 'c.example.com:28015')
.process();
for (const s of results) {
console.log(s.server.label(), s.online ? s.players() : s.errorCode);
}
use GameQuery\GameQuery;
// One server, by protocol
$r = GameQuery::queryOne('source', '1.2.3.4:27015');
var_dump($r->online, $r->name(), $r->players(), $r->maxPlayers());
// Or by game id — protocol + port resolved for you
$rust = GameQuery::queryGame('rust', 'my-server.com');
// A whole fleet, concurrently
$results = (new GameQuery(2000, 1))
->addGame('cs2', 'a.example.com')
->addGame('minecraft', 'b.example.com')
->addServer('source', 'c.example.com:28015')
->process();
foreach ($results as $s) {
echo $s->server->label(), ' ', $s->online ? $s->players() : $s->errorCode->value, PHP_EOL;
}
27015 but joined on
7777. The game database exposes both, so you can show a correct connect
address without ever pointing a query at it.
Protocol coverage
23 families behind 33 registered keys. Pass a key to addServer(), or skip it entirely and use a game id.
| Key | Family / games | Transport |
|---|---|---|
source (source-players, source-full) | Source / A2S — CS2, TF2, Rust, ARK, GMod, SCUM, most Steamworks games | UDP |
minecraft (minecraft-ping) | Minecraft: Java Edition SLP (-ping adds 0x01 ping latency) | TCP |
minecraft-legacy | Minecraft: Java Edition (≤1.6 legacy ping) | TCP |
minecraft-query | Minecraft: Java enable-query (full player list) | UDP |
bedrock (minecraft-bedrock) | Minecraft: Bedrock Edition (RakNet) | UDP |
fivem (fivem-info) | FiveM / CFX (GTA V multiplayer) | TCP/HTTP |
palworld (palworld-info) | Palworld REST API | TCP/HTTP |
quakeworld (quake1) | QuakeWorld / Quake 1 | UDP |
quake2 | id Tech 2 / Quake 2 | UDP |
quake3 | id Tech 3 — Quake 3, CoD 1/2/4, OpenArena, Xonotic, ET | UDP |
gamespy1 | GameSpy 1 — Unreal, early UT, Tribes 2, older titles | UDP |
gamespy2 | GameSpy 2 — Battlefield 1942/Vietnam, Halo, UT2004 | UDP |
gamespy3 | GameSpy 3 — Battlefield 2, Crysis, UT3, later titles | UDP |
unreal2 (unreal2-info) | Unreal Engine 2 — UT2003/2004, Killing Floor | UDP |
doom3 | id Tech 4 — Doom 3, Quake 4, ET: Quake Wars, Prey | UDP |
ase | All-Seeing Eye — Multi Theft Auto (MTA:SA) | UDP |
mumble | Mumble / Murmur voice servers | UDP |
teamspeak3 | TeamSpeak 3 / TeaSpeak (ServerQuery) | TCP |
frostbite | Battlefield 3/4, Bad Company 2, Medal of Honor | TCP |
assettocorsa | Assetto Corsa (HTTP /INFO) | TCP/HTTP |
terraria | Terraria via TShock REST | TCP/HTTP |
samp (openmp, samp-info) | SA-MP / open.mp (GTA: San Andreas) | UDP |
satisfactory | Satisfactory Lightweight Query (name/state/build) | UDP |
Game database
53 games map straight to a protocol and port, so queryGame('rust', host) just works. Amber marks a different join port.