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:

lua
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:

lua
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:

lua
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

  • coords is the center of a box or sphere. Get it with GetEntityCoords(PlayerPedId()) while standing there, or from a coordinate tool.
  • size for a box is width, length and height as a vec3, centered on coords.
  • rotation turns the box around its vertical axis, in degrees. Use the heading of the building or door.
  • radius is 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:

lua
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,
})
  • onEnter and onExit run once per crossing. Use them for notifications, text UI, loading or unloading something.
  • inside runs 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:

lua
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 onEnter and onExit rather than inside.
  • Turn off debug in 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 →

Keep reading