# Body armour in FiveM: values, items, vests and persistence

> How body armour works in FiveM: SetPedArmour and max armour, ox_inventory and QBCore vest items, visible vests on component 9, saving armour and cheat checks.

Source: https://fam-tools.com/guides/fivem-body-armor-vests · FAM Tools · published 2026-09-19 · updated 2026-09-19 · about 12 min

**Short answer.** Armour is a value from 0 up to the player's maximum, 100 by default: `SetPedArmour(ped, n)` sets it, `AddArmourToPed` adds to it, `GetPedArmour` reads it and `SetPlayerMaxArmour(PlayerId(), n)` changes the cap. ox_inventory ships an `armour` item that sets 100; QBCore's qb-smallresources has `armor` (75) and `heavyarmor` (100, with a visible vest). The visible vest is clothing component 9. Armour is not saved by default everywhere, so store it on disconnect, and check it on the server with `GetPedArmour` under OneSync.

## Key points

- Armour is separate from health and capped by `SetPlayerMaxArmour` (default 100).
- The vest you see is clothing component 9; the number and the clothing are independent.
- ox_inventory's `armour` item sets armour to 100 if the player is below it.
- Save armour yourself if your framework does not, and restore it after load.
- Melee weapons carry the `ArmourPenetrating` flag, so vests do less against them.

Armour is the other half of weapon balance. You can tune every gun on the server and still end up with one-sided fights if one player has a full vest and the other has none, or if armour quietly resets on every relog. Armour in GTA is a simple number, but the item that grants it, the vest people can see, and whether it survives a restart are all left to your scripts.

## How armour works

A player ped has two pools: health and armour. Player health tops out at 200 by default, and armour runs from 0 to the player's maximum armour, which is 100 unless a script changes it. Incoming weapon damage is taken from armour first, so a full vest is effectively extra hit points against gunfire.

*Armour natives*

| Native | Side | What it does |
| --- | --- | --- |
| `SetPedArmour(ped, amount)` | Client and server | Sets armour to a value |
| `AddArmourToPed(ped, amount)` | Client | Adds to the current value |
| `GetPedArmour(ped)` | Client and server | Reads the current value |
| `SetPlayerMaxArmour(player, value)` | Client | Changes the cap (default 100) |
| `GetPlayerMaxArmour(player)` | Client and server | Reads the cap |

Note that `SetPlayerMaxArmour` takes a player id, not a ped. `SetPedArmour` and `GetPedArmour` exist on the server as well, which is what makes server-side armour logic possible with OneSync.

Two exceptions to "armour absorbs gunfire" matter for balance. Melee weapons such as the knife, bat and machete have the `ArmourPenetrating` flag in their weapons.meta, and headshots are governed by the critical hit rules, not the vest. If a vest does not seem to help against headshots, look at [headshot damage settings](https://fam-tools.com/guides/fivem-headshot-damage-settings), where `SetPedSuffersCriticalHits` lives.

## Giving armour from a script

The simplest use is a one-liner, but do it where it cannot be abused. Setting armour in a client event that any client can trigger is an open door for cheaters, so apply it from the server.

*server.lua*

```lua
local function giveArmour(src, amount)
    local ped = GetPlayerPed(src)
    if ped == 0 then return end
    SetPedArmour(ped, math.min(amount, 100))
end
```

If you raise the cap for a special role, the client must call `SetPlayerMaxArmour(PlayerId(), 150)` first, otherwise values above 100 are clamped. Most roleplay servers keep 100 as the maximum for everyone and vary how much each vest gives.

## The ox_inventory armour item

ox_inventory ships an item called `armour`, labelled "Bulletproof Vest", weighing 3000 and not stackable. Using it plays a clothing animation for 3.5 seconds; its client handler only runs if the player's armour is below 100, and when the item is used it sets the maximum and the armour to 100.

*ox_inventory/data/items.lua (as shipped)*

```lua
['armour'] = {
    label = 'Bulletproof Vest',
    weight = 3000,
    stack = false,
    client = {
        anim = { dict = 'clothingshirt', clip = 'try_shirt_positive_d' },
        usetime = 3500
    }
},
```

For several vest tiers, add your own items and a client export that follows the same pattern: check the current armour, let ox_inventory consume the item, then apply the value.

*ox_inventory/data/items.lua*

```lua
['vest_light'] = {
    label = 'Light Vest',
    weight = 1500,
    stack = false,
    client = {
        anim = { dict = 'clothingshirt', clip = 'try_shirt_positive_d' },
        usetime = 2500,
        export = 'my_armour.vest',
    },
},
```

*my_armour/client.lua*

```lua
local VESTS = { vest_light = 50, vest_heavy = 100 }

exports('vest', function(data, slot)
    local amount = VESTS[data.name]
    if not amount or GetPedArmour(PlayerPedId()) >= amount then return end

    exports.ox_inventory:useItem(data, function(used)
        if used then
            SetPlayerMaxArmour(PlayerId(), 100)
            SetPedArmour(PlayerPedId(), amount)
        end
    end)
end)
```

The pattern of `useItem` with a callback is the same one ox_inventory uses for its own armour item, so the item is only removed when the use actually completes. Item fields are covered in [ox_inventory items.lua](https://fam-tools.com/guides/ox-inventory-items-lua).

## QBCore armour items

In QBCore the vests live in qb-smallresources as usable items. `armor` gives 75 armour and refuses if the player already has 75 or more; `heavyarmor` gives 100 and, unless disabled in the config, also puts a vest drawable on the player. Both remove the item on the server and set armour there with `SetPedArmour(GetPlayerPed(source), value)`. There is also a police-only `/resetarmor` command that takes the heavy vest off and returns the item.

QBCore keeps an `armor` value in player metadata, but how and when it is written depends on your resource versions, so check that armour actually survives a relog on your server before assuming it does.

## The visible vest (component 9)

Armour as a number and a vest you can see are unrelated. The visible vest is ped clothing component 9, listed as Kevlar in the MP freemode component list. Setting armour does not add a vest, and adding a vest does not add armour; scripts that want both do both.

*client.lua*

```lua
local saved

local function wearVest(drawable, texture)
    local ped = PlayerPedId()
    saved = { GetPedDrawableVariation(ped, 9), GetPedTextureVariation(ped, 9) }
    SetPedComponentVariation(ped, 9, drawable, texture, 0)
end

local function removeVest()
    if not saved then return end
    SetPedComponentVariation(PlayerPedId(), 9, saved[1], saved[2], 0)
    saved = nil
end
```

Drawable numbers differ between the male and female freemode models and between game builds, which is why QBCore's heavy armour code checks the character's gender before picking one. Find the numbers for your server in a clothing menu rather than copying them from another server. Many servers also remove the vest drawable automatically when armour reaches zero, so a "shot-through" vest disappears.

## Saving armour across relogs

If armour resets to zero on reconnect, players lose vests they paid for; if it resets to full, vests become free. When your framework does not save it, store it yourself. This example uses the resource key-value store and the player's license identifier.

*server.lua*

```lua
local function key(src)
    local license = GetPlayerIdentifierByType(src, 'license')
    return license and ('armour:' .. license)
end

AddEventHandler('playerDropped', function()
    local src = source
    local k = key(src)
    if not k then return end
    SetResourceKvpInt(k, GetPedArmour(GetPlayerPed(src)))
end)

RegisterNetEvent('armour:restore', function()
    local src = source
    local k = key(src)
    if not k then return end
    local value = GetResourceKvpInt(k)
    if value > 0 then
        SetPedArmour(GetPlayerPed(src), math.min(value, 100))
    end
end)
```

Trigger `armour:restore` from the client after your framework has finished loading the character and spawning the ped; armour set on a ped that is about to be replaced is lost. If your server has multiple characters per license, key the value by character id instead.

## Checking armour on the server

Armour is a favourite target for menus because it is a single number. With OneSync the server can read it and compare it with what is possible.

*server.lua*

```lua
CreateThread(function()
    while true do
        Wait(10000)
        for _, id in ipairs(GetPlayers()) do
            local ped = GetPlayerPed(id)
            if ped ~= 0 and GetPedArmour(ped) > 100 then
                print(('[armour] %s has %d armour'):format(GetPlayerName(id), GetPedArmour(ped)))
            end
        end
    end
end)
```

A value above the maximum is a strong signal. A player who goes from 0 to 100 without using an item is another; you can track that by recording when your own scripts grant armour. As with any detection, log first and act once you trust it; the wider approach is in [server-side weapon validation](https://fam-tools.com/guides/fivem-weapon-anticheat-validation).

## Balancing armour against your weapons

- **Vest strength shapes time-to-kill** more than small damage tweaks. A full vest is a large block of extra effective health, so test your weapons against armoured and unarmoured targets separately.
- **Make vests cost something.** Weight in the inventory, a price, a crafting recipe or job-only access keep full armour from being the default state.
- **Consider tiers.** Light vests for civilians and gangs, heavy vests for police and tactical units, gives each side a reason to engage differently.
- **Remember melee.** Because melee weapons penetrate armour, a knife fight ignores vests; decide whether that is realistic enough or whether to scale melee damage down, as covered in [the melee guide](https://fam-tools.com/guides/fivem-melee-weapons-guide).

## Questions

### How do I give armour in FiveM?

Call `SetPedArmour(ped, 100)`. With OneSync it also works on the server with `SetPedArmour(GetPlayerPed(source), 100)`, which is safer than a client event.

### What is the maximum armour in FiveM?

100 by default. Raise the cap with `SetPlayerMaxArmour(PlayerId(), value)` on the client before setting a higher value.

### How do I make the vest visible on the player?

Set clothing component 9 with `SetPedComponentVariation(ped, 9, drawable, texture, 0)`. The visible vest and the armour value are separate.

### Why does armour reset when a player reconnects?

Armour is a ped value and is not saved unless your framework or a script stores it. Save it on `playerDropped` and restore it after the character has loaded.

### Does armour protect against headshots and knives?

Headshots follow the critical hit rules rather than the vest, and melee weapons carry the `ArmourPenetrating` flag, so vests help far less against both.

## Related

- https://fam-tools.com/guides/fivem-weapon-balance-roleplay
- https://fam-tools.com/guides/fivem-headshot-damage-settings
- https://fam-tools.com/guides/fivem-weapon-damage-modifier
- https://fam-tools.com/guides/ox-inventory-items-lua
- https://fam-tools.com/guides/fivem-police-armory-setup
