Server owners only touch the `editable/` folder.
Everything else in the library can be updated without losing your changes.

## Files

| File | Purpose |
| --- | --- |
| `editable/shared/config.lua` | All settings (`ObsiConfig`). Shared with clients, so no secrets here |
| `editable/server/webhooks.lua` | Discord webhook URLs (server only) |
| `editable/server/framework.lua`, `editable/client/framework.lua` | Custom framework functions and per-function patches |
| `editable/server/inventory.lua`, `editable/client/inventory.lua` | Custom inventory functions and per-function patches |
| `editable/server/notify.lua`, `editable/client/notify.lua` | Custom notification backend |

## General

```lua title="editable/shared/config.lua"
ObsiConfig.Locale = 'en'          -- 'en' | 'pl' (other codes fall back to 'en')
ObsiConfig.Debug = false          -- extra console output
ObsiConfig.VersionCheck = true    -- check GitHub releases on start
```

## Framework, inventory and notifications

```lua
ObsiConfig.Framework = 'auto'     -- 'auto' | 'esx' | 'qb' | 'qbx' | 'custom'
ObsiConfig.Inventory = 'auto'     -- 'auto' | 'ox_inventory' | 'qb-inventory' | 'codem-inventory' | 'jaksam_inventory' | 'custom'
ObsiConfig.Notify = 'obsi'        -- 'obsi' (built-in) | 'esx' | 'qb' | 'ox' (ox_lib) | 'custom'
```

With `Inventory = 'auto'`, the first started inventory is used.
Without any of them, the framework's own item functions are used, which also covers ps-inventory and lj-inventory.

## Integrations

`'auto'` detects the started resource, `'none'` disables the integration, or name a resource to force it.

```lua
ObsiConfig.Integrations = {
    Banking = 'auto',     -- 'Renewed-Banking' | 'okokBanking' | 'qb-banking' | 'qb-management' | 'esx_society' | 'none'
    VehicleKeys = 'auto', -- 'qbx_vehiclekeys' | 'qb-vehiclekeys' | 'wasabi_carlock' | 'mk_vehiclekeys' | 'none'
    Target = 'auto',      -- 'ox_target' | 'qb-target' | 'none'
    Clothing = 'auto',    -- 'illenium-appearance' | 'fivem-appearance' | 'qb-clothing' | 'esx_skin' | 'none'
    Fuel = 'auto',        -- 'ox_fuel' | 'LegacyFuel' | 'cdn-fuel' | 'ps-fuel' | 'lc_fuel' | 'native'
    Sound = 'auto',       -- 'xsound' | 'nui' (built-in player only) | 'none'
}
```

## Police and admins

```lua
ObsiConfig.PoliceJobs = { police = true, sheriff = true }   -- counted by ObsiLib.job.copsOnline()
ObsiConfig.CopsRequireDuty = false                          -- count only officers on duty

ObsiConfig.Admin = {                                        -- defaults for ObsiLib.player.isAdmin
    Ace = 'command',                                        -- ACE permission, false to skip
    Groups = { 'admin', 'superadmin', 'god' },              -- framework groups
    Identifiers = {},                                       -- 'license:...', 'discord:...', character identifiers
}
```

## UI

```lua
ObsiConfig.UI = {
    Accent = '#e5122e',
    Notify = {
        Position = 'top-right', -- 'top-right' | 'top-left' | 'top' | 'right' | 'left' | 'bottom-right' | 'bottom-left'
        Duration = 5000,        -- ms, when a notification sets none
        MaxVisible = 5,
    },
    Progress = {
        Offset = 14,            -- distance from the bottom of the screen in vh
        CancelKey = 73,         -- control id (73 = X)
        CancelLabel = 'X',
    },
    TextUI = {
        Position = 'left',      -- 'left' | 'right' | 'top' | 'bottom'
    },
}
```

## Sounds

```lua
ObsiConfig.Sound = {
    DefaultVolume = 0.5,
    DefaultRange = 20.0,       -- metres, positional sounds
    MaxBroadcastRange = 250.0, -- metres, cap for client requested broadcasts
    BroadcastCooldown = 1000,  -- ms between broadcasts per player
    AllowRemoteUrls = false,   -- let clients broadcast http(s) URLs to other players
}
```

## Vehicles and plates

```lua
ObsiConfig.Plates = {          -- ObsiLib.vehicle.generatePlate
    Letters = 4, Numbers = 4,
    LetterSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
    NumberSet = '0123456789',
    Separator = '',
}

ObsiConfig.VehicleTables = {   -- saveOwned and plate uniqueness
    esx = { table = 'owned_vehicles', owner = 'owner', plate = 'plate', vehicle = 'vehicle', stored = 'stored' },
    qb = { table = 'player_vehicles', plate = 'plate' },
}
```

## Stashes, offline jobs and item images

| Setting | Purpose |
| --- | --- |
| `ObsiConfig.Stash` | Fallback stashes for inventories without their own: KVP prefix, session length (600 s), default slots (50) and weight, and the qb-inventory stash tables |
| `ObsiConfig.OfflineJobTables` | Tables used by `ObsiLib.job.setOffline` when the player is offline |
| `ObsiConfig.ItemImages` | Image URL templates for `ObsiLib.items.image`, per inventory |
| `ObsiConfig.ItemCatalogCacheSeconds` | How long the item catalogue is cached (60 s) |
| `ObsiConfig.TargetIcons` | Extra short icon names for `ObsiLib.target`, mapped to FontAwesome classes |
| `ObsiConfig.DevTools.CoordsCommand` | The coordinates pointer command, `obsicoords` by default (`false` disables it) |

## Discord webhooks

Webhook URLs live in `editable/server/webhooks.lua`, which clients never download:

```lua title="editable/server/webhooks.lua"
ObsiWebhooks = {
    main = 'https://discord.com/api/webhooks/...',
    vehicles = '',
}

ObsiWebhookStyle = {
    username = 'Obsi Scripts',
    avatar = nil,
    color = 0xE5122E,
}
```

See [Discord logs](/lib/server-api/notifications-and-logs#discord-logs) for how to send to them.
