ox_lib zones: lib.zones.box, sphere and poly with onEnter and onExit
How to use ox_lib zones in FiveM: lib.zones.box, sphere and poly with coords, size, rotation and debug, onEnter, onExit, inside, removing zones, and ox_target.
You need to know when a player walks into a shop, a garage or a restricted area, without checking the distance in a loop every frame. ox_lib zones do that for you: you describe a shape, and it calls your function when the player enters or leaves. This guide covers the three shapes, the callbacks, removal and when to use ox_target instead.
Zones need ox_lib as a dependency, loaded with @ox_lib/init.lua. If it is missing, see ox_lib init.lua not found.
Zone types
There are three, all under lib.zones and all client side.
Box, a rectangle with a height, good for rooms and doorways:
local zone = lib.zones.box({
coords = vec3(441.0, -981.0, 30.7),
size = vec3(4.0, 6.0, 3.0),
rotation = 90.0,
debug = true,
onEnter = function(self)
print('entered the box')
end,
onExit = function(self)
print('left the box')
end,
})Sphere, a radius around a point, good for open areas:
local zone = lib.zones.sphere({
coords = vec3(441.0, -981.0, 30.7),
radius = 3.0,
debug = true,
onEnter = function() print('in range') end,
onExit = function() print('out of range') end,
})Poly, an outline of points for irregular shapes such as a yard or a parking lot:
local zone = lib.zones.poly({
points = {
vec3(400.0, -1000.0, 29.0),
vec3(420.0, -1000.0, 29.0),
vec3(420.0, -970.0, 29.0),
vec3(400.0, -970.0, 29.0),
},
thickness = 4.0,
debug = true,
onEnter = function() print('in the yard') end,
onExit = function() print('left the yard') end,
})For a poly, thickness is how tall the shape is around the points' height. Keep the points at ground level and the thickness big enough to include players on stairs.
Coords, size and rotation
coordsis the center of a box or sphere. Get it withGetEntityCoords(PlayerPedId())while standing there, or from a coordinate tool.sizefor a box is width, length and height as avec3, centered oncoords.rotationturns the box around its vertical axis, in degrees. Use the heading of the building or door.radiusis for spheres.
The easiest way to get these right is debug = true: the zone is drawn in the world, so you can walk through it, change a number and restart the resource until it fits. Turn debug off before release, as it draws every frame.
Callbacks: onEnter, onExit, inside
Three callbacks cover the cases:
local zone = lib.zones.sphere({
coords = vec3(441.0, -981.0, 30.7),
radius = 4.0,
onEnter = function(self)
lib.showTextUI('[E] Open shop')
end,
onExit = function(self)
lib.hideTextUI()
end,
inside = function(self)
if IsControlJustReleased(0, 38) then
print('pressed E inside the zone')
end
end,
})onEnterandonExitrun once per crossing. Use them for notifications, text UI, loading or unloading something.insideruns every tick while the player is inside. Anything that must be checked each frame, like a keypress, goes here, and nothing outside the zone costs you.
Keep inside short: no database calls, no lib.callback.await, no Wait in a loop. For the keypress detail, see markers and text UI.
Removing zones
lib.zones.box returns an object. Keep it and remove it when the area stops mattering:
local shopZone
local function createZone()
shopZone = lib.zones.box({
coords = vec3(441.0, -981.0, 30.7),
size = vec3(4.0, 4.0, 3.0),
onEnter = function() lib.showTextUI('[E] Shop') end,
onExit = function() lib.hideTextUI() end,
})
end
local function removeZone()
if shopZone then
shopZone:remove()
shopZone = nil
end
end
AddEventHandler('onResourceStop', function(resource)
if resource == GetCurrentResourceName() then
removeZone()
lib.hideTextUI()
end
end)Remember to hide any UI the zone was showing: if the player is inside when you remove it, onExit may not be called for them.
You can also ask a zone whether a point is inside it, with zone:contains(coords), for example to check the player's position when a menu opens.
Zones vs ox_target
They solve different problems:
| lib.zones | ox_target | |
|---|---|---|
| Question | "Is the player in this area?" | "Is the player looking at this and pressing a button?" |
| Output | Your callbacks | A list of options on the target menu |
| Use for | Safe zones, speed limits, music areas, spawning NPCs when near | Doors, ATMs, shop counters, objects |
If the player should choose to interact, use ox_target zones, and compare it with older systems in qb-target vs ox-target. If something should happen automatically when they arrive, use a lib zone. Combining them is common: a zone loads a ped when you arrive, and ox_target gives the ped its options.
Performance
Zones are cheap compared with a manual #(coords - playerCoords) < 3.0 loop, because ox_lib checks them in one central loop and only runs your inside function where it applies. A few habits keep it that way:
- Create zones once, not in a loop or every time a menu opens.
- Do not create hundreds of tiny zones if a single bigger one would do.
- Put the work in
onEnterandonExitrather thaninside. - Turn off
debugin production.
Check the result with resmon, as in high resmon ms.
Checklist
| Symptom | Fix |
|---|---|
| Zone never fires | Use the center coords, the right size and rotation; set debug = true to see it |
lib is nil |
Add @ox_lib/init.lua to shared_scripts |
| Text UI stays after leaving | Call lib.hideTextUI() in onExit and when the zone is removed |
| Keypress not detected | Check it in inside, not onEnter |
| Resource restart leaves zones behind | Call zone:remove() on onResourceStop |
| High resmon | Keep inside light and disable debug |
Quick answers
What is the difference between lib.zones and ox_target?
lib.zones tells you when the player is inside an area, so you run your own code. ox_target adds an interaction (a look-and-click option) to an entity or zone. Use zones for areas, ox_target for things the player presses.
How do I show a zone while building it?
Set debug = true in the zone options. It draws the shape in the world, so you can fix coordinates, size and rotation. Turn it off when you are done.
How do I remove a zone?
Keep the object that lib.zones.box returns and call zone:remove(). Do it when the resource stops or when the area is no longer needed.
Scripts that skip this problem
Shop CreatorBuild a shop in under a minute — owners, employees, vaults and robberies included.View script →
Advanced BoostingTablet-driven vehicle boosting: contracts from class D to S+, crews and a live queue.View script →
CCTV Security CamerasPlaceable cameras, a live multi-view tablet and printed evidence photos.View script →