<!-- fivemad.com, Markdown source of https://fivemad.com/docs/development/navmesh -->
> Source: https://fivemad.com/docs/development/navmesh
> Section: Development
> Tags: navmesh, ynv, peds, pathfinding, natives, streaming
> Last updated: 2026-09-25
> Site map for assistants: https://fivemad.com/llms.txt

# The navmesh at runtime

What a resource can and cannot do to the GTA V navmesh: the 17 navmesh natives, blocking objects, disabling areas, streaming regions, and why peds refuse to walk on a custom map.

Peds walk on a navmesh. It is not the same thing as the path node network that traffic drives on, it is not collision, and it is not something a script can build. This page is about what your resource can actually do to it while the server is running, and about the question everybody hits eventually: why peds refuse to walk on a custom map.

## The whole native surface is 17 natives, and every write is subtractive

An exhaustive search of the Cfx.re native list for `NAVMESH` or `NAV_MESH` returns exactly 17 natives. Here they are, grouped by what they do.

**Masking what is already there**

| Native | What it does |
| --- | --- |
| `ADD_NAVMESH_BLOCKING_OBJECT` | Adds a box peds and vehicles route around. Returns a handle. |
| `UPDATE_NAVMESH_BLOCKING_OBJECT` | Moves or resizes one you added, by that handle. |
| `REMOVE_NAVMESH_BLOCKING_OBJECT` | Removes it. |
| `DOES_NAVMESH_BLOCKING_OBJECT_EXIST` | Asks whether a handle is still live. |
| `DISABLE_NAVMESH_IN_AREA` | Turns the navmesh off over a box outright. |

**Streaming, not content**

| Native | What it does |
| --- | --- |
| `ADD_NAVMESH_REQUIRED_REGION` | Asks the streamer to load the navmesh around a point. |
| `REMOVE_NAVMESH_REQUIRED_REGIONS` | Drops every request you made. |
| `ARE_ALL_NAVMESH_REGIONS_LOADED` | Are the requested regions in yet. |
| `_IS_NAVMESH_REQUIRED_REGION_OWNED_BY_ANY_THREAD` | Unnamed native, takes no arguments. |

**Asking questions**

| Native | What it does |
| --- | --- |
| `IS_NAVMESH_LOADED_IN_AREA` | Is navmesh streamed in over this box. |
| `GET_NUM_NAVMESHES_EXISTING_IN_AREA` | How many navmesh files cover this box, loaded or not. |
| `GET_NAVMESH_ROUTE_RESULT` | The state of the route a ped is following. |
| `GET_NAVMESH_ROUTE_DISTANCE_REMAINING` | How far is left, and whether the path is ready. |

**Issuing tasks**

| Native | What it does |
| --- | --- |
| `TASK_FOLLOW_NAV_MESH_TO_COORD` | Walk a ped to a point, on the navmesh. |
| `TASK_FOLLOW_NAV_MESH_TO_COORD_ADVANCED` | The same with more arguments. |
| `TASK_VEHICLE_GOTO_NAVMESH` | Drive a vehicle to a point using the navmesh. |
| `SET_VEHICLE_ACTIVE_FOR_PED_NAVIGATION` | Whether peds treat a vehicle as something to path around. |

Read that list again and notice what is not in it. **Nothing creates a polygon. Nothing moves one. Nothing retags one.** Not one of these natives takes a vertex, a triangle or a flag word. Every write among them takes a box and subtracts.

> [!NOTE]
> Two natives often listed as navmesh natives are not. `_ADD_TACTICAL_ANALYSIS_POINT` and `_CLEAR_TACTICAL_ANALYSIS_POINTS` are in the MISC namespace and belong to `CTacticalAnalysis`, which is combat cover scoring. Neither has NAVMESH in its name and neither touches the navmesh.

## Blocking objects: the tool you usually want

If peds are walking through your new building, or across a bridge you removed, a blocking object is the answer. It is a box, it takes effect immediately and it needs no file.

```lua title="client.lua"
local blocker = 0

AddEventHandler('onClientResourceStart', function(resource)
    if resource ~= GetCurrentResourceName() then return end
    -- x, y, z is the centre. width, length, height are the full extents.
    blocker = AddNavmeshBlockingObject(
        -820.0, -1075.0, 11.0,   -- centre
        24.0, 18.0, 6.0,         -- width, length, height
        0.0,                     -- heading, in degrees
        false,                   -- bPermanent
        7                        -- eBlockingObjectFlags
    )
end)

AddEventHandler('onResourceStop', function(resource)
    if resource ~= GetCurrentResourceName() then return end
    if blocker ~= 0 then
        RemoveNavmeshBlockingObject(blocker)
        blocker = 0
    end
end)
```

The last argument is a flag set, documented on the native itself:

| Value | Name | Blocks |
| --- | --- | --- |
| 0 | `BLOCKING_OBJECT_DEFAULT` | The default. |
| 1 | `BLOCKING_OBJECT_WANDERPATH` | Wander paths. |
| 2 | `BLOCKING_OBJECT_SHORTESTPATH` | Regular shortest paths. |
| 4 | `BLOCKING_OBJECT_FLEEPATH` | Flee paths. |
| 7 | `BLOCKING_OBJECT_ALLPATHS` | All paths. |

> [!WARNING]
> **Only 32 blocking objects can exist at once**, and nothing manages them for you. Keep every handle, remove them in `onResourceStop`, and do not add one per frame or per spawned prop. Once you are past 32 the next call quietly gives you nothing.

`bPermanent` is the eighth argument. False lets the game clean the object up on a streaming pass; true keeps it until you remove it.

## Disabling a whole area

For anything larger than a handful of boxes, switch the area off instead of stacking blockers.

```lua title="client.lua"
local x1, y1, z1 = -900.0, -900.0, 0.0
local x2, y2, z2 = -750.0, -750.0, 80.0

DisableNavmeshInArea(x1, y1, z1, x2, y2, z2, true)
-- Ped paths are a separate switch. Flip both, or peds keep spawning on
-- the pavement you just made unwalkable.
SetPedPathsInArea(x1, y1, z1, x2, y2, z2, false, 0)
```

Put both back when you are done, and from `onResourceStop` as well, otherwise the area stays off for everyone who was already connected:

```lua title="client.lua"
DisableNavmeshInArea(x1, y1, z1, x2, y2, z2, false)
SetPedPathsBackToOriginal(x1, y1, z1, x2, y2, z2, 0)
```

The native documentation for `DISABLE_NAVMESH_IN_AREA` says it in one line: use this for a large area, use `ADD_NAVMESH_BLOCKING_OBJECT` for a small one.

## Is there a navmesh here at all?

This is the check worth running before you debug anything else. The two natives answer different questions and both matter.

```lua title="client.lua"
RegisterCommand('navcheck', function()
    local p = GetEntityCoords(PlayerPedId())
    local x1, y1, z1 = p.x - 40.0, p.y - 40.0, p.z - 20.0
    local x2, y2, z2 = p.x + 40.0, p.y + 40.0, p.z + 20.0

    -- Is it streamed in right now?
    local loaded = IsNavmeshLoadedInArea(x1, y1, z1, x2, y2, z2)
    -- How many navmesh files cover this box at all, loaded or not?
    local existing = GetNumNavmeshesExistingInArea(x1, y1, z1, x2, y2, z2)

    print(('navmesh loaded: %s, files covering this box: %d'):format(tostring(loaded), existing))
end, false)
```

`GET_NUM_NAVMESHES_EXISTING_IN_AREA` returning 0 is the real answer for a custom map built out over the sea: the game ships no navmesh there, so there is nothing to stream and nothing to load. `IS_NAVMESH_LOADED_IN_AREA` returning false with a non zero count just means it has not streamed in yet, which you can fix:

```lua title="client.lua"
AddNavmeshRequiredRegion(x, y, 150.0)

while not AreAllNavmeshRegionsLoaded() do
    Wait(0)
end

-- ... do the thing that needed it ...

RemoveNavmeshRequiredRegions()
```

That streams a file the game already has. It does not make one.

## Why peds will not walk on your custom map

Because there is no navmesh under it, and no native can make one.

`TASK_FOLLOW_NAV_MESH_TO_COORD` needs navmesh to path on. Its own documentation is explicit about what happens when it cannot find a route: *"Sometimes a path may not be able to be found. This could happen because there simply isn't any way to get there... In this case the ped will simply stand still."* There is no error, no event and no return value on the task itself. The ped stands there.

To find out rather than guess, read the route back:

```lua title="client.lua"
local ped = PlayerPedId()
TaskFollowNavMeshToCoord(ped, x, y, z, 1.0, 20000, 1.0, 0, 0.0)

Wait(500)
local state, remaining, ready = GetNavmeshRouteDistanceRemaining(ped)
print(('route state %s, ready %s, %s m left'):format(state, tostring(ready), remaining))
print('route result ' .. GetNavmeshRouteResult(ped))
```

There is one flag worth knowing in the task's own flag set, `ENAV_GO_FAR_AS_POSSIBLE_IF_TARGET_NAVMESH_NOT_LOADED = 4`. Its documentation says it makes the ped get as close as possible on whatever navmesh is loaded, and then adds the line that matters: **the navmesh must still be loaded at the path start.** It does not rescue a ped standing on a platform with no navmesh under it.

So the honest options for a custom map are:

- Build it over ground the game already has navmesh for, which is most of the map.
- Ship a `.ynv` for it, with everything below in mind.
- Move peds with something that is not the pathfinder: `TASK_GO_STRAIGHT_TO_COORD`, a sequence of waypoints you author yourself, or attaching them to something.

## Streaming a .ynv, and the trap in it

A `.ynv` in a resource's `stream/` folder needs **no `data_file` line, no `files{}` entry and no script**. The client takes the extension, looks up the streaming module for it, takes the file name without the extension as a slot name, and swaps the slot's handle to point at your file. That is the same generic path every streamed asset goes through.

**A stock name is the clean case.** Ship `navmesh[123][102].ynv` and it replaces the game's file of that name. The original handle is pushed onto a stack first, so when the resource stops the game's own file comes back, and restarting the resource works.

**A new name is not the clean case.** Two things happen:

1. A new slot gets created in the navmesh store, and **navmesh slots are never removed**. FiveM's own source skips them on cleanup with a comment saying that re-registration crashes. So the slot name leaks for the rest of the game session. Restarting the resource works, precisely because nothing tried to remove the slot.
2. Nothing is going to ask for it. The game requests navmesh by region, through the `navmesh[x][y]` naming and the grid behind it. A slot called `mymap_navmesh` is registered and never requested.

> [!CAUTION]
> Treat "stream a `.ynv` under a new name" as unproven rather than as a feature. The registration is real and is in the source. That the game ever loads it is not something the source shows, and the naming scheme argues against it.

One more thing about timing: the cleanup that would release a slot only runs at session shutdown, not at resource stop. So a server that restarts a navmesh resource repeatedly during a session is accumulating state either way.

## The file itself

If you do need to look at a `.ynv`, the [YNV Navmesh Viewer](/tools/navmesh-viewer) opens one over the real map and takes it apart: the polygons coloured by flag, the full flag set of any polygon with the real bit names, the sector tree, the portals and cover points, and a count of which neighbouring cells the file's edges point into.

That last number is why there is a viewer and not an editor. An edge in a `.ynv` names the polygon on the other side by an area id and a polygon **index**, and where that area is a different cell, the index is a position in the neighbouring file's own polygon list. Across the stock build that is 2.75% of edge parts, roughly 350 per file. Add or remove one polygon and everything after it renumbers, which silently repoints the edges of up to four files you do not have open.

For the road network that traffic drives on, which is a completely separate set of files, see the [YND Traffic Path Editor](/tools/ynd-editor). Path nodes can be edited one file at a time, and that tool writes them.
