> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/decal-v-graffiti-and-vehicle-sticker/configuration.md).

# Configuration

Everything lives in `rm_decalv/cfg.lua` (open file, comments included) and `rm_decalv/decals.lua` (the catalog, covered on [Adding decals](/resources/decal-v-graffiti-and-vehicle-sticker/adding-decals.md)). The important knobs, in the order they appear:

{% hint style="info" %}
**Updating from an older version?** The config was reorganized into groups in 3.3.0, but every key from older configs (`cfg.disableVehicleStickers`, `cfg.renderDistance`, `cfg.interfaceSettings`, `cfg.limits.maxSize`, ...) keeps working unchanged — and when both spellings exist, the old one wins. You do not need to touch an existing `cfg.lua`.
{% endhint %}

## Locale & bridges

```lua
cfg.locale = 'en'          -- locales/<locale>.json
cfg.bridge = {
    framework = 'auto',    -- 'auto' | 'qb' | 'esx'
    notification = 'ox_lib', -- any file name from bridge/notification/
}
cfg.nui = { primaryColor = '#f15d38' } -- accent color of the whole interface
```

Every notification adapter is an open file in `rm_decalv/bridge/notification/` and its file name is the value for `cfg.bridge.notification`; the annotation in `cfg.lua` lists the set bundled with your version. Copy an adapter to integrate a resource we don't cover.

### Placement ranges

```lua
cfg.placement = {
    minSize = 0.5,     -- sliders and ctrl+scroll offer this range...
    maxSize = 5.0,
    sizeStep = 0.5,
    minOpacity = 0.4,  -- lowest opacity players can pick (0.4 = 40%)
}
```

The server enforces the same ranges on save, so this block is the single source of truth; there is no separate server-side size cap to keep in sync.

## Feature toggles

```lua
cfg.features = {
    vehicleStickers = true,  -- false: the catalog only places graffiti
    graffiti = true,         -- false: the catalog only places vehicle stickers

    gifDecals = false,       -- admin GIF import + animated rendering
    maxGifDuis = 8,          -- distinct GIFs a client renders per session

    httpImageUrls = false,   -- allow plain http:// sources for admin imports
}
```

{% hint style="warning" %}
Every distinct GIF runs its own browser instance on **every client**, and that's real memory per GIF. Keep the feature admin-only and the cap low.
{% endhint %}

## Rendering

```lua
cfg.rendering = {
    distance = 50,           -- synced to clients, also caps placement range

    graffitiBudget = 128,    -- most graffiti drawn at once (farthest dropped first)
    vehicleBudget = 24,      -- vehicles with stickers rendered at once

    farDecalDistance = 25.0, -- beyond this, vehicles render a reduced set...
    farDecalCap = 2,         -- ...of this many decals

    occlusionCulling = true, -- decals nobody can see release their engine load
}
```

`occlusionCulling` keeps the live decal load low by releasing decals that are off screen or blocked from view for a few seconds; they return as soon as they are visible again. It ships enabled; set it to `false` if you suspect it in a visual issue.

## Zones & model rules

* `cfg.restrictedZones`: decals can't be placed inside these (admins bypass). Every attempt fires the `onRestrictedAttempt` hook, see [Events & Exports](/resources/decal-v-graffiti-and-vehicle-sticker/events-and-exports.md).
* `cfg.blacklistModels`: stickers can **never** go on these vehicle models (emergency fleet by default). To *reserve* models for a faction instead, use `vehicleModels` on a catalog block, see [Adding decals](/resources/decal-v-graffiti-and-vehicle-sticker/adding-decals.md). Unoptimized vehicle models that overload the decal engine also belong here: the [mesh probe](/resources/decal-v-graffiti-and-vehicle-sticker/troubleshooting.md) finds them for you and prints ready-to-paste lines.

Both zone lists (`cfg.restrictedZones` and `cfg.vehicleOwnerCheck.bypassZones` below) accept spheres plus the parameter shapes of ox\_lib zones and PolyZone, so definitions you already have paste over unchanged:

```lua
{ coords = vec3(441.0, -982.0, 30.7), radius = 60.0 }                       -- sphere
{ coords = vec3(...), size = vec3(40.0, 60.0, 20.0), rotation = 45.0 }      -- box, ox_lib style
{ coords = vec3(...), length = 60.0, width = 40.0, heading = 45.0, minZ = 25.0, maxZ = 45.0 } -- box, PolyZone style
{ points = { vec3(...), vec3(...), vec3(...) }, thickness = 20.0 }          -- poly, ox_lib style
{ points = { vec2(...), vec2(...), vec2(...) }, minZ = 25.0, maxZ = 45.0 }  -- poly, PolyZone style
```

The checks run server side with the resource's own math; neither ox\_lib zones nor PolyZone needs to be installed for this.

## Entry points

```lua
cfg.commands = {
    create = 'decals',            -- opens the catalog
    catalog = 'decal_catalog',    -- read-only browser (customers window-shop)
    clear_sticker = 'clear_sticker',
    clear_graffiti = 'clear_graffiti',
    admin = 'decals_admin',       -- admin panel

    disabled = false,             -- removes all commands
    adminsOnly = false,           -- keeps them, but only admins may use them
}

cfg.items = {
    create = 'paint_spray',
    clear_sticker = 'scraper',
    clear_graffiti = 'white_spray',

    disabled = true,
    consumeOnUse = false,         -- consume the item on use
}
```

## Permissions & protection

```lua
cfg.adminList = { }                   -- license/steam/fivem/citizenid; ace 'command' also counts

cfg.vehicleOwnerCheck = {
    create = true,                    -- must own the vehicle to sticker it
    clear = false,                    -- ...and/or to clear it
    bypassJobs = { ['mechanic'] = true },
    bypassZones = {
        -- { coords = vec3(-347.3, -133.6, 39.0), radius = 25.0, jobs = { 'mechanic' } },
    },
}

cfg.onlyOwnersCanClear = true         -- creator / vehicle owner / admin; false = anyone
```

`bypassZones` is how tuning shops work on customer cars: inside the area the ownership check is skipped for everyone, or only for the listed jobs/gangs.

## Server-side limits

```lua
cfg.limits = {
    maxSize = 10.0,                   -- absolute decal size cap
    saveCooldown = 3000,              -- ms between saves per player
    maxDecalsPerVehicle = 5,          -- mirrored decals count as two; admins bypass
    densityMax = 5,                   -- graffiti within densityRadius; 0 disables
    densityRadius = 10.0,
}
cfg.inactiveStickerDeleteInterval = '2 WEEK' -- prune stickers of unseen vehicles at boot
```

These are enforced on the server no matter what a client sends. Decal size and opacity limits live in `cfg.placement` at the top of the file; the interface and the server validation share them.

## Editor fonts

`cfg.editorFonts` lists the fonts offered by the admin decal editor's text tool. They load at runtime; nothing ships in the resource. Both forms work, and `name` must match the font's real family name:

```lua
{ name = 'Bangers', url = 'https://fonts.googleapis.com/css2?family=Bangers&display=swap' }, -- Google Fonts embed URL
{ name = 'MyFont',  url = 'https://example.com/fonts/myfont.woff2' },                        -- direct file URL
```

### Advanced

A few optional keys exist for fine-tuning; the defaults suit most servers, add them only if you need them:

```lua
cfg.limits.maxPlaceDistance = 60.0             -- placement range cap; defaults to cfg.rendering.distance + 10
cfg.limits.maxClearDistance = 30.0             -- how far away a decal can still be cleared
cfg.maxImageUrlBytes        = 15 * 1024 * 1024 -- size cap for admin URL imports and editor image layers
cfg.dev                     = true             -- enables the diagnostic /decals_meshprobe command (see Troubleshooting)
```

Raising `maxImageUrlBytes` also moves the editor save ceiling to match, so a large imported layer can still bake and save. Saved decals stay small regardless of import size, because the editor export is capped and re-encoded.
