Server API (mp.*, JavaScript)

This is the contract a server package can rely on. It covers only what is implemented and tested. Anything not listed here does not exist yet. The parity status is tracked in rage-like-parity.md (docs/rage-like-parity.md).

Server packages run in Node.js with full Node access (require, fs, net). Each package has its own V8 isolate and its own heap. Packages do not share JavaScript values; they talk to each other only through events.

Packages

Each directory under packages/ is one package. A JavaScript package starts from index.js, index.cjs or index.mjs. An optional nextmp.json names the runtime and entry point explicitly, and declares dependencies:

{"runtime": "js", "main": "server/main.js", "dependencies": ["database", "accounts"]}
  • Keys. runtime and main come together ("js" or "csharp"); leave both out to keep the index.* convention. dependencies are names of other server packages. Any other key refuses the whole server start, so a typo is never ignored.
  • Start order. Dependencies start before the packages that need them. Among packages that don't depend on each other, the order is alphabetical, never whatever order the file system happened to list the directory in.
  • Stop order. A package stops before its dependencies, so its packageStop handler still sees them running.
  • Refusals. A dependency that is not a server package here, one named twice, or a cycle (the error walks it: a -> b -> a) refuses the whole server start.
  • A dependency that fails to start stops the server start. Packages already started are stopped again in reverse order.
  • No versions. All packages of a server ship together in one directory, so there is nothing to version against.

General rules

  • One object per entity. Within one package, mp.players.at(5) === mp.players.at(5). The same object reaches every event handler, so identity comparisons work and properties you set yourself stay on it (player.loggedIn = true). The object is released when the entity is gone: a player quits, a vehicle, object or ped is removed, or a periodic sweep finds that a blip, marker, checkpoint or colshape was destroyed.
  • A reconnecting player is a new object. Player ids are reused. An object kept from an earlier connection answers valid === false and refuses every call, even when a new player later gets the same id.
  • Entities are ids, not live records. Every property read asks the server again. Once the entity is destroyed, reads and writes throw ReferenceError and valid is false.
  • Invalid values are refused, never clipped. A value outside its documented range throws TypeError. So does assigning a read-only property. When the server refuses to create an entity (limit reached), new throws RangeError.
  • Dimensions. A dimension is a signed 32-bit integer; null means global (seen in every dimension). New entities start in dimension 0.
  • Hashes are unsigned 32-bit numbers. Hashes that come from game natives on the client are signed; convert them with >>> 0.
  • Positions are plain {x, y, z} objects. Changing a returned object changes nothing until you assign it back.

mp.events

CallMeaning
add(name, handler) / add({name: handler, ...})Subscribe. Handlers run in subscription order.
once(name, handler)Subscribe for one call.
remove(name, handler?)Unsubscribe; omitted handler removes this package's handlers for that name. Returns the count.
getAllOf(name)Detached snapshot of this package's current handlers.
reset()Clears this package's event, command and procedure registrations.
call(name, ...args)Call local handlers in this package (at most 128 arguments).
addCommand(name, handler) / addCommand({name: handler})Chat command; see below.
addProc(name, handler)Procedure that client packages call with callRemoteProc; see Procedures.
add(name, limits, handler) / once(name, limits, handler) / addProc(name, limits, handler)The same, with this handler's own limits; see Handler limits.

Names starting with __nextmp: are reserved.

Handler limits

A handler can declare its own limits for calls that clients send:

mp.events.add('inventory:use', { rateLimit: { calls: 5, seconds: 1 }, maxPayloadBytes: 512 }, (player, item) => { /* … */ });
mp.events.addProc('bank:balance', { rateLimit: { calls: 2, seconds: 1 } }, player => balanceOf(player));
  • rateLimit allows calls per seconds for each player: a bucket that holds calls and refills evenly. One client cannot use up the quota of others. The bucket follows the connection, so a player who reconnects under the same id starts fresh.
  • maxPayloadBytes (1–4096) is the largest encoded argument payload. The global limit stays 4096 bytes; a payload that size never reaches a handler, so a handler limit can only narrow it. Larger values throw TypeError, as do unknown keys and malformed values.
  • A refused event call skips only this handler; other handlers of the same name still run. A refused procedure call answers busy (rate) or failed (payload) without running the handler.
  • Limits apply only to calls from clients: engine events, mp.events.call and events from other packages are never limited.
  • Limits belong to the handler. remove, reset and stopping or restarting the package drop them with it, and a player's counters are dropped when the player leaves.
  • Refusals are counted, not logged per call: node tools/admin.cjs events shows them as handler rate limit and handler payload limit, next to the global reasons (malformed payload for arguments that cannot be read, too many open calls for procedures). Payloads over 4096 bytes are refused before the name can be read and appear as malformed messages in admin net.
  • C# packages do not have handler limits yet.

Values across the network

Event arguments (player.call, client events) and procedure arguments and answers are null, booleans, finite numbers, strings, arrays and plain objects, within the variable limits (see Entity variables). They may also carry entities:

  • A player, vehicle, object or ped travels as a reference (its kind and id), never as its state.
  • The receiver gets its own object for that entity: the same one as mp.players.at(id) and so on.
  • A reference to an entity the receiver does not have (gone, or not streamed to that client) arrives as null. Check before use.
  • Variables never hold entities, because a stored reference outlives its entity. setVariable with an entity throws TypeError. Store id instead.

Commands

A chat line starting with / is never broadcast to chat. If a package has registered the command, its handler gets (player, fullText, ...arguments). fullText is everything after the command name; arguments are its words (at most 64). Otherwise playerCommand(player, text) fires with the line minus the slash.

Command names are case-insensitive, 1–32 characters of A-Z a-z 0-9 _ -. Registering the same name twice in one package throws TypeError.

Procedures

A procedure is a call that gets an answer. It is separate from events, which never answer.

SideRegistersCalls
Servermp.events.addProc(name, (player, ...args) => value)player.callProc(name, args?) → Promise
Clientmp.events.addProc(name, (...args) => value)mp.events.callRemoteProc(name, ...args) → Promise
C#Events.AddProc(name, handler)player.CallProcAsync(name, args) → Task<JsonElement>
  • The answer.
    • The handler's return value is the answer. A returned promise (or C# Task) answers when it settles.
    • A throw or a rejection answers with its message.
    • Arguments and the answer follow the network value rule, entities included, and undefined answers null.
  • One handler per name. A second addProc for a name in the same package throws.
    • In C#, one handler per name across all C# packages.
    • If several packages register the same name, one of them answers. Name procedures uniquely.
  • The call never hangs. Every call ends exactly once, with a value or with an error whose code is one of:
codeMeaning
failedThe handler threw or rejected (its message follows), or its value cannot be sent.
noHandlerNobody on the other side registered the name.
busyToo many unanswered calls: at most 4096 waiting per side, and 64 open calls per caller on the receiving side.
timeoutNo answer within 10 s. A late answer is dropped.
disconnectedThe player left or the session ended.
  • Stopping answers for you. A package stopped while one of its handlers is still pending answers failed ("the package stopped").
  • Ordering. Calls travel in the same ordered reliable stream as events.
  • Answers match their call. Each answer is matched to its call by a number unique on that connection. A reconnecting player never receives an answer meant for the previous one.
  • Answers are never awaited synchronously. The server tick never waits for a client.

Server events

EventArguments
packagesLoaded— (once, after every package started)
serverShutdownNo arguments, once before final package teardown while Core is alive; synchronous, non-cancellable. No asynchronous delayShutdown facility.
packageStopname: only to the package being stopped (server shutdown or restart), while Core and players are still alive. It cannot delay the stop.
playerJoinplayer (the client is ready)
playerQuitplayer, reason. The player is still readable.
playerSpawnplayer
playerDeathplayer, weapon, killer | null
playerChatplayer, text. Return false to keep the line out of chat.
playerCommandplayer, text
playerDamageplayer, healthLoss, armourLoss, attacker | null, weapon
playerStartEnterVehicle / playerEnterVehicleplayer, vehicle, seat
playerExitVehicleplayer, vehicle
playerStartExitVehicleplayer only, on the observed leaving-state rising edge while still seated in the same known vehicle. Non-cancellable.
playerVehicleSeatChangeplayer, vehicle, seat, oldSeat
playerWeaponChangeplayer, oldWeapon, newWeapon
playerDimensionChangeplayer, oldDimension, newDimension
entityModelChangeplayer, oldModel: currently Player model writes only, after Core commit and publication. Non-cancellable; unchanged/invalid writes do not dispatch. Not a generic model event for immutable entity classes.
entityCreatedentity: current server-created Vehicle/Object/Ped after Core insertion and publication. vehicleCreate runs first; if an earlier callback destroys it, generic delivery is skipped. Non-cancellable. Player and virtual/world entity creation is not covered.
playerStreamIn / playerStreamOutplayer, forPlayer — player started or stopped being shown to forPlayer (see below)
playerEnterColshape / playerExitColshapeplayer, colshape
playerEnterCheckpoint / playerExitCheckpointplayer, checkpoint
vehicleCreate / vehicleDestroyvehicle (destroy fires before removal)
vehicleDamagevehicle, bodyLoss, engineLoss, tankLoss, attacker | null, weapon
vehicleDeathvehicle — once per wreck (exploded or burnt out), again only after a repair; the cause hash and killer RAGE MP adds are not known and are not passed
playerDeviceplayer, {status, deviceId, keyStorage} when the device key check settles (player identity (docs/security/player-identity.md))
playerIntegrityplayer, {status, reasons} when the integrity state or its reason words change: first report, a periodic re-check, a missed re-check (client integrity (docs/security/client-integrity.md)). Heuristic reasons are not proof of cheating.

Seats use game numbering: -1 is the driver and passengers count from 0. The same numbering is used everywhere in this API. Exit-start follows a committed snapshot. Missing intermediate states are not invented; GTA timing is not live verified. JS and C# subscriptions are package-owned and cleared on stop. Remote event names cannot impersonate these engine facts. C# exposes Events.ServerShutdown and Events.PlayerStartExitVehicle directly, without JS routing. Events.EntityModelChange also uses Core directly; the captured player generation prevents a removed/reconnected Player from receiving an old model-change fact. The first model write reports old model zero if appearance has not been announced. Handlers are package-owned.

playerStreamIn(player, forPlayer) and playerStreamOut(player, forPlayer) follow the server's interest graph — the same answer as player.isStreamedFor(forPlayer) — after it is refreshed, and are raised before the tick, like colshape events. The server builds them only while some JS package has a handler for that name, or while any C# package runs (Events.PlayerStreamIn/PlayerStreamOut), because on a busy server edges change by the hundreds per tick. A player who quits raises playerQuit, not a stream-out; changes whose players left or reconnected before the announcement are skipped. A handler that subscribes during a tick receives changes from the next refresh on.

A client-side event reaches the server as (player, ...args) under the name the client used. A client cannot use any name from the table above. Always check that the sending player may ask for what the event requests.

mp.players

player.notify(message) shows a notification above this player's minimap (the game feed): 1..255 UTF-8 bytes, returns false for a gone player and throws TypeError for an empty or too long text. C#: player.Notify(message). It travels as the platform event __nextmp:notify to that player only.

Pool: at(id), atRemoteId(id), exists(player), forEach(fn), forEachInRange(position, range, fn) (range 0–10000), toArray(), broadcast(text), length, read-only size. broadcastInRange(position, range, [dimension,] text), broadcastInDimension(dimension, text), callInRange(position, range, [dimension,] name, args?) and callInDimension(dimension, name, args?) pick players by their last reported position and current dimension (null is the global dimension) and then chat or emit to each of them. C#: Mp.Players.InRange(...) or Mp.Players.All filtered by Dimension, then OutputChatBox/Call on each. All eight server pools also have getClosest(position, limit?) (an entity, or with limit 0..4096 an array, nearest first, ties by id), getClosestInDimension(position, dimension, limit?), forEachInDimension(dimension, fn) (exact match: global null entities only for null) and apply(fn, ...args). They walk the pool's own forEach snapshot; polygon colshapes, which have no position, are never "closest". new mp.Event(name, handler) subscribes like mp.events.add and .destroy() removes exactly that handler (also on the client). C# uses LINQ over All instead. All eight current server pools also expose forEachFast as the exact captured forEach callback and deprecated toArrayFast as the exact toArray callback. These do not claim another fast iteration engine. Traversal uses the existing bounded snapshot and rechecks destroyed lifetimes; arrays are detached snapshots. C# continues to use All/foreach rather than unnatural JS aliases.

mp.players.call(name, args = []) sends the existing typed reliable event to all current players. mp.players.call(playersArray, name, args = []) targets a bounded array of genuine live wrappers. Payload validation and generation revalidation precede sending. These are JS conveniences over the same Core event operation used by C#; no separate transport or RPC system exists. player.callUnreliable(name, args = []) sends the same typed event over the unreliable state channel: no resend, no ordering against reliable messages, so it may arrive before a just-announced player or not at all. Use it for frequent, replaceable updates. The argument and reserved-name rules are those of call. C#: player.CallUnreliable(name, args...). There is no pool-wide unreliable call, and the client's mp.events.callRemoteUnreliable is not available. Production stdin is log-only; it is not an administrative command source or consoleCommand event. The existing server playerJoin remains after client scripts report ready. It is not counted as exact RAGE connection-time event parity. Existing numeric Marker/Checkpoint type properties also keep their style semantics, not the string class discriminator.

PropertyAccessNotes
id, remoteId, validread
typeread"player"; vehicles, objects and peds expose their respective class name.
name, ip, pingread
socialClubreadIn-game player name as the client declared it; not a verified Rockstar account (player identity (docs/security/player-identity.md))
serialreadLegacy machine fingerprint, 16 hex digits; "" if unknown (never "0"). Client-declared, unverified
deviceStatusreadpending, verified, unavailable or invalid (device key (docs/security/player-identity.md))
deviceVerifiedreaddeviceStatus === 'verified'
deviceIdread32 hex digits, this server only; null unless verified. Proves key possession, not an unmodified client or a unique machine
deviceKeyStoragereadhardware or software as declared by the client (not attested); null unless verified
integrityStatusreadoff, pending, valid, degraded, no-report or invalid (client integrity (docs/security/client-integrity.md)); the client's statement about itself, not proof of honesty
integrityReasonsreadreason words without module names: strong code-modified, imports-modified, image-mismatch, code-changed; heuristic entry-hooks, late-modules, unsigned-modules, debugger. A fresh array per read
positionread/writeWriting teleports the player.
heading, velocityreadFrom the latest client snapshot.
dimensionread/write
health, armourread/write0–65535. The server owns both.
modelread/writeNonzero hash.
vehicle, seatreadnull when on foot.
weapon, weaponAmmoreadWeapon in hand.
weaponsread[{hash, ammo, tint, components}]
allWeaponsreadDetached server-held {unsignedDecimalHash: ammo} snapshot; editing it never changes inventory.
frozen, invincibleread/writeboolean
animationread{dictionary, name} or null (server-requested animations only).
hairColor, hairHighlightColorreadPrimary/secondary colour from announced appearance, otherwise null; use setHairColor to write.
eyeColorread/writeGetter is null before announcement; setter requires an integer 0..255 and uses the existing Core appearance operation.
isAiming, isClimbing, isEnteringVehicle, isInCover, isJumping, isLeavingVehicle, isReloading, isInMeleereadLatest client-reported flags; not a server-authoritative anticheat assertion.

Methods:

  • call(name, args = []) sends an event to this player's client; callUnreliable(name, args = []) sends it without a delivery promise (see above).
  • callProc(name, args?) calls a procedure of this player's client packages and returns a Promise of its answer (see Procedures). A player who left rejects with disconnected instead of throwing.
  • teleport(position), kick(reason?) and outputChatBox(text).
  • isStreamedFor(viewer) (RAGE name isStreamed): whether this player is currently shown to viewer, by the server's interest graph. Never true for the player themselves; false for a viewer who left. Vehicles have the same pair. C#: IsStreamedFor(viewer).
  • giveWeapon(weapon | weapon[], ammo?, equip = true): at most 64 weapons; only the last one is equipped. A weapon is an unsigned 32-bit hash or its name ('weapon_pistol', hashed with the game's joaat), as RAGE MP's HashOrNumberOrString; signed hashes are still refused (>>> 0).
  • removeWeapon(weapon), removeAllWeapons(), setWeaponAmmo(weapon, ammo) and getWeaponAmmo(weapon) (0 if the weapon is not owned), with the same hash-or-name rule.
  • setClothes(component 0..11, drawable, texture, palette? 0..3).
  • setProp(index, drawable, texture): GTA slots 0, 1, 2, 6 and 7; -1 removes the prop. Empty GTA slots 3..5 are refused. The five packed wire slots are internal, not public indices.
  • getClothes(component 0..11) returns {drawable, texture, palette}; getProp(slot) returns {drawable, texture}; getHeadBlend() returns {shapes: [a,b,c], skins: [a,b,c], shapeMix, skinMix, thirdMix}. These are detached Core values, or null before appearance is announced, never guessed defaults. Stale connection wrappers are rejected. C# exposes matching direct Core reads.
  • getFaceFeature(index 0..19) returns the normalized announced scale or null; C# uses GetFaceFeature, HairColor, HairHighlightColor, and EyeColor with existing SetEyeColor.
  • getStateFlags() returns the latest reported protocol flag bitset, not an authority assertion.
  • playAnimation(dictionary, name, speed?, flags?) and stopAnimation().
  • putIntoVehicle(vehicle, seat -1..15) and clearBlood(). Seating requests go to the client; vehicle and seat reflect reported state, not the request.
  • Appearance:
    • setHeadBlend(shape×3, skin×3 0..255, shapeMix, skinMix, thirdMix 0..1);
    • setHeadOverlay(slot 0..12, index 0..255 (255 = none), opacity 0..1) and setHeadOverlayColor(slot, colorType 0..2, color, secondColor?);
    • setFaceFeature(index 0..19, scale -1..1), setHairColor(color, highlight?) and setEyeColor(color);
    • setDecoration(collection, overlay), removeDecoration(...) and clearDecorations();
    • getAppearance(), which returns null until the player declared an appearance;
    • `setCustomization(male, shape×3, skin×3, shapeMix, skinMix, thirdMix, eyeColor, hairColor, highlightColor, faceFeatures[≤20])` (RAGE): the freemode model by gender plus the setters above, every argument checked before any is applied. C#: SetCustomization.
  • Weapons: giveWeaponComponent(weapon, component), removeWeaponComponent(...) and setWeaponTint(weapon, tint 0..255).
  • Other: playSpeech(speech, params?, voice?) and setMaxArmour(0..65535).
  • Player microphone voice: setVoiceMuted, setVoiceRange, joinVoiceChannel, leaveVoiceChannel, blockVoice. These control server authorization, not microphone consent; see voice (docs/voice.md) for signatures, bounds, radio/proximity policy and privacy. This is distinct from playSpeech.

These methods, attachments and mp.world are shared operations (script/operations.hpp). JS and C# use the same names, limits and refusal messages: a bad argument throws TypeError with the reason, and the methods return the core's answer.

mp.vehicles

Pool: new(model, position, heading?), at, atRemoteId, exists, forEach, forEachInRange, length.

PropertyAccessNotes
id, remoteId, valid, modelread
positionread/writeWriting teleports the vehicle; heading is kept.
rotation, velocityreadDegrees for rotation.
headingread/write−360..360
dimensionread/write
controllerread/writePlayer | null. Pins who simulates the vehicle; applies at the next ownership review, not immediately. Every change of controller starts a new control generation; the client must sign its snapshots with it, and a snapshot of an earlier generation is refused even from the same player (see Controller authority).
bodyHealth, engineHealth, petrolTankHealthreadReported by the controller.
engine, siren, lightsread
horn, highbeams, deadreadExisting controller-reported flags; not an anticheat assertion. No setter or implicit entity removal.
lockedread/writeboolean
windowsOpenread/writeBit mask 0–255.
occupantsreadPlayers
numberPlateread/writeAt most 8 characters of A-Z 0-9 and space; stored upper-case.
numberPlateTyperead/write0–5
livery, wheelType, windowTintread/write−1 is stock.
pearlescentColor, wheelColorread/writePalette 0–255.
dirtLevelread/write0–15
neonEnabledread/writeboolean (all four sides)

Methods:

  • destroy(), teleport(position, heading?) and repair().
  • setColor(primary, secondary) and getColor(0 | 1).
  • setColorRGB(r1, g1, b1, r2, g2, b2) and getColorRGB(0 | 1). getColorRGB returns null when a palette colour is in use.
  • setNeonColor(r, g, b) and getNeonColor().
  • setMod(slot 0..48, index) (-1 is stock) and getMod(slot).
  • setExtra(index 1..16, value) and getExtra(index). value must be a boolean: the client-side game native's second argument means disable, so a number would read inverted for code moved from the client. Extras 15 and 16 (RAGE MP's limit) are applied only on models that report them.
  • setPaint(primaryType, primaryColor, secondaryType, secondaryColor) with paint types 0..5 (normal, metallic, pearl, matte, metal, chrome) and palette colours 0..255; like setColor it clears custom RGB. getPaint(0 | 1) returns the paint type, or -1 while the server never set one (the game's own type then stays). Both travel in VehicleAppearance (protocol 67), reach every client that streams the vehicle, and are re-sent on stream-in. Live GTA application (SET_VEHICLE_MOD_COLOR_1/2) is not yet verified.
  • setDoor(door 0..5, level 0..7) and getOccupant(seat).
  • getOccupants() returns a detached array of current Player wrappers, excluding missing, disconnected or vehicle-mismatched occupants; retained/stale Vehicle receivers are rejected.

Common Distance Helpers

Players, vehicles, peds, objects, blips, markers, checkpoints and positioned colshapes share dist(position) and distSquared(position): current 3D Euclidean distance, finite vector input, genuine current receiver, no pool scan. A polygon colshape has no centre and rejects the call rather than fabricating one. C# DistanceTo(Vector3) and DistanceSquaredTo(Vector3) read Core directly and apply the same lifetime and finite-input checks.

mp.objects and mp.peds

Objects:

  • Pool: new(model, position, rotation?) or the RAGE MP options form: objects take new(model | name, position, {rotation, dimension}), peds take `new(model, position, {heading, dimension}). RAGE options NextMP has no order for (alpha on objects; dynamic, frozen`, invincible, lockController on peds) are refused with a TypeError, not ignored. Also at, atRemoteId, exists, forEach, length.
  • Properties: id, valid and model are read-only; position, rotation and dimension are read/write.
  • Methods: move(position, rotation?) and destroy().

Peds (server-owned):

  • Pool: the same as objects.
  • Read-only: id, valid and model.
  • Read/write:
    • position, rotation, dimension and weapon;
    • heading, which is rotation.z (−360..360);
    • health, maxHealth and armour (0–65535).
  • Methods: move(position, rotation?) and destroy().
  • Setting health to 0 on a living ped kills it. Raising it heals.

Attachments

Every player, vehicle, object and ped has attachTo(target, bone, offset, rotation, collision?, fixedRotation?), detach() and getAttachment().

  • Bone: an index (-1 is the root) or a bone name of at most 64 bytes.
  • Attaching to itself is refused.
  • Reading back: getAttachment() returns `{target, bone, boneName, offset, rotation, collision, fixedRotation} or null`.

mp.world

  • Weather: weather (read/write), getWeather() and setWeather(name). An unknown name throws.
  • Time: getTime() returns {hour, minute, second}; setTime(hour 0..23, minute?, second?). Seconds are preserved by the existing clock and world replication. C# SetTime has the same optional minute/second values. mp.world.time.set(hour, minute, second) uses that operation; editing time.hour/minute/second stages a resource-local draft until time.set() commits it. Invalid values do not alter the draft or world. Getters without a draft read the current clock.
  • Explosions: `explode(position, type 0..82, {scale?, audible?, invisible?, shake?, dimension?}). The explosion reaches everyone in range and in the dimension (default 0; null` is global).
  • Weather transition: setWeatherTransition(name, duration?) changes the weather smoothly for every player over duration seconds (0..300, default 15). weather returns the new name at once. The replicated weather stays the old one until the transition ends, so a player who joins meanwhile gets the new weather then, without a jump. setWeather cancels a running transition. An unknown name or duration throws.
  • Map placements (IPL): requestIpl(name) and removeIpl(name) load or unload an IPL for every player, including players who join later (sent in request order). removeIpl also unloads IPLs the game loads itself. Names are 1..64 Latin letters, digits, _ and -; the server keeps the last order for up to 1024 names. When a player leaves the server, each IPL goes back to the state the player's game had before the first order, so the next server finds an unchanged map. The client applies them with REQUEST_IPL/REMOVE_IPL; live GTA V verification is pending.

mp.server and mp.config

mp.joaat(name) and mp.joaat(names) expose the existing unsigned Jenkins name hash, ASCII-case-insensitive, also available in C# as Mp.Hash. Arrays are bounded to 1024 strings and each string to 1024 UTF-8 bytes; invalid inputs fail before any server state changes.

  • Settings: mp.config is {name, port, maxPlayers, passworded, tickRate, streamDistance}, the same value as mp.server.getConfig(). Scripts cannot change it.
  • Packages: startPackage(name), stopPackage(name) and restartPackage(name). They apply before the next tick, and a package may restart itself. An unknown name throws.
    • startPackage first starts any stopped dependencies.
    • stopPackage first stops the running packages that depend on it.
    • restartPackage stops its dependents and restarts the package. Then it starts again the dependents that were running, and only those.
  • Model catalogues: getVehicleModel(hash), getPedModel(hash) and getWeaponModel(hash) read the server's game data catalogue.
    • A model the catalogue does not list returns null.
    • A server without a catalogue throws, because "unknown" is not "absent".
  • Vehicles: vehicle.getModCount(slot 0..48) returns -1 without a catalogue.

Entity variables

Players, vehicles, objects and peds carry variables that the server shares with clients:

MethodMeaning
setVariable(name, value)Set one variable.
setVariables({name: value, ...})Set several at once; either all apply or none do.
deleteVariable(name)Remove it; returns whether it existed.
getVariable(name)The value, or undefined when unset (null is a legitimate value).
hasVariable(name), getVariableNames()
  • Shared state: variables live in the server core, not in one package. Every package, and later every scripting language, sees the same values.
  • Names: 1–64 bytes of UTF-8.
  • Values: null, booleans, finite numbers, strings, arrays and plain objects (not class instances, Map, proxies, functions or undefined). Each value may be at most 4096 bytes encoded and 8 levels deep, with at most 128 items per array or object.
  • Per-entity limits: at most 64 variables and 16 KB in total; past those, setVariable throws RangeError.
  • Who receives them: clients that currently have the entity streamed, plus a player always receiving their own. A client gets the full set when the entity streams in, then changes, and forgets them on stream-out.
  • Lifecycle: variables disappear with their entity. A reused player id starts with none.
  • Events: setting the same value again sends nothing. There is no server-side change event, because the server script that makes a change already knows about it.
  • Blips, markers, checkpoints and colshapes have the same six methods with the same names, values and limits, also shared by every package and C# (SetVariable … on WorldThing), and removed with the entity — but their variables stay on the server. These kinds have no entity kind on the wire: clients hold no wrapper for server blips, and the network views of markers and checkpoints carry no variables.

mp.blips, mp.markers, mp.checkpoints

These are drawn, not simulated. Every property write replaces the whole record on every client. The options object in new takes the same names as the properties. An unknown option name throws TypeError, so a typo cannot silently keep a default.

PoolCreation
mp.blipsnew(sprite, position, {name, color, alpha, scale, shortRange, dimension})
mp.markersnew(type 0..43, position, scale, {rotation, direction, color, visible, bobUpAndDown, faceCamera, rotate, drawDistance, dimension})
mp.checkpointsnew(type 0..69, position, radius, {direction, height, color, visible, drawDistance, dimension})

Every pool also has at(id), exists, forEach and length. Every instance has id, valid and destroy().

Field types:

  • Blip name: at most 64 bytes.
  • Blip color: palette index 0–255.
  • Marker and checkpoint color: [r, g, b] or [r, g, b, a].
  • Marker scale: a number or {x, y, z}.
  • scale, radius and height: 0–1000.
  • drawDistance: 0–10000.

marker.setColor(r, g, b, a) accepts exactly four integer channels in 0..255 and returns undefined. It atomically changes the existing Core color record without changing other fields; malformed values and forged, wrong-class or destroyed receivers are rejected. The reviewed compatibility contract uses those four required arguments. C# retains its direct-Core Marker.Color property; there is no second replication path. Unestablished getColor() return semantics are not guessed.

marker.hideFor(player) and checkpoint.hideFor(player) hide that one drawing from one player; everyone else keeps seeing it, including players who join later. showFor(player) undoes it. Both return false when the player has left. The server keeps the list and simply does not send the drawing to a hidden player (clients never learn it exists); a leaving player's id is forgotten, so whoever reuses the id is not affected. C#: Marker.HideFor/ShowFor, Checkpoint.HideFor/ShowFor. Blips have no hideFor.

checkpoint.isPlayerWithin(player) answers from the same server tick that produced the enter/exit events. A checkpoint is entered through a column: the point's radius wide, reaching one radius below the point and max(height, radius) above it. The column exists because the point usually sits on the ground, while a player's position is the middle of their body, and higher still in a vehicle.

mp.colshapes

Invisible zones. The server checks them once per tick for every player whose position it knows. A player enters or exits only when their dimension meets the zone's.

CreationShape
newCircle(x, y, radius, dimension?)Circle on the ground plane; any height.
newSphere(x, y, z, radius, dimension?)Sphere.
newTube(x, y, z, radius, height, dimension?)Upright column; base at z.
newCuboid(x, y, z, width, depth, height, dimension?)World-axis box centred on the point.
newRectangle(x, y, width, depth, dimension?)Rectangle centred on the point; any height.
newPolygon(minZ, maxZ, points, dimension?)3–64 points as [x, y] or {x, y}, between two heights.

Sizes must be finite and within 0–20000 m. Pool: at, exists, forEach, length.

Instance:

  • Read-only: id, valid and shapeType ("circle", "sphere", "tube", "cuboid", "rectangle", "polygon").
  • Read/write: position, and dimension (null = global). A polygon has no position.
  • Methods: isPointWithin(point), isPlayerWithin(player) (from the last tick) and destroy().

Destroying a zone, and a player quitting, produce no exit event. Players do not trigger zones until their client has sent its first position. Vehicles and objects do not trigger zones.

Configuration limits (conf.json)

max-vehicles, max-objects, max-peds, max-blips, max-markers, max-checkpoints and max-colshapes cap how many entities scripts may create. Past the cap, new throws RangeError. See server layout (docs/migration/server-layout.md).

Platform additions

Transfers between cluster instances, Discord presence and sign-in, outgoing HTTP, the scheduler, state across restarts, the join queue and the idle helper are documented in platform.md. playerQuit now receives the leave reason as a word (quit, lost, kicked, afk, shutdown, transfer) and the kick text as a third argument; two-argument handlers keep working.

Controller authority

The server decides who simulates each vehicle (driver first, then a script-pinned controller, then the nearest player within range, in the same dimension). Each change of controller increments that vehicle's control generation exactly once (server/src/entity_controller.cpp, handController). The generation travels with the grant (VehicleAuthority, and VehicleAdded for a newly streamed vehicle, protocol 64), and the controlling client stamps it on every VehicleState it sends.

A snapshot is accepted only when the sender is the current controller and the generation is the current one. A late snapshot from a previous controller — or from the same player under an earlier grant (A → B → A) — carries an older generation and is refused as STALE_GENERATION, without any timing heuristic. Vehicle ids are never reused within a server process, and generations only grow, so a pair (id, generation) names one grant. The client forgets the generation when the vehicle streams out, is removed, or the session ends, and sends nothing for a vehicle it does not currently control.

Refusals are counted by reason in nextmp_security_ownership_rejections_total, nextmp security status and the dashboard: NOT_CONTROLLER, STALE_GENERATION, ENTITY_NOT_STREAMED, WRONG_DIMENSION, WRONG_SESSION, UNKNOWN_ENTITY. node tools/admin.cjs inspect --kind vehicle --id N shows the current controller and controlGeneration. Only vehicles are client-controlled: peds and objects are server-owned, and player snapshots describe only the sender.