Cookbook

Small examples for common tasks. Every ts server and ts client block on this page is type-checked against sdk/types by the tools test suite (tools/dev/dev.test.cjs). A broken example fails the build, not a reader.

Type-checked is not the same as played. None of these examples has been run in a live GTA V session as part of this documentation. Behaviour inside the game is covered by the owner's live certification, which is a separate step.

The full API reference is docs/api/server.md, docs/api/client.md and docs/api/csharp.md. You can also search everything at once with node tools/nextmp.cjs docs.

A basic server package

packages/gamemode/index.js (or .ts compiled to dist/index.js, see getting started):

mp.events.add('playerJoin', (player) => {
    player.outputChatBox(`Welcome, ${player.name}`);
    mp.players.broadcast(`${player.name} joined (${mp.players.length} online)`);
});

mp.events.add('playerQuit', (player) => {
    mp.players.broadcast(`${player.name} left`);
});

Commands

mp.events.addCommand('pos', (player) => {
    const { x, y, z } = player.position;
    player.outputChatBox(`${x.toFixed(1)} ${y.toFixed(1)} ${z.toFixed(1)}`);
});

mp.events.addCommand('tp', (player, _full, x, y, z) => {
    const target = new mp.Vector3(Number(x), Number(y), Number(z));
    player.teleport(target);
});

Events between client and server

The client sends a named event, and the server answers by calling the client.

mp.events.add('scoreboard:show', (rows) => {
    mp.console.logInfo(`scoreboard rows: ${JSON.stringify(rows)}`);
});
mp.events.callRemote('scoreboard:request');
// Custom events have no fixed signature, so the player parameter is annotated.
mp.events.add('scoreboard:request', (player: NextMPServer.Player) => {
    const rows = mp.players.toArray().map((other) => [other.name, other.ping]);
    player.call('scoreboard:show', [rows]);
});

Event payloads are bounded and validated by the server. node tools/admin.cjs events shows the busiest names, the peak sender and why calls were rejected (debugging (docs/developer/debugging.md)).

Limiting what one client can send

Give a handler its own limits when a single player must not flood it. The limits count per player, apply only to calls from clients, and refusals show up in admin events.

mp.events.add('inventory:use', { rateLimit: { calls: 5, seconds: 1 }, maxPayloadBytes: 256 },
    (player: NextMPServer.Player, item: unknown) => {
        player.outputChatBox(`Used ${String(item)}`);
    });
mp.events.addProc('bank:withdraw', { rateLimit: { calls: 1, seconds: 2 } }, () => true);

Procedures (request and answer)

mp.events.addProc('bank:balance', (player) => {
    const balance = player.getVariable('balance');
    return typeof balance === 'number' ? balance : 0;
});
async function showBalance(): Promise<void> {
    try {
        const balance = await mp.events.callRemoteProc('bank:balance');
        mp.console.logInfo(`balance: ${String(balance)}`);
    } catch (error) {
        mp.console.logError(`balance unavailable: ${String(error)}`);
    }
}
void showBalance();

A procedure that nobody answers rejects with noHandler, and one that takes longer than ten seconds rejects with timeout. Exactly one package answers a given name.

Players and synced variables

mp.events.add('playerReady', (player) => {
    player.setVariable('balance', 100);
    player.setVariables({ job: 'courier', level: 1 });
    player.giveWeapon(mp.joaat('weapon_pistol'), 60, true);
});

Vehicles

mp.events.addCommand('car', (player, _full, model = 'adder') => {
    const vehicle = mp.vehicles.new(mp.joaat(model), player.position, player.heading);
    player.outputChatBox(`Spawned ${model} (#${vehicle.id})`);
});

Who simulates a vehicle, and why that changed, can be checked with node tools/admin.cjs inspect --kind vehicle --id N (debugging (docs/developer/debugging.md)).

Markers, checkpoints, blips and colshapes

const garage = new mp.Vector3(215.0, -810.0, 30.7);
mp.markers.new(1, garage, 2.0);
mp.blips.new(357, garage, { name: 'Garage' });
const zone = mp.colshapes.newSphere(garage.x, garage.y, garage.z, 3.0);

mp.events.add('playerEnterColshape', (player, shape) => {
    if (shape === zone) player.outputChatBox('Press E to open the garage');
});

Client storage

Client packages keep small per-server data through mp.storage. There is no built-in server database: use the database library of your choice inside a server package.

const visits = typeof mp.storage.data.visits === 'number' ? mp.storage.data.visits : 0;
mp.storage.data.visits = visits + 1;
mp.storage.flush();

A browser (in-game UI page)

A browser loads a page from your own client package only (package://). It never loads an arbitrary web address.

const hud = mp.browsers.new('package://hud/index.html');
mp.events.add('hud:money', (amount) => {
    hud.execute(`window.setMoney(${Number(amount)})`);
});

C# packages

node tools/nextmp.cjs init my-server --template csharp creates a package that builds against the NextMP.Server reference package. The API and a complete example are in docs/api/csharp.md.

Custom assets (models, maps, DLC packs)

Put streamed files under client_packages/<resource>/stream/ and DLC packs under game_resources/dlcpacks/, then check them before you start the server:

node tools/nextmp.cjs assets validate --manifest assets.json

See assets (docs/developer/assets.md) for the layout, what is checked and how cache invalidation works.

Native plugins

node tools/nextmp.cjs create plugin metrics creates a CMake project that finds the SDK with find_package(NextMPPlugin) and exports nextmp_plugin_v1. See docs/plugins.md (docs/plugins.md).

Unit tests and bots

  • Unit tests run your package with @nextmp/testing, without a server or the game: gamemode testing (docs/developer/testing.md).
  • Bots drive a real server over the real protocol (no GTA, no browser pages): bots (docs/developer/testing.md).