Grand opening25% off everything with FAM-261023-261025-UL4B9this weekendTick it in your cart Halloween Edition36 maps, limited runlimited runShop the drop

FREE TOOL · ZONES

FiveM zone creator

Draw the zone on the real GTA 5 map and copy it as code that runs. A circle, a box with a heading, or a free polygon you click out corner by corner — then pick your library and the call appears underneath with the coordinates already in it. ox_lib zones, PolyZone, ox_target and qb-target are all written from the same drawing, and you can paste a zone you already have back in to move it.

The syntax for each library was taken from its own source rather than from memory: ox_lib v3.40.3, ox_target v1.18.1, PolyZone v2.6.2 and the current qb-target templates. Nothing is uploaded — the drawing and the code generation both happen in your browser.

1 map pixel ≈ 1.47 game units

Loading the map…

Click to move the centre. Drag to pan, Ctrl and scroll to zoom.

Shape

Height

For a circle these two only set the centre, z 31: a sphere's height is its radius. ox_lib and ox_target both test in three dimensions, and PolyZone's CircleZone takes no minZ or maxZ at all — this page sets useZ = true so it tests as a sphere, because with useZ = false it is a cylinder of infinite height.

The ground figure is this site's own height sample, a median of the world objects it catalogues, so treat it as a starting point and check the number in game with /coords or an admin menu before you ship a thin zone.

What the zone is for

ox_lib and PolyZone zones are plain areas: they tell you when a player crosses the edge and nothing more. Pick ox_target or qb-target below if you want a third-eye option on it, and the label and event fields appear here.

Centre
195.2, -933.8
Footprint
314 m²
Height
5 units
ox_lib zones — client.lua

Client-side. Needs ox_lib started before your resource and shared_script '@ox_lib/init.lua' in your fxmanifest. thickness is the full height of the prism, centred on the Z of the points.

local zone = lib.zones.sphere({
    name = 'my_zone',
    coords = vec3(195.2, -933.8, 31.0),
    radius = 10.0,
    debug = true,

    onEnter = function(self)
        print(('entered %s'):format(self.name))
    end,

    onExit = function(self)
        print(('left %s'):format(self.name))
    end,
})

Edit a zone you already have

Paste the call out of a resource and it comes back onto the map, where you can move it, reshape it and export it to any of the four libraries. It reads lib.zones.*, PolyZone:Create, BoxZone:Create, CircleZone:Create, the ox_target add*Zone exports and the qb-target Add*Zone exports. A bare list of coordinates works too.

HOW TO USE IT

Draw it, set the height, paste the call

Pick the shape, click the map, and set minZ and maxZ to the band the zone should actually cover. Then copy the call into a client script in your resource. The only thing the page cannot do for you is confirm the height: the Z it offers is a sample, so stand in the spot in game once and check it.

A polygon zone in ox_lib, with the debug flag kept in one place

Client-side. Needs ox_lib started before your resource and shared_script '@ox_lib/init.lua' in your fxmanifest. thickness is the full height of the prism and it is centred on the Z of the points, so these points at 12.33 with thickness 4 cover 10.33 to 14.33.

-- client.lua
-- One flag for every zone in the file, so debug never ships by accident.
local drawZones = false

local shop = lib.zones.poly({
    name = 'vespucci_shop',
    points = {
        vec3(-1222.4, -906.8, 12.33),
        vec3(-1215.1, -902.4, 12.33),
        vec3(-1210.6, -910.2, 12.33),
        vec3(-1218.0, -914.6, 12.33),
    },
    thickness = 4.0,
    debug = drawZones,

    onEnter = function(self)
        lib.showTextUI('[E] Open the shop')
    end,

    onExit = function(self)
        lib.hideTextUI()
    end,
})

-- The zone is a handle, not a one-shot call: keep it and you can
-- turn the drawing on from a command while you are placing things.
RegisterCommand('shopzone', function()
    shop:setDebug(not shop.debug)
end, false)
The height trap: one outline, one zone per floor

Client-side. Needs PolyZone's files imported in dependency order: client.lua, BoxZone.lua, EntityZone.lua, CircleZone.lua, ComboZone.lua. Each zone name has to be unique, which is why the floor carries its own.

-- The height trap, written out. One outline, three floors, three zones.
-- A single tall zone would fire the ground-floor shop for somebody
-- standing on the second-floor balcony above it.
local outline = {
    vector2(-1222.4, -906.8),
    vector2(-1215.1, -902.4),
    vector2(-1210.6, -910.2),
    vector2(-1218.0, -914.6),
}

local floors = {
    { name = 'tower_g',  minZ = 11.8, maxZ = 15.2 },
    { name = 'tower_1',  minZ = 15.2, maxZ = 18.6 },
    { name = 'tower_2',  minZ = 18.6, maxZ = 22.0 },
}

for _, floor in ipairs(floors) do
    local zone = PolyZone:Create(outline, {
        name = floor.name,
        minZ = floor.minZ,
        maxZ = floor.maxZ,
        gridDivisions = 30,
        debugPoly = false,
    })

    zone:onPlayerInOut(function(isPointInside)
        if isPointInside then
            print(('on %s'):format(floor.name))
        end
    end, 500)
end
The Z-height trap, in one line

A polygon with no height is a column that reaches from the sea bed to the sky. It works perfectly at ground level, and then fires for somebody on the roof, in the basement car park, or in an interior that happens to sit under your outline.

Why a thin zone feels broken

The test uses one point, at the ped's feet. A two-unit slab is fine on flat tarmac and starts dropping out on a kerb, a ramp or a staircase. Three units above the floor and one below is a sane starting shape.

Circle or polygon

A circle is one subtraction and a compare, so it is the cheapest test there is. A polygon costs more but follows the real boundary — and PolyZone's gridDivisions exists precisely to make that cost nearly flat for big outlines.

Names have to be unique

PolyZone, and therefore qb-target, keys its zones by name across the whole client. Two zones called shop in two resources collide, and the symptom is one of them silently never firing.

QUESTIONS

About FiveM zones

Why does my polygon zone work on the street but not on a roof?
Because a polygon is a flat outline and the library gives it a height from somewhere else. In PolyZone that height is minZ and maxZ; leave them out and the zone is infinitely tall, so a player on the roof of the building inside your outline is also inside the zone. Set them, and the slab only covers the band you named — which is exactly what you want for a shop floor and exactly what breaks a roof or a basement, because the ped coordinate used for the test is at the feet, not the chest. ox_lib takes the same idea from the other end: thickness is the full height of the prism and it is centred on the Z of the points, so points at z 30 with thickness 4 cover 28 to 32 and nothing else. For a building with floors, make one zone per floor rather than one tall zone, otherwise a player two storeys up triggers the ground-floor shop.
What is the difference between ox_lib zones and PolyZone?
PolyZone is the older stand-alone resource: you import its files into your own fxmanifest in dependency order and call PolyZone:Create, BoxZone:Create or CircleZone:Create, then poll with onPlayerInOut or ask isPointInside yourself. ox_lib zones are a module inside ox_lib, so there is nothing to import beyond ox_lib itself, and they are event driven — you hand the zone an onEnter, an onExit and an inside function and the library runs one grid-backed loop for every zone on the client instead of one thread per zone. The shapes are also described differently: PolyZone takes minZ and maxZ, ox_lib takes a thickness around the points, and PolyZone boxes take length before width while ox_lib boxes take a single size vector. This page writes whichever of the two you pick from the same drawing.
Do I need ox_target or qb-target as well?
Only if you want the zone to be something a player aims at and clicks. ox_lib and PolyZone zones are areas: they tell you when somebody crosses the boundary, and nothing more. ox_target and qb-target build an interaction on top of a zone — the third eye goes blue inside it and a labelled option appears. Under the hood ox_target hands your table straight to ox_lib zones, so size, rotation and thickness mean the same thing there, and qb-target wraps PolyZone, so its second table is the PolyZone options and its third is the list of interaction options. If you only need to know when a player walks in, use the zone library directly and skip the targeting resource.
In a PolyZone box, which number is the length and which is the width?
BoxZone:Create takes the centre first, then the length, then the width — in that order — and the same goes for the qb-target AddBoxZone export that wraps it. The length runs along the box’s own Y axis and the width along its own X axis, and the heading option rotates the whole thing afterwards. Swapping the two is the most common reason a box that looked right in a debug draw turns out to be rotated ninety degrees from what you wanted. ox_lib has no such ordering problem: it takes one size vector, where x is the width, y is the length and z is the height.
How accurate are the coordinates this page gives me?
The X and Y come from a measured map-to-world transform this site checked against 6,954 street lights and traffic objects taken from the game: at the values it uses, 81.9% of them land on a road pixel against a road coverage of 10.5%. That puts a click within roughly one map pixel, about 1.5 game units — good enough to lay out a zone, and not a survey. The Z is a different matter: the figure shown is a median height sample of the world objects this site catalogues, not a raycast, so treat it as a starting point and confirm it in game before you ship a zone only a metre and a half tall.
Can I turn debug drawing off without editing every zone?
Keep it in one variable. All four libraries take the debug flag as a plain boolean on the zone, so a local drawZones = false at the top of the file and debug = drawZones on each zone lets you flip the whole file at once. It matters more than it looks: ox_target warns in the console for every zone created with debug enabled, and PolyZone spawns a drawing thread per zone that runs every frame, so a shipped resource that forgot to turn it off costs real client frames for nothing.

ALSO FREE

Tools that go with this one

Picked because they touch the same files, natives or part of the game.

FROM THE SHOP

What we build when it is not free

The free tools and the paid work come out of the same place. These are the lines closest to what this page is about.

Grand opening: 25% off every product with FAM-261023-261025-UL4B9, until 25 October. It is applied at checkout.

Orange EMBER FiveM-compatible minimap for GTA 5 — google style — showing Los Santos street detail
Minimaps

Minimaps for FiveM

Twelve colour palettes across three base map styles, each one a drop-in resource. Add your logo and your district labels in the editor.

From $8.0036 finished mapsBrowse the minimaps
An animated neon Vinewood banner on the hillside of a FiveM server
Vinewood signs

Vinewood signs for FiveM

Your server name standing on the hillside in real 3D geometry. It ships as a pure map resource with no script, so there is nothing to break.

From $20.0082 typefacesOpen the sign studio