Write a separate resource
Last updated on
How a resource outside opx_infinity uses OPX//77 — server and client exports, public server events, opx_lib — what the player state bag no longer carries, and what it still cannot do.
For a resource that is not part of opx_infinity (your own my_jobs, for example). It uses the curated surface opx_infinity publishes for creators. If you need a module's full contract (every function of character, inventory…), write a module instead.
What a separate resource can use
| Surface | Side | Use it for | Read |
|---|---|---|---|
| Server exports | server | read a player's data, money, job, items, down state, the roster, the jobs, the catalogue, vehicles, doors; change money, jobs, gangs, duty, your own metadata (online or not), items, stashes, chat, keys, vehicles, doors; register items, use handlers and crafting benches; a toast; a bar the server judges; revive | Server exports |
| Client exports | client | open a menu, form or panel, show a toast, run a progress bar, put rows on the eye and hints on the key strip, open your crafting bench, play an animation, read the local player; subscribe to OPX client events | Client exports |
| Public server events | server | react when a character loads, money moves, an item is used, a car is bought… | Server events |
| Player state bag | — | nothing since PR #93: never a name to a stranger | below |
| opx_lib | client | input, markers, zones, RPC helpers… | opx_lib |
| Ordinary commands | server | running an unrestricted command | below |
Start with For creators: the answer every export gives, the allowlist the operator controls (writes are refused until your resource is in SERVER.EXPORTS.WRITERS), and when you must await.
A minimal example
resource "my_jobs"
version "1.0.0"
dependency "opx_infinity"
server_script "server.lua"
client_script "client.lua"
permissions { "network.events" }exports('OnOpxEvent', function(event, payload) TriggerEvent(event, payload) end)
RegisterCommand('jobmenu', function()
exports.opx_infinity:OpenMenu({
title = 'ODD JOBS',
closeOnSelect = true,
items = { { id = 'deliver', label = 'Deliver a parcel' } },
})
end)
AddEventHandler('opx:on:menu:action', function(p)
if p.action == 'select' and p.itemId == 'deliver' then
TriggerServerEvent('my_jobs:start')
end
end)-- the operator adds my_jobs = true to SERVER.EXPORTS.WRITERS
RegisterNetEvent('my_jobs:start', function()
local playerId = source
CreateThread(function()
local job = exports.opx_infinity:GetJob(playerId) -- a read: sync is fine
if not job.ok then return end
-- ... run the job, check it on the server, then pay:
local pending = Open77.exports.call('opx_infinity', 'AddMoney',
playerId, 'EDDIES', 200, 'odd job')
local answer = pending and pending:await()
if answer and answer.ok then
exports.opx_infinity:SendChat(playerId, { text = 'Paid 200 €$', kind = 'info' })
end
end)
end)
AddEventHandler('opx:on:character:unloaded', function(playerId, payload)
-- forget anything you kept for payload.citizenId
end)SendChat is a write too, so it needs the same allowlist entry; it answers at once, so the sync form works.
The player state bag
The character module writes no key on the player's replicated state bag any more (PR #93). The old keys (citizenId, name, charInfo, job, gang, username, life) reached every client in the bucket, the whole city, and are cleared. Ask the server instead, and keep the rule the framework keeps: never a name to a stranger — do not relay a character's name, citizen id, job or balance to a client whose player has not met that character.
local res = exports.opx_infinity:HasJob(playerId, 'police')
if res.ok and res.value then
-- ...
end- The local player's own character: the client export
GetPlayerData. - Another player: the server exports (
GetPlayerData,HasJob,IsDown…), gated bySERVER.EXPORTS.READ. - Platform names outside OPX:
Open77.players.nearbyand the platform chat/scoreboard still show account names; that is a platform setting.
opx_lib
A client library any resource may use.
dependency "opx_lib"local Lib = require('@opx_lib')
print(Lib.Manifest()) -- the permissions line your manifest needsCommands
A server resource whose manifest declares runtime.commands can queue an unrestricted command line with ExecuteCommand(line). Restricted (staff) commands are refused, and the answer goes to the server log.
Things to avoid
| Do not | Why |
|---|---|
Listen to or raise opx:in:* events | Server TriggerEvent is host-wide, so you can see them, but they are private and change without notice. Use the public events. |
Write to opx_infinity's tables directly | It bypasses its rules, hooks and audit, and its in-memory copy overwrites your change. Read-only access is possible with database.access. |
| Trust a client | The client exports act on one player's screen. Charge, give and check on the server. |
What you still cannot do
| You want to | Status |
|---|---|
| Veto an action (hooks) | OPX.Hooks runs inside opx_infinity only: a veto answers inside the action, synchronously, and a call into another resource cannot be bounded there. |
| Hear any OPX client event | Only the events on the Subscribe list. |
| Open an impound or garage screen for a player, or gate a garage on a job | No export yet. |
Run an item shop of the shops module | shops is the clothing shop. Sell with your own menu and RemoveMoney + AddItem, or a crafting bench. |
| Register a weapon at runtime | RegisterItem refuses weapons and ammunition: they need a TweakDB record and a class from data/weapons.lua. |
What used to be on this list and is not any more: the panel, the key strip and the eye (client exports), a bar the server starts and judges (StartProgress), an offline character's metadata (SetMetadata with a citizen id), runtime items and use handlers (RegisterItem, RegisterUsableItem).
A request/answer between your client and your server is Lib.Callback in opx_lib, over Open77.net: it is between your own two halves and needs no export.
Code written for the old opx77_* exports does not work as is; see From opx77_* to opx_infinity.
Write a module
Add a new module to opx_infinity step by step — declaration, config, text, server and client halves, a contract, a network event, a command and the manifest lines.
Convert from FiveM
How FiveM, ESX and QBCore habits map onto Open77 and opx_infinity — what stays, what changes, and where each familiar framework call now lives.