Client JS API
Client resources use JavaScript and the global varion object. The runtime injects globalThis.varion (also reachable as plain varion) before your client-main runs.
This page is the full reference for the client runtime API, grouped by namespace. See also WebView, GTA natives and the short client overview.
Terminology: client events are local events inside the client; server events are events exchanged with the server. There is no
varion.onClienton the client — you receive events from the server viavarion.onServer, andonClientexists only on the server side.
Logging
varion.log('message'); // info
varion.warn('message'); // warning
varion.error('message'); // errorvarion.warn and varion.error are the only level helpers (logWarning / logError do not exist).
Events
Local client events
const sub = varion.on('myEvent', (a, b) => varion.log(`${a} ${b}`));
varion.once('resourceStop', () => varion.log('stopping'));
varion.off('myEvent', handler);
sub.destroy(); // on/once return { destroy() }
varion.emit('myEvent', 1, 2); // local emit for other client handlers
varion.emitRaw('anyEvent', payload); // no reserved-name filter (for tests)varion.emit blocks engine-reserved names (connection:*, stream:*, playerSpawn, entityStreamIn, etc.) and varion:-prefixed names. Listen to those with varion.on; only the engine may emit them.
Server events
varion.onServer('chat:addMessage', (author, text) => {});
varion.onceServer('welcome', (data) => {});
varion.offServer('chat:addMessage', handler);
varion.emitServer('chat:message', 'hello');
varion.emitServerUnreliable('position:update', x, y, z);
// Unreliable receive
varion.onServerUnreliable('position:update', (x, y, z) => {});
varion.offServerUnreliable('position:update', handler);
varion.onceServerUnreliable('position:update', handler);emitServer arguments are auto-encoded into typed MValues (see below).
Listener introspection
varion.getLocalEventListeners('myEvent'); // local
varion.getRemoteEventListeners('chat:addMessage'); // server (reliable)
varion.getUnreliableRemoteEventListeners('pos'); // server (unreliable)Tick loop
varion.everyTick(() => { /* ~every game tick */ });
varion.on('everyTick', handler); // equivalentColShape events (casing matters)
The client uses exactly this casing:
| Event | Arguments |
|---|---|
playerEnterColShape | (colShape) |
playerExitColShape | (colShape) |
entityEnterColshape | (colShape, entity) |
entityLeaveColshape | (colShape, entity) |
Note: playerEnter/ExitColShape use a capital S, while entityEnter/LeaveColshape use a lowercase s (and Leave, not Exit).
RPC
// Call a server RPC and await the response (Promise)
const result = await varion.callServer('server:ping', arg);
const same = await varion.emitRpc('server:ping', arg); // emitRpc is an alias of callServer
// Client RPC handler (server calls it via EmitClientRpcAsync)
varion.onRpc('client:ping', async () => ({ pong: true }));
varion.offRpc('client:ping', handler);callServer / emitRpc return a Promise that resolves with the server result or rejects on error/timeout.
MValue
The typed-value factory varion.MValue (applied automatically by emitServer, but also available explicitly):
varion.MValue.nil();
varion.MValue.bool(true);
varion.MValue.int(-5);
varion.MValue.uint(10);
varion.MValue.double(1.5);
varion.MValue.string('hi');
varion.MValue.list(a, b, c);
varion.MValue.dict({ k: 'v' });
varion.MValue.vector3(x, y, z);
varion.MValue.entity(entityType, id);
varion.MValue.rgba(255, 0, 0, 255);
varion.MValue.byteArray(str);
varion.MValue.from(anyValue); // auto-detect type
varion.MValue.to(mvalue);Hashing and model loading
const hash = varion.hash('mp_m_freemode_01'); // joaat is an alias of hash
varion.joaat('adder');
varion.loadModel('adder');
varion.isModelLoaded('adder'); // → boolean
varion.unloadModel('adder');
await varion.waitForModel('adder', 10000); // Promise, resolves the hash
varion.requestIpl('v_janitor');
varion.removeIpl('v_janitor');Timers
Tick-driven timers (pumped on the game tick). Available both as varion.* methods and as global functions.
const t = varion.setTimeout(() => {}, 500);
varion.clearTimeout(t);
const i = varion.setInterval(() => {}, 1000);
varion.clearInterval(i);
setTimeout(fn, 500); // global aliases are available tooA resource's timers are cleared automatically when the resource stops.
Cursor and input
varion.isKeyDown(varion.KeyCode.E); // by code or name
varion.showCursor(true); // show/hide the cursor
varion.getCursorPos(true); // { x, y } (true = normalized 0–1)
varion.setCursorPos({ x, y }, true);
varion.toggleGameControls(false); // disable game controls
varion.gameControlsEnabled(); // → booleanvarion.KeyCode(aliasvarion.Key) maps key names to codes (A–Z,F1–F12,Numpad0–Numpad9,Shift,Ctrl,Space,MouseLeft, etc.).varion.GamepadButtonmaps gamepad buttons (A,B,X,Y,LB,RB,DPadUp, etc.).
Keybind
const kb = new varion.Keybind(varion.KeyCode.E, /* hold */ false);
kb.on('pressed', () => varion.log('E down'));
kb.on('released', () => varion.log('E up'));
kb.gameControlsOnly = true; // ignore while game controls are disabled
kb.enableInUi = false; // ignore while a WebView is focused / cursor shown
kb.repeatIntervalMs = 0; // >0 — auto-repeat while held
kb.isPressed; // current state
kb.destroy();Entities
BaseObjectType
varion.BaseObjectType
// { Player:0, Vehicle:1, Ped:2, Object:3, Blip:4, ColShape:7,
// Checkpoint:8, VirtualEntity:9, Marker:22, TextLabel:23 }Base Entity
varion.Entity.getByID(type, id);
varion.Entity.getByRemoteID(type, remoteId);
varion.Entity.getByScriptID(scriptId);
varion.Entity.all;Common instance getters: id, remoteId, isRemote, type, scriptID, valid, isStreamedIn, streamed, isSpawned, dimension, isDead, speed, visible, alpha, nativeModel, heading, pos, rot.
Instance methods:
entity.exists();
entity.destroy();
entity.setPos(x, y, z);
entity.setRot(x, y, z);
await entity.setVisible(true);
await entity.setAlpha(200, false);
await entity.resetAlpha();
entity.attachTo(target, bone, offset, rotation, useSoftPinning, collision, fixedRot);
entity.detach();
entity.setCollision(true, true);
await entity.freezePosition(true);
entity.getVariable(key); // synced meta
entity.hasVariable(key);
await entity.isDeadAsync(); // exact read from the game thread
await entity.getSpeedAsync();
await entity.existsAsync();
setPos/setRot/destroyon other players are server-authoritative and returnfalse.
Player
const me = varion.Player.local;
varion.Player.get(id);
varion.Player.getByID(id);
varion.Player.getByRemoteID(id);
varion.Player.getByScriptID(scriptId);
varion.Player.all; // includes the local player
varion.Player.streamedIn; // streamed-in only
varion.Player.count;
varion.getPlayers(); // = Player.allKey player getters: name (remote), model, health, armour, currentWeapon, currentAmmo, currentWeaponComponents, currentWeaponTintIndex, moveSpeed, forwardSpeed, strafeSpeed, speedVector, aimPos, headRot, entityAimingAt, vehicle, seat, netOwner, isTalking, micLevel, plus state flags isAiming, isJumping, isShooting, isCrouching, isStealthy, isInRagdoll, isReloading, isEnteringVehicle, isLeavingVehicle, isOnLadder, isInMelee, isInCover, isParachuting, flashlightActive.
Player.local also exposes: weapon, ammoInClip, interiorId, maxHealth, maxArmour, dimension, isDead, isSpawned. Methods: hasWeaponComponent(weapon, component), getWeaponTintIndex(weapon).
Vehicle, Object, Ped
varion.Vehicle.get(id); // .all / .streamedIn / .count / .getByID /
// .getByRemoteID / .getByScriptID
varion.getVehicles();
varion.Object.get(id); // same set of statics
varion.getObjects();
varion.Ped.get(id); // same set of statics
varion.getPeds();Vehicle getters: model, speedVector, engineOn, engineHealth, petrolTankHealth, gear, rpm, steeringAngle, lockState, isDestroyed, handling, netOwner.
VirtualEntity
varion.VirtualEntity.get(id); // .all / .streamedIn / .getByRemoteID
varion.VirtualEntityGroup.get(id); // .all
varion.VirtualEntityGroup.cellKey(x, y, 100);Dimension-scoped queries
varion.getPlayersInDimension(dim);
varion.getVehiclesInDimension(dim);
varion.getObjectsInDimension(dim);
varion.getPedsInDimension(dim);
varion.getEntitiesInDimension(dim);Entity metadata
entity.getMeta(key); entity.setMeta(key, value); // local meta
entity.hasMeta(key); entity.deleteMeta(key);
entity.getSyncedMeta(key); entity.hasSyncedMeta(key); // server-synced
entity.getStreamSyncedMeta(key); // stream-synced
entity.getMetaDataKeys(); entity.getSyncedMetaKeys();Local entities (client only)
Created and owned entirely on the client, with no network ID and no server synchronization. Useful for decorative peds, objects and vehicles.
const ped = new varion.LocalPed(model, { x, y, z }, { x, y, z });
const obj = new varion.LocalObject(model, { x, y, z }, { x, y, z }, /* noOffset */ false);
const veh = new varion.LocalVehicle(model, { x, y, z }, { x, y, z });
obj.pos = { x, y, z }; // pos / rot / frozen / collision / visible are get/set
obj.frozen = false;
obj.destroy();About
scriptID: right after the constructorscriptIDis0andidis negative (a local id). Once the engine commits the entity into the world,scriptIDbecomes a non-zero GTA handle and thegameEntityCreate/baseObjectCreateevents fire. Checkentity.isSpawnedor wait forgameEntityCreatebefore calling native handles byscriptID.
varion.WeaponObject(model, pos, rot, ammo, showWorldModel) is a separate local weapon object with a destroy() method.
World objects
The Blip, Marker, Checkpoint, ColShape, TextLabel classes share statics all, count, get(id), getByID(id), getByRemoteID(id) (Blip also has getByScriptID).
const blip = varion.Blip.get(id);
blip.sprite = 1; blip.color = 5; blip.scale = 1.2; // get/set
blip.name = 'Shop'; blip.shortRange = true; blip.route = false;
blip.pos = { x, y, z }; // + radius/width/height, isRadius/isArea
const mk = varion.Marker.get(id);
mk.markerType = 1; mk.scale = 2; mk.color = { r, g, b, a };
const cp = varion.Checkpoint.get(id);
cp.checkpointType = 1; cp.radius = 3; cp.height = 2; cp.color = { r, g, b, a };
const cs = varion.ColShape.get(id); // colshapeType / radius / height /
// halfExtents / points / pos / dimension
// Local 3D text:
const label = new varion.TextLabel('Hello', 0, { x, y, z }, 1, { r:255,g:255,b:255,a:255 }, true, 0);
label.destroy();Global and local metadata
// Local (synthetic, client only)
varion.setMeta(key, value); varion.getMeta(key); varion.hasMeta(key);
varion.deleteMeta(key); varion.getMetaKeys();
// Server-synced global (read-only on the client)
varion.getSyncedMeta(key); varion.hasSyncedMeta(key); varion.getSyncedMetaKeys();
// Local player meta
varion.setLocalMeta(key, value); varion.getLocalMeta(key);
varion.hasLocalMeta(key); varion.deleteLocalMeta(key); varion.getLocalMetaKeys();
// Request a stream-synced meta set (sent to the server)
varion.setStreamSyncedMeta(key, value);
varion.deleteStreamSyncedMeta(key);Voice
varion.Voice.volume = 1; // 0–2
varion.Voice.muteInput = true; // mute your own microphone
varion.Voice.activationLevel = 37; // 0–100 (voice activation)
varion.Voice.noiseSuppressionEnabled = true;
varion.Voice.inputDevice = deviceUuid;
varion.Voice.activityInputEnabled = true;
varion.Voice.toggleInput(true); // manual push-to-talk
varion.Voice.mutePlayer(player); varion.Voice.unmutePlayer(player);
varion.Voice.isPlayerTalking(player); varion.Voice.isPlayerMuted(player);
varion.Voice.getPlayersInChannel(channelId);
varion.Voice.getAvailableInputDevices();
varion.Voice.clearLocallyMuted();
const ch = varion.Voice.getChannel(channelId); // or varion.VoiceChannel.getByID(id)
ch.spatial; ch.maxDistance; ch.volume; ch.members; ch.muted; ch.filter;
ch.getPlayers(); ch.isActive();
const filter = varion.AudioFilter.create('echo'); // by name or hash
varion.muteInput(true);
varion.toggleVoiceControls(true);
varion.clearLocallyMuted();Audio
const snd = varion.Audio.create(varion.assetUrl('mymode', 'sfx/click.wav'), 1);
snd.loop = false; snd.category = 'sfx';
await snd.play(); snd.pause(); snd.stop(); snd.seek(2);
snd.setVolume(0.5); snd.destroy();
snd.playing; snd.currentTime; snd.duration; snd.playbackRate;
varion.Audio.playFrontend(url, 1); // frontend (2D) sound
// 3D sound: add a world output
snd.addOutput(new varion.AudioOutputWorld({ x, y, z }, 50));
const cat = varion.AudioCategory.get('sfx'); cat.volume = 0.8;HTTP
const res = await varion.Http.get('/server-info.json');
// { ok, status, statusCode, url, text, body, headers }
const data = await varion.Http.getJson('/data.json'); // + res.json
const r = await varion.fetch('/api', { method: 'POST', body: JSON.stringify({}) });
const client = varion.HttpClient.create();
client.setExtraHeader('X-Auth', token);
await client.get(url); // + head/post/put/delete/patch/options/connect/traceRequests are allowed only to the host/port of the resource's manifest HTTP server or its asset-pack; the game port 7788 is blocked.
Resources
varion.Resource.all; // all resources
varion.Resource.current; // current one (in execution context)
varion.Resource.getByName('mymode'); // or varion.Resource.get('mymode')
varion.Resource.resolveAsset('mymode', 'ui/index.html');
varion.Resource.getPath('mymode', 'client/main.js');
varion.getResources(); // = Resource.all
varion.getResourceConfig('mymode'); // config object from the manifest
varion.getResourcePath('mymode', 'client/main.js');
varion.hasResource('mymode');
// Inter-resource exports (client-local, no server)
varion.exports.foo = () => 42; // register on YOUR resource
varion.exports = { foo, bar }; // or in bulk
varion.getResourceExports('other').foo(); // call another resource's export
const other = varion.require('other'); // throws if the resource is not foundURLs and assets
varion.httpUrl('cef/index.html'); // resource's base HTTP server
varion.assetUrl('mymode', 'ui/index.html'); // resource asset-pack
varion.streamUrl('mymode', 'vehicles/adder.ytd'); // resource stream assets
varion.streamCachePath('mymode', 'file.ytd'); // path in the local stream cacheStorage
varion.LocalStorage.set('token', 'value');
varion.LocalStorage.get('token');
varion.LocalStorage.has('token');
varion.LocalStorage.delete('token'); // = remove
varion.LocalStorage.clear(); // = deleteAll
varion.LocalStorage.save(); // persist to diskLocalStorage is saved automatically on resourceStop.
Files
await varion.File.write('cache:save.json', JSON.stringify(data));
const text = await varion.File.read('cache:save.json', 'utf8');
await varion.File.exists('cache:save.json'); // → boolean
await varion.File.delete('cache:save.json');
varion.File.resolvePath('resource:mymode/data.json');
varion.readFile(path); // aliases
varion.writeFile(path, data);Camera and screen
varion.getCamPos(); // { x, y, z }
varion.getCamRot(); // { x, y, z }
varion.getCamFov(); // number
varion.getCamForwardVector(); // { x, y, z }
varion.getScreenResolution(); // { x, y }
varion.screenToWorld(0.5, 0.5, 100); // synchronous (ray)
await varion.screenToWorldAsync(0.5, 0.5, 500); // exact game-thread pick
await varion.worldToScreenAsync(x, y, z); // { x, y, z } normalized
varion.worldToScreen(x, y, z); // synchronous (from cache)
varion.copyToClipboard('text');Stats
await varion.getStat('MP0_CASH'); await varion.setStat('MP0_CASH', 1000);
await varion.getStatFloat('name'); await varion.setStatFloat('name', 1.5);
await varion.getStatBool('name'); await varion.setStatBool('name', true);
varion.resetStat('name'); varion.resetAllStats(0);
varion.getPackedStat(index, 0); varion.setPackedStat(index, value, 0);
varion.getPackedStatBool(index, 0); varion.setPackedStatBool(index, value, 0);getStat* / setStat* go through the game bridge and return a Promise.
Permissions and screenshots
varion.Permission; // { None:0, ScreenCapture:1, WebRTC:2, Microphone:3, Clipboard:4, All:255 }
varion.PermissionState; // { Allowed:0, Denied:1, Unspecified:2, Failed:3 }
varion.getPermissionState(varion.Permission.ScreenCapture);
varion.hasPermission(varion.Permission.Microphone);
const state = await varion.requestPermission(varion.Permission.ScreenCapture);
const base64 = await varion.takeScreenshot(); // whole screen (base64 PNG)
const gameOnly = await varion.takeScreenshotGameOnly(); // game frame onlytakeScreenshot will request the ScreenCapture permission itself if needed.
WebSocket and Worker
const ws = new varion.WebSocketClient('wss://example/path');
ws.on('open', () => ws.send('hi'));
ws.on('message', (data) => {});
ws.on('close', (code, reason) => {});
ws.autoReconnect = true;
ws.addSubProtocol('json'); ws.setExtraHeader('X-Auth', token);
ws.start(); ws.stop(); ws.destroy();
varion.WebSocketReadyState; // { CONNECTING, OPEN, CLOSING, CLOSED }
const worker = new varion.Worker(varion.assetUrl('mymode', 'worker.js'));
worker.on('message', (data) => {});
worker.start(); worker.pause(); worker.resume(); worker.destroy();Discord
varion.Discord.isReady(); // readiness
varion.Discord.currentUser; // { id, name, discriminator, avatar } | null
varion.Discord.updateActivity({ details: 'In game', state: 'Freeroam', largeImageKey: 'logo' });
varion.Discord.getActivity();
varion.Discord.clearActivity();
await varion.Discord.requestOAuth2Token(appId);
varion.on('discord:ready', (user) => {});Handling and MapZoom
const hd = varion.HandlingData.getForName('adder'); // or .getForHash(hash)
hd.acceleration; hd.brakeForce; hd.maxTraction; /* ... */
hd.setField('acceleration', 0.5);
await hd.applyTo(vehicle);
varion.getVehicleHandling(vehicle);
varion.HandlingData.reloadVehiclePhysics(modelHash);
const zoom = varion.MapZoomData.get('MAIN_MAP');
zoom.fZoomScale = 1.2;
varion.MapZoomData.resetAll();Server, connection and diagnostics
varion.getServerHost(); varion.getServerIp(); varion.getServerPort(); varion.getServerName();
varion.getPing();
varion.isConnected();
varion.getConnectionPhase(); // 'idle' | 'ready' | ...
varion.isPaused(); varion.isMenuOpen(); varion.isRadarHidden();
varion.getFps(); varion.getTotalPacketsSent(); varion.getTotalPacketsLost();
varion.getLocale(); // varion.locale
varion.LicenseType; // { None, Rockstar, Steam, Epic, Xbox }
varion.getLicenseType(); varion.getLicenseHash();
varion.getGxtText('key'); // GXT label textWeather and config flags
varion.setWeatherCycle(['CLEAR', 'RAIN'], [10, 5]);
varion.setWeatherSyncActive(true);
varion.setConfigFlag('flagName', true);
varion.getConfigFlag('flagName');
varion.loadYtyp('name'); varion.unloadYtyp('name');Utilities
varion.Utils — vector math and helpers:
varion.Utils.distance(a, b); varion.Utils.squaredDistance(a, b);
varion.Utils.vectorLength(v); varion.Utils.normalize(v);
varion.Utils.dot(a, b); varion.Utils.cross(a, b);
varion.Utils.add(a, b); varion.Utils.sub(a, b);
varion.Utils.mul(v, s); varion.Utils.divide(v, s); varion.Utils.neg(v);
varion.Utils.lerp(a, b, t); varion.Utils.clamp(v, min, max);
varion.Utils.degToRad(d); varion.Utils.radToDeg(r);
varion.Utils.randomInt(min, max); varion.Utils.isNaN(v);
await varion.Utils.wait(500); // Promise delay
await varion.Utils.registerPedheadshotBase64(ped); // + registerPedheadshot3Base64 / *Transparent*
const b64 = await varion.getHeadshotBase64(headshotId);
const p = new varion.Profiler();
p.timeStart('x'); const ms = p.timeEnd('x');
varion.Profiler.fps; varion.Profiler.tickMs; varion.Profiler.nativeCalls;
varion.getGamepads(); // array of Gamepad
varion.Gamepad.get(0); // .connected / .axisLeftX / .isButtonPressed(btn)UI: WebView and RmlDocument
The full WebView reference is on the WebView page.
const view = varion.WebView.create({ url: varion.assetUrl('ui-pack', 'index.html'), overlay: true });
view.visible = true;
view.on('hud:ready', () => {});
view.emit('hud:setHealth', 200);
// RmlDocument is HTML/WebView-backed (there is no native RmlUi renderer; a .html
// source loads into a WebView, a .rml source shows a notice):
const doc = new varion.RmlDocument(varion.assetUrl('ui-pack', 'menu.html'));
doc.show(false, true); doc.hide(); doc.update();Stream events
varion.on('stream:ready', (...mounts) => {
// one argument per mounted resource
for (const mount of mounts) varion.log(`${mount.name} -> ${mount.streamBase}`);
});
varion.on('stream:updated', (...mounts) => {});
varion.on('stream:mounted', (stats) => {}); // { registered, existing, failed, ... }Each mount is an object { name, streamBase } (IStreamResourceMount). It is not a resource-name string; a full remount delivers every resource at once (for a single resource, a single object).
GTA Natives
// Call by name (camelCase)
varion.natives.setEntityVisible(scriptId, true, false);
varion.natives.freezeEntityPosition(scriptId, true);
// Call by hash
varion.natives.invoke('0x262B14F48D29DE80', pedHandle, 11, 1, 0, 0);
varion.natives.invokeFloat('0xEEF059FAD016D209', pedHandle);
varion.natives.invokeVector3('0x1EB9E7DF7E839CAF', x, y, z, heading, rx, ry, rz);
// Real return values come from the async variants
const health = await varion.natives.invokeAsync('GET_ENTITY_HEALTH', scriptId);varion.game is the same bridge plus joaat. A synchronous invoke returns a placeholder for most read natives; for a real value use invokeAsync / invokeFloatAsync / invokeStringAsync / invokeVector3Async. See the GTA natives page for details.
Client JS must not be treated as trusted from a gameplay/security standpoint. Validate important gameplay, economy and permission actions on the server.
Source backing
This page is built from the runtime files sdk/client-js-runtime/injected/core-bootstrap.js, events-bootstrap.js, entities-bootstrap.js, services-bootstrap.js, world-bootstrap.js, interaction-bootstrap.js, platform-bootstrap.js, dispatch-bootstrap.js, mvalue-bootstrap.js and client/src/stream/CStreamResourceClient.cpp.