F5 StudioF5 Studio
Skip to main content

Configuration

F5 Board configuration is split into five files inside config/:

FileLoaded asContents
config.luasharedGeneral options, board catalog, placer, editor, blips, debug
framework_config.luasharedFramework selection, bridge logging, permission mapping
target_config.luasharedTarget system override (qb-target / ox_target)
inventory_config.luasharedInventory adapter selection, custom callbacks
discord_webhook_config.luaserver-onlyDiscord webhook URLs, embed colors, queue tuning
Server-only

discord_webhook_config.lua is loaded only on the server. Never include webhook URLs in any other config file — they would leak to clients.

General

config/config.lua
Config.Locale = 'en'
OptionTypeDefaultDescription
Localestring'en'Translation file from locales/<code>.lua. See Localization for the full list of supported codes.

Board Content Limits

Hard caps on how much content one board can hold. The server enforces these — clients cannot bypass them.

config/config.lua
Config.MaxBoardsPerPlayer = 5
Config.BoardNameMaxLength = 32
Config.MaxNotesPerBoard = 100
Config.MaxStringsPerBoard = 200
Config.MaxStrokesPerBoard = 500
OptionTypeDefaultDescription
MaxBoardsPerPlayernumber5Maximum number of placed boards a single player can own at the same time
BoardNameMaxLengthnumber32Max length of the board's display name (used on the blip and in the admin panel)
MaxNotesPerBoardnumber100Max sticky notes (text + image) per board
MaxStringsPerBoardnumber200Max connecting strings between notes per board
MaxStrokesPerBoardnumber500Max free-hand drawings per board
tip

For investigation / detective servers MaxNotesPerBoard = 100 is usually plenty. If you raise the limits, also raise Config.Dui.idleTimeoutMs to keep busy boards from despawning.

Performance & Sync

config/config.lua
Config.MaxRenderDistance     = 80.0
Config.SyncRadius = 200.0
Config.MaxEditDistance = 8.0

Config.MaxStrokesPerBatch = 8
Config.MaxPointsPerBatch = 1500
Config.StrokeBatchWindowMs = 50
Config.StrokeBatchCooldownMs = 50
Config.MutationCooldownMs = 150
OptionTypeDefaultDescription
MaxRenderDistancenumber80.0Beyond this distance the board model is despawned client-side
SyncRadiusnumber200.0Radius (m) used to broadcast board events to nearby players. Spatial grid keeps server cost flat regardless of total board count
MaxEditDistancenumber8.0If the player walks beyond this distance while editing, the editor closes
MaxStrokesPerBatchnumber8Max number of strokes packed into a single network message while drawing
MaxPointsPerBatchnumber1500Max total points across all batched strokes per network message
StrokeBatchWindowMsnumber50How long the client collects strokes before sending a batch (ms)
StrokeBatchCooldownMsnumber50Minimum gap between two outgoing stroke batches (ms)
MutationCooldownMsnumber150Minimum gap between any two content mutations from one player (add/remove/edit note, string, etc.)
Tuning

Strokes are batched aggressively so drawing fast feels instant locally and stays network-cheap. Lower StrokeBatchWindowMs if you want smoother live-drawing for other observers; raise it if you see net traffic spikes.

World

config/config.lua
Config.BoardCollision = false
OptionTypeDefaultDescription
BoardCollisionbooleanfalseIf true, placed boards block players and vehicles. If false, players walk through them

Map Blips

Each placed board creates a per-owner minimap blip with a randomly chosen color from the palette.

config/config.lua
Config.BlipSprite  = 793
Config.BlipScale = 0.85
Config.BlipDisplay = 4

Config.BlipPalette = {
1, 2, 3, 5, 17, 25, 27, 38, 46, 50, 59, 65, 73, 75, 76, 84,
}
OptionTypeDefaultDescription
BlipSpritenumber793GTA blip sprite ID. See FiveM blip list
BlipScalenumber0.85Blip scale (1.0 = default GTA blip size)
BlipDisplaynumber4Display mode: 2 = minimap only, 4 = map + minimap, 6 = map only
BlipPalettenumber[](16 IDs)Pool of GTA blip color IDs. The server picks an unused color per owner when possible, falls back to wrapping around when the palette is exhausted

/board Command

The /board command opens the board catalog — a NUI grid where the player picks a model to place.

config/config.lua
Config.Command = {
name = 'board',
enabled = true,
suggestion = 'ui_cmd_board_help',

access = 'admin',
permission = 'admin',
jobs = nil,

requestCooldownMs = 700,
placeCooldownMs = 400,
requestTimeoutMs = 3000,

requireItem = false,
}
OptionTypeDefaultDescription
namestring'board'Command name (without slash)
enabledbooleantrueMaster switch. false/board is disabled
suggestionstring'ui_cmd_board_help'Locale key for the chat suggestion text
accessstring'admin'Access mode: 'admin', 'job', or 'everyone'. Unknown value fails closed (treated as 'admin')
permissionstring'admin'Permission node for access='admin', also acts as admin bypass when access='job'
jobstable|nilnilJob whitelist for access='job'. Same schema as Config.AllowedJobs (see below)
requestCooldownMsnumber700Cooldown for opening the catalog (ms)
placeCooldownMsnumber400Cooldown between consecutive placements (ms)
requestTimeoutMsnumber3000Server response timeout (ms); on timeout the player sees a "server did not respond" notification
requireItembooleanfalseIf true, the catalog only lets the player place boards they actually carry. The item is consumed on placement. If false, placement is free for anyone who passes the gate

See Commands for full access-mode examples.

Item Use — Allowed Jobs

Independent gate for using a board item from the inventory (CreateUseableItem). Useful when you want, for example, only police officers to be able to use the corkboard item directly, while /board stays admin-only.

config/config.lua
Config.AllowedJobs = nil

nil or empty table → item use is open to everyone (default).

Two accepted shapes:

Config.AllowedJobs = { 'police', 'ambulance', 'sheriff' }

All listed jobs, all grades.

FormMeaning
jobname = trueAll grades
jobname = { N }Only grade N
jobname = { A, B, C }Whitelist of grades
jobname = { min = N }Grade >= N
jobname = { max = N }Grade <= N
jobname = { min = A, max = B }Range [A..B] inclusive
Pitfalls
  • Don't use nil as a value in the map — Lua silently drops nil entries. Use true for "all grades".
  • Mixing range (min/max) with a whitelist ({ N, M }) is not supported — the range wins.
  • Mixed shape (array entries + string keys in one table) is not supported. Pick one form.
  • Malformed value (e.g. a plain string 'police' inside a map) fails closed and logs an INIT warning at startup.

/badmin Command

Admin panel — list, preview, teleport-to, and delete any board on the server.

config/config.lua
Config.AdminCommand = {
name = 'badmin',
enabled = true,
suggestion = 'admin_command_help',
permission = 'admin',
ownerCacheTtlMs = 30000,
}
OptionTypeDefaultDescription
namestring'badmin'Command name
enabledbooleantrueMaster switch
suggestionstring'admin_command_help'Locale key for chat suggestion
permissionstring'admin'Permission node required to open the panel
ownerCacheTtlMsnumber30000Offline-owner resolution cache TTL (ms). For offline owners the panel looks up charinfo from the database; results are cached for this window

Board Catalog

The list of board models exposed in the catalog and as usable inventory items.

config/config.lua
Config.Boards = {
{ model = 'prop_cork_board', item = 'corkboard', label = 'ui_board_cork' },
{ model = 'prop_muster_wboard_01', item = 'chalkboard', label = 'ui_board_chalk' },
{ model = 'prop_muster_wboard_02', item = 'chalkboard_stand', label = 'ui_board_chalk_stand' },
{ model = 'p_planning_board_01', item = 'planning_stand', label = 'ui_board_planning_stand' },
{ model = 'p_planning_board_02', item = 'planning_board', label = 'ui_board_planning' },
{ model = 'p_planning_board_03', item = 'planning_board_v2', label = 'ui_board_planning_v2' },
{ model = 'prop_w_board_blank_2', item = 'whiteboard', label = 'ui_board_whiteboard' },
{ model = 'prop_w_board_blank', item = 'greyboard', label = 'ui_board_grey' },
{ model = 'prop_b_board_blank', item = 'brownboard', label = 'ui_board_brown' },
}
FieldTypeDescription
modelstringGTA prop name
itemstringInventory item name (CreateUseableItem)
labelstringLocale key for the displayed name

To remove a board variant simply delete the entry. To add your own, you need both a GTA model name and a matching surface geometry entry in Config.PropModels (next section).

Per-Model Surface Geometry

Each board model has a different surface pivot, size and orientation. This table tells the renderer where the drawable surface sits relative to the prop's pivot.

config/config.lua
Config.PropModels = {
prop_cork_board = {
surfaceForward = 0.0134,
surfaceUp = 0.0036,
surfaceWidth = 2.5196,
surfaceHeight = 1.8367,
},
-- ... (one entry per model)
}
FieldTypeDescription
surfaceForwardnumberForward offset from the model pivot to the visible surface (m)
surfaceUpnumberVertical offset from the pivot to the surface center (m)
surfaceWidthnumber|nilSurface width (m). If omitted, derived automatically from the prop's bounding box
surfaceHeightnumber|nilSurface height (m). If omitted, derived automatically
Custom Board Models

Use the companion tool f5_board_calibrate to obtain these four values without trial and error. It spawns the prop in front of you, auto-measures the bounding box, lets you tune surfaceForward / surfaceUp / surfaceWidth / surfaceHeight against a live 3D wireframe overlay, and exports a paste-ready Config.PropModels block. See the calibration workflow for the full step-by-step.

DUI (Content Rendering)

The board content (notes, strings, drawings) is rendered offscreen by a CEF browser (DUI) and projected onto the prop surface as a texture.

config/config.lua
Config.Dui = {
width = 1024,
height = 1024,
renderRadius = 25.0,
fadeMs = 0,
fadeBandStart = 22.0,
fadeBandEnd = 25.0,
idleTimeoutMs = 30000,
}
OptionTypeDefaultDescription
width / heightnumber1024 / 1024DUI texture resolution (px). Higher → sharper, more VRAM
renderRadiusnumber25.0Max distance at which the DUI is active (m). Beyond this the prop is rendered without content
fadeBandStartnumber22.0Distance at which the smooth fade-out begins (m)
fadeBandEndnumber25.0Distance at which the content is fully invisible (m)
idleTimeoutMsnumber30000A DUI that goes idle (player walked away, but board is still in range) is deactivated after this window to free VRAM
fadeMsnumber0Legacy field, unused — actual fade is driven by fadeBandStart / fadeBandEnd
Heavy Servers

On servers with many boards visible at once, lower width/height to 768 — content remains readable from interaction range but each board uses 56% less VRAM. The fade band is independent so you can also shrink renderRadius to 20.0 to despawn DUIs more aggressively.

Placer

Settings for the placement mode (the ghost preview you control with mouse + scroll + arrows).

config/config.lua
Config.Placer = {
rotationStep = 5.0,
heightStep = 0.05,

heightMin = -2.0,
heightMax = 3.0,

snapToSurface = false,
snapTo15Deg = true,

ghostAlpha = 145,
ghostTintValid = { r = 255, g = 255, b = 255 },
ghostTintBlocked = { r = 230, g = 70, b = 70 },

raycastFlags = 1 | 16,
modelYawOffset = 0.0,
pitchSign = 1.0,
}
OptionTypeDefaultDescription
rotationStepnumber5.0Degrees rotated per scroll tick
heightStepnumber0.05Meters added/subtracted per arrow tick
heightMin / heightMaxnumber-2.0 / 3.0Min/max height offset from the raycast hit point (m)
snapToSurfacebooleanfalseDefault state of "snap to surface" mode. Player toggles it in-session with F
snapTo15DegbooleantrueDefault state of "snap rotation to 15°" mode. Toggled in-session with G
ghostAlphanumber145Ghost transparency (0–255). 0 = invisible, 255 = solid
ghostTintValidrgbwhiteGhost color when the position is legal
ghostTintBlockedrgbredGhost color when the position is blocked (collides, out of range, no surface)
raycastFlagsnumber1 | 16GTA raycast bitmask: 1 = world geometry, 16 = objects
modelYawOffsetnumber0.0Constant yaw correction (degrees) — useful if a custom prop's "forward" axis is rotated
pitchSignnumber1.0-1 or 1. Flip if your custom prop renders upside-down

Move (Relocating a Placed Board)

When the owner picks the Move board option on a placed board, the placer re-opens with limits relative to the original position.

config/config.lua
Config.Move = {
maxDistFromOriginal = 5.0,
maxPlacementRange = 12.0,
}
OptionTypeDefaultDescription
maxDistFromOriginalnumber5.0Player must stand within this distance from the original board to start a move (m). Server clamps to 1..10
maxPlacementRangenumber12.0New placement point cannot be further than this from the player (m). Server clamps to 2..20

Editors Whitelist

Per-board editor whitelist — the owner grants edit access to specific players, independent of the global "public edit" toggle.

config/config.lua
Config.Editors = {
nearbyRadius = 10.0,
maxPerBoard = 10,
}
OptionTypeDefaultDescription
nearbyRadiusnumber10.0Radius from the owner used to populate the "Players nearby" list in the management modal (m)
maxPerBoardnumber10Max number of whitelisted editors per board. Server-enforced

Controls

Keybinds for the placer and the editor toolbar shortcuts. Values are FiveM control IDs — see the FiveM control reference.

config/config.lua
Config.Controls = {
ids = {
confirm = 24, -- Mouse Left
cancel = 25, -- Mouse Right

scrollUp = 241,
scrollDown = 242,

altModifier = 19, -- Alt
shiftModifier = 21, -- Shift
ctrlModifier = 36, -- Ctrl

arrowUp = 172,
arrowDown = 173,
arrowLeft = 174,
arrowRight = 175,

surfaceToggle = 23, -- F
snapToggle = 47, -- G
resetRotation = 45, -- R
},

hud = {
{ key = 'scroll-rotate', label = 'ui_label_rotate' },
{ key = 'alt+scroll', label = 'ui_label_depth' },
{ key = '↕', label = 'ui_label_height' },
{ key = '↔', label = 'ui_label_slide' },
{ divider = true },
{ key = 'F', label = 'ui_label_surface', toggle = 'surface' },
{ key = 'R', label = 'ui_label_reset' },
{ divider = true },
{ key = 'mouse-left', label = 'ui_label_place', accent = 'ok' },
{ key = 'mouse-right', label = 'ui_label_cancel', accent = 'no' },
},
}

Config.Controls.hud builds the on-screen HUD legend shown during placement. Each entry takes either:

FieldTypeDescription
keystringKeycap text
labelstringLocale key for the action name
togglestring|nilIf set, the key is rendered as a toggle and reflects state (e.g. surface)
accentstring|nilVisual accent: 'ok' (green) or 'no' (red)
dividerbooleanIf true, inserts a vertical separator instead of a key

Target Options (qb-target / ox_target)

Each placed board exposes up to six target options. Icons are Font Awesome class names; label is a locale key.

config/config.lua
Config.Target = {
icon = 'fas fa-hammer',
label = 'target_pickup',
distance = 5.0,
zonePadding = 0.10,
fallbackZoneSize = { x = 1.2, y = 0.4, z = 1.6 },
debugPoly = false,
}

Config.TargetRename = { icon = 'fas fa-pen', label = 'target_rename' }
Config.TargetPreview = { icon = 'fas fa-eye', label = 'target_preview' }
Config.TargetEdit = { icon = 'fas fa-pen-to-square', label = 'target_edit' }
Config.TargetMove = { icon = 'fas fa-arrows-up-down-left-right', label = 'target_move' }
Config.TargetPublicEdit = { icon = 'fas fa-users',
labelEnable = 'target_public_edit_enable',
labelDisable = 'target_public_edit_disable' }
Config.TargetManageEditors = { icon = 'fas fa-user-shield', label = 'target_manage_editors' }

The base Config.Target controls the BoxZone created around every board:

OptionTypeDefaultDescription
distancenumber5.0Interaction range (m)
zonePaddingnumber0.10Padding (m) added around the model-derived BoxZone
fallbackZoneSize{x,y,z}1.2 / 0.4 / 1.6BoxZone size used when the model returns no dimensions
debugPolybooleanfalseDraw BoxZone outline in-world. Use for prop calibration

Visibility of each option follows ownership and per-board settings:

OptionShown to
PreviewEveryone in range
Edit boardOwner, whitelisted editors, OR anyone if public edit is ON
RenameOwner only
Toggle public editOwner only
Manage editorsOwner only, and only while public edit is OFF
Move boardOwner only
Collect boardOwner only

Editor

Settings for the in-session editor (the "edit board" target option opens it).

config/config.lua
Config.Edit = {
maxDistance = 4.0,
closeKeys = { 177, 200, 25 },
stringTolerance = 0.008,
noteBaseSize = { w = 0.10, h = 0.10 },
updateNoteThrottleMs = 100,
}
OptionTypeDefaultDescription
maxDistancenumber4.0If the player walks beyond this from the board, the editor closes (m)
closeKeysnumber[]{177, 200, 25}Control IDs that close the editor (default: Backspace, Esc, Mouse Right)
stringTolerancenumber0.008Click tolerance for hitting a string (UV-space units; 1.0 = full surface)
noteBaseSize{w,h}0.10 / 0.10Default size of a newly created sticky note (UV-space)
updateNoteThrottleMsnumber100Throttle for net updates while dragging a note (ms)

Editor Toolbar

The order and identity of tools shown in the editor's left-hand toolbar.

config/config.lua
Config.Tools = {
{ id = 'select', icon = 'fas fa-arrow-pointer', label = 'tool_select' },
{ id = 'note', icon = 'fas fa-note-sticky', label = 'tool_note' },
{ id = 'image', icon = 'fas fa-image', label = 'tool_image' },
{ id = 'string', icon = 'fas fa-link', label = 'tool_string' },
{ id = 'draw', icon = 'fas fa-pen', label = 'tool_draw' },
{ id = 'eraser', icon = 'fas fa-eraser', label = 'tool_eraser' },
}
FieldTypeDescription
idstringInternal tool ID — do not change. Allowed: select, note, image, string, draw, eraser
iconstringFont Awesome class
labelstringLocale key for the tool tooltip

To hide a tool, comment out the entry. To reorder, change the array order.

See Features for what each tool does.

Board Preview (Camera)

The "Look at board" target option opens a close-up camera view of the board (read-only).

config/config.lua
Config.Preview = {
fov = 45.0,
fitMargin = 1.15,
fadeMs = 500,
surfaceUp = 0.05,
closeKeys = { 177, 25, 200 },
}
OptionTypeDefaultDescription
fovnumber45.0Field of view (degrees). Lower = more zoomed in
fitMarginnumber1.15Framing margin multiplier. 1.0 = tight, 1.2 = loose
fadeMsnumber500Camera fade-in / fade-out (ms)
surfaceUpnumber0.05Vertical camera offset from the surface center (m)
closeKeysnumber[]{177, 25, 200}Control IDs that close preview (default: Backspace, Mouse Right, Esc)

Debug

config/config.lua
Config.Debug = {
enabled = false,

categories = {
INIT = true,
SPAWN = true,
PLACER = true,
EDITOR = true,
CONTENT = true,
RENDER = false,
IO = true,
SECURITY = true,
PERF = false,
WEBHOOK = true,
ERROR = true,
},

colors = {
INIT='^3', SPAWN='^2', PLACER='^5', EDITOR='^4', CONTENT='^6',
RENDER='^7', IO='^6', SECURITY='^8', PERF='^8', WEBHOOK='^3', ERROR='^1',
},

severityPrefix = {
info = '',
warn = '[WARN] ',
err = '[ERR] ',
},

perfThresholdMs = 50,
}
OptionTypeDefaultDescription
enabledbooleanfalseMaster switch. false → no debug output regardless of category states
categories.<NAME>booleanmixedPer-category toggle. Disable noisy categories without touching the master switch
colors.<NAME>string^1..^8FiveM ANSI color code per category
severityPrefixtable(see above)Prefix per severity (info, warn, err)
perfThresholdMsnumber50Threshold above which PERF warnings are emitted
CategoryWhat it logs
INITResource start, config loading
SPAWNBoard spawn / despawn / bulk spawn
PLACERPlacement, moving
EDITOREditor session lifecycle
CONTENTNotes / strings / strokes mutations
RENDERDUI activation, fade band, idle timeout
IODatabase queries, network events
SECURITYRejected actions, cooldown hits, missing permissions
PERFSlow operations above perfThresholdMs
WEBHOOKDiscord dispatch / queue / sessions
ERRORErrors (always shown when master is on)

Framework Config

Selecting which framework adapter is used, and how the bridge logs.

config/framework_config.lua
Config.Framework = {
mode = 'auto', -- 'auto' | 'qb' | 'qbx' | 'esx' | 'ox' | 'custom'

custom = {
resource = nil, -- e.g. 'qby-core'
basedOn = 'qb', -- 'qb' | 'qbx' | 'esx' | 'ox'

exports = {
core = nil,
target = nil,
inventory = nil,
},

events = {
playerLoaded = nil,
playerUnload = nil,
clientPlayerLoaded = nil,
clientPlayerUnload = nil,
clientPlayerUnloadAlias = nil,
notify = nil,
},
},
}

Config.Bridge = {
debug = false,
notifyMode = 'auto', -- 'auto' | 'oxlib'
}
OptionTypeDefaultDescription
Framework.modestring'auto''auto' — detect (priority: QBX > ox_core > QBCore > ESX). Or force a specific adapter. 'custom' requires a custom.resource to be started
Framework.custom.resourcestring|nilnilCustom framework's resource name (must be started). When set and started, it wins over 'auto' detection
Framework.custom.basedOnstring'qb'Which built-in adapter logic to inherit
Framework.custom.exports.*string|nilnilOverride export names (core, target, inventory). nil = inherit
Framework.custom.events.*string|nilnilOverride event names. nil = inherit
Bridge.debugbooleanfalseVerbose bridge logs. Enable while debugging adapter wiring
Bridge.notifyModestring'auto''auto' — use each framework's native notify; 'oxlib' — force lib.notify (falls back to 'auto' if ox_lib isn't started)

See Permissions for the Config.Permissions block in the same file.

Target Override

Independent of framework — useful when you run ox_target on QBCore, or a renamed fork.

config/target_config.lua
Config.Framework.target = {
resource = nil, -- e.g. 'my-target'
basedOn = nil, -- 'qb' | 'ox' | nil (inherit framework default)
}
OptionDefaultDescription
resourcenilTarget resource name. nil = inherit framework default. QB → qb-target; QBX/ESX/ox → ox_target
basedOnnilWhich target API the resource exposes: 'qb' (positional AddBoxZone) or 'ox' (options-table addBoxZone). nil = inherit
API mismatch

The target resource must expose the API matching basedOn. Mismatched API throws at runtime (e.g. ox_target with basedOn='qb').

Inventory Adapter

config/inventory_config.lua
Config.Inventory = {
mode = 'auto',

custom = {
addItem = nil,
removeItem = nil,
hasItem = nil,
getItemCount = nil,
canCarryItem = nil,
},
}
ModeAuto-resolves to
'auto' (default)QB framework → 'qb'; QBX or ox_core → 'ox'; ESX + ox_inventory started → 'ox'; ESX without ox_inventory → 'esx_native'
'ox'ox_inventory
'qb'qb-inventory
'qs'qs-inventory (Quasar)
'tgiann'tgiann-inventory
'codem'codem-inventory
'ps'ps-inventory (Project Sloth)
'esx_native'xPlayer.addInventoryItem fallback (ESX only)
'custom'Uses the five Config.Inventory.custom callbacks

For 'custom' mode you must define all five callbacks:

Custom adapter — required signatures
addItem      = function(source, item, count, metadata) return boolean end
removeItem = function(source, item, count, metadata) return boolean end
hasItem = function(source, item, count) return boolean end
getItemCount = function(source, item) return number end
canCarryItem = function(source, item, count) return boolean end

The bridge errors at startup if any callback is missing. If your inventory has no native weight/slot pre-check, return true from canCarryItem.

See Also

  • Items — per-framework item registration templates
  • Webhooks — Discord webhook setup (server-only config file)
  • Permissions — full permission mapping reference
  • Commands/board access modes, item gating
  • Features — what each toolbar tool and target option does