Configuration
F5 Shadow Market has two configuration files:
| File | Loaded as | Contents |
|---|---|---|
config.lua | shared | Everything below — economy, bargaining, discounts, delivery, framework/inventory/target, money, admin, database, debug, placer, controls |
config_webhooks.lua | server-only | Discord webhook URLs, per-event colors, queue tuning (Webhooks) |
config_webhooks.lua is loaded only on the server. Never put webhook URLs in config.lua — they would leak to clients.
Sections in config.lua are ordered by how often a server owner needs them — economy first, advanced tuning last.
General
F5Cfg.Locale = 'en'
F5Cfg.Item = 'market_tablet'
F5Cfg.Command = {
mode = 'item_or_command',
name = 'market',
suggestion = true,
}
| Option | Type | Default | Description |
|---|---|---|---|
Locale | string | 'en' | Translation file from locales/<code>.lua. See Localization |
Item | string | 'market_tablet' | The usable inventory item that opens the market |
Command.mode | string | 'item_or_command' | 'item_only' (no command), 'item_or_command' (command requires the item), 'command_always' (command needs no item). See Commands |
Command.name | string | 'market' | Chat command name (ignored when mode = 'item_only') |
Command.suggestion | boolean | true | Show the /command hint in the chat suggestion list |
Interface Sounds
F5Cfg.Sounds = {
enabled = true,
defaultVolume = 0.6,
}
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch for the NUI sound layer |
defaultVolume | number | 0.6 | Starting volume 0.0–1.0. Players can override it in the UI |
Market Economy
The core auction-house economy.
F5Cfg.Market = {
feeMode = 'percent',
listingFee = 500,
listingFeePercent = 0.07,
listingFeeCharge = 'on_sale',
platformCut = 0.10,
minPrice = 1,
maxPrice = 1000000,
minBuyout = 1,
maxBuyout = 5000000,
maxActiveListings = 10,
durations = {
{ hours = 1 },
{ hours = 6 },
{ hours = 12 },
{ hours = 24 },
{ unlimited = true },
},
anonymousPrefix = 'Shadow',
minBidIncrement = 0.05,
minBidIncrementFlat = 100,
reviewWindowHours = 24,
favoriteRetentionDays = 2,
bidHistoryRetentionDays = 2,
notificationRetentionDays = 7,
notificationStaleGraceMinutes = 30,
itemFilter = { mode = 'blacklist', blacklist = { ... }, whitelist = { ... } },
}
Fees & Cuts
| Option | Type | Default | Description |
|---|---|---|---|
feeMode | string | 'percent' | 'percent' → fee is a share of the start price; 'flat' → fee is a fixed amount |
listingFee | number | 500 | Flat fee amount (used when feeMode = 'flat') |
listingFeePercent | number | 0.07 | Share of the start price (used when feeMode = 'percent'), 0.07 = 7% |
listingFeeCharge | string | 'on_sale' | 'on_sale' → fee deducted from the seller's payout on completion; 'on_create' → fee paid upfront when the listing is created |
platformCut | number | 0.10 | The market's cut of every completed sale, 0.10 = 10%. Deducted from the seller's payout |
On a completed sale the seller receives price − platformCut − (listingFee if charged on_sale). The buyer's money is held in escrow from the moment of purchase / winning bid until the package is collected.
Price Limits
| Option | Type | Default | Description |
|---|---|---|---|
minPrice / maxPrice | number | 1 / 1000000 | Allowed start-price range |
minBuyout / maxBuyout | number | 1 / 5000000 | Allowed buy-now / auction buyout range |
maxActiveListings | number | 10 | Max active listings per seller |
Durations
durations is the list of listing lifetimes offered in the UI. Each entry is either { hours = N } or { unlimited = true }.
| Form | Meaning |
|---|---|
{ hours = N } | Timed listing that expires after N hours |
{ unlimited = true } | Buy-now only — never expires. Auctions clamp an "unlimited" choice down to the longest timed entry |
Seller Identity
| Option | Type | Default | Description |
|---|---|---|---|
anonymousPrefix | string | 'Shadow' | Prefix for the anonymous seller handle, e.g. Shadow#1234. Each citizen is assigned a stable handle stored in f5_shadowmarket_identities |
Bidding
| Option | Type | Default | Description |
|---|---|---|---|
minBidIncrement | number | 0.05 | Next bid must beat the current bid by at least this share (5%) … |
minBidIncrementFlat | number | 100 | … or by at least this flat amount — whichever is larger. The first bid must be at least the start price |
Retention & Cleanup
A sweep runs server-side every 60 s; the retention purges below run every 5 cycles (≈5 min).
| Option | Type | Default | Description |
|---|---|---|---|
reviewWindowHours | number | 24 | How long after an order concludes a buyer may rate the seller |
favoriteRetentionDays | number | 2 | Favorites of closed listings are purged after this many days |
bidHistoryRetentionDays | number | 2 | Closed auctions stay in the player's "My bids" view this long |
notificationRetentionDays | number | 7 | Old notifications are deleted after this many days |
notificationStaleGraceMinutes | number | 30 | Notifications about a concluded listing/order are purged this long after conclusion (read ones go sooner). sale_completed and review notifications are protected from auto-purge |
Item Filter
Controls which inventory items can be listed on the market.
itemFilter = {
mode = 'blacklist',
blacklist = {
'id_card', 'driver_license', 'lawyerpass',
'market_tablet', 'phone', 'radio',
},
whitelist = {
'lockpick', 'weapon_pistol', 'goldbar',
},
}
| Option | Type | Default | Description |
|---|---|---|---|
mode | string | 'blacklist' | 'blacklist' → every item is listable except those in blacklist; 'whitelist' → only items in whitelist are listable |
blacklist | string[] | (6 items) | Item names blocked when mode = 'blacklist' |
whitelist | string[] | (3 items) | Item names allowed when mode = 'whitelist' |
The active list is evaluated by Bridge.IsItemAllowed(itemName) — an item with no name is always rejected.
Bargaining
Buy-now counter-offers: a buyer proposes a lower price, the seller accepts, declines or counters.
F5Cfg.Bargain = {
enabled = true,
allowOnDiscounted = true,
maxDiscount = 0.30,
maxOffers = 5,
}
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Master switch for bargaining |
allowOnDiscounted | boolean | true | Allow offers on listings the seller already marked down. false hides the offer box on discounted listings |
maxDiscount | number | 0.30 | Lowest acceptable offer = buyout * (1 - maxDiscount) — here, 70% of buyout |
maxOffers | number | 5 | Number of times the seller can decline before the buyer is locked out of offering on that listing |
Price Discounts
Lets sellers cut the price of their own buy-now listings.
F5Cfg.Discount = {
enabled = true,
minStepPercent = 0.05,
cooldownMinutes = 10,
notifyFavorites = true,
}
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Let sellers lower the price of their own buy-now listings |
minStepPercent | number | 0.05 | Each cut must lower the price by at least this share (5%) |
cooldownMinutes | number | 10 | Minimum wait between price cuts on the same listing |
notifyFavorites | boolean | true | Notify buyers who favorited the listing when its price drops |
See Features → Seller Discounts.
Delivery
After a sale the seller physically drops off a package, the buyer collects it. This section drives that loop.
F5Cfg.Delivery = {
itemPackage = {
model = 'prop_cs_street_binbag_01',
blipSprite = 501,
blipColor = 2,
blipScale = 0.8,
blipLabel = 'blip_package_pickup',
pickupDist = 2.0,
},
dropoffTimeoutHours = 24,
pickupTimeoutHours = 24,
maxDropoffDist = 24.0,
visibility = 'parties',
renderDistance = 100.0,
syncInterval = 1500,
}
| Option | Type | Default | Description |
|---|---|---|---|
itemPackage.model | string | 'prop_cs_street_binbag_01' | Prop spawned as the dropped package |
itemPackage.blipSprite | number | 501 | Package blip sprite |
itemPackage.blipColor | number | 2 | Package blip color |
itemPackage.blipScale | number | 0.8 | Package blip scale |
itemPackage.blipLabel | string | 'blip_package_pickup' | Locale key for the blip label |
itemPackage.pickupDist | number | 2.0 | Distance within which the buyer can collect the package (m) |
dropoffTimeoutHours | number | 24 | Seller must drop the package within this window after the sale, or the order expires (buyer refunded) |
pickupTimeoutHours | number | 24 | Buyer must collect within this window after dropoff, or the order expires (buyer refunded) |
maxDropoffDist | number | 24.0 | Max distance between the seller and the placed package (m) |
visibility | string | 'parties' | 'parties' → only buyer + seller see the package; 'everyone' → anyone nearby sees it |
renderDistance | number | 100.0 | Distance at which the package prop spawns client-side (m) |
syncInterval | number | 1500 | Client delivery sync loop interval (ms) |
See Features → Delivery.
Framework & Compatibility
Framework, inventory, target and notification selection. All default to 'auto'.
F5Cfg.Framework = {
mode = 'auto', -- 'auto' | 'esx' | 'qb' | 'qbx' | 'custom'
inventory = 'auto', -- 'auto' | 'ox' | 'qb' | 'qs' | 'ps' | 'codem' | 'tgiann' | 'esx_native' | 'custom'
notify = 'framework', -- 'auto' | 'framework' | 'oxlib'
notifyStyle = 'custom', -- 'custom' | 'native'
custom = { base = 'qb', getCore = nil, --[[ ... ]] },
}
F5Cfg.Target = {
system = 'auto', -- 'auto' | 'ox' | 'qb' | 'custom' | 'none'
pickup = {
label = 'target_pickup_package',
icon = 'fas fa-box-open',
iconColor = nil, -- ox_target only
distance = 2.5,
},
custom = { base = 'ox', resource = 'my-target' },
}
F5Cfg.Inventory = {
imageUrl = 'auto',
}
| Option | Type | Default | Description |
|---|---|---|---|
Framework.mode | string | 'auto' | 'auto' detects ESX/QB/QBX by resource; or force a specific adapter, or 'custom' for a fork |
Framework.inventory | string | 'auto' | 'auto' detects the inventory; or force one |
Framework.notify | string | 'framework' | 'framework' uses the framework's native notify; 'auto' uses ox_lib when present, else the framework; 'oxlib' forces ox_lib |
Framework.notifyStyle | string | 'custom' | 'custom' = this resource's styled top-right toasts; 'native' = hand off to the notify backend |
Framework.custom | table | — | Custom-framework adapter config (only when mode = 'custom') |
Target.system | string | 'auto' | 'auto' detects ox_target/qb-target; or force one; 'none' = no target interaction; 'custom' for a fork |
Target.pickup.label | string | 'target_pickup_package' | Locale key for the package pickup option label |
Target.pickup.icon | string | 'fas fa-box-open' | Font Awesome icon for the pickup option |
Target.pickup.iconColor | string | nil | Icon tint, e.g. '#e6b800'. ox_target only — ignored by qb-target. nil = theme default |
Target.pickup.distance | number | 2.5 | Package pickup interaction distance (m) |
Target.custom | table | — | Custom-target adapter config (only when system = 'custom') |
Inventory.imageUrl | string | 'auto' | 'auto' matches the detected inventory's image path, or set a nui://<resource>/.../%s.png override |
The full detection logic, the custom adapter fields, and all per-inventory image paths and limitations are documented on the Framework Compatibility page.
Money & Currencies
Account-based (non-item) money. The whole market uses a single wallet — payoutType.
F5Cfg.Money = {
payoutType = 'bank',
types = {
{ id = 'cash', label = 'Cash', icon = 'fa-solid fa-money-bill-wave', accounts = { qb = 'cash', qbx = 'cash', esx = 'money' } },
{ id = 'bank', label = 'Bank', icon = 'fa-solid fa-building-columns', accounts = { qb = 'bank', qbx = 'bank', esx = 'bank' } },
{ id = 'crypto', label = 'Crypto', icon = 'fa-solid fa-coins', accounts = { qb = 'crypto', qbx = 'crypto' } },
{ id = 'blackmoney', label = 'Black Money', icon = 'fa-solid fa-sack-dollar', accounts = { esx = 'black_money' } },
},
}
| Option | Type | Default | Description |
|---|---|---|---|
payoutType | string | 'bank' | The single wallet used for balances, all spending and all payouts. Must be the id of a type below |
types[].id | string | — | Internal currency id (alphanumeric, _, -) |
types[].label | string | — | Display label |
types[].icon | string | — | A Font Awesome class (e.g. fa-solid fa-coins) or an image path relative to the NUI (e.g. img/crypto.png) |
types[].accounts | table | — | Native account key per framework (qb / qbx / esx) |
A type with no account for the active framework is auto-skipped (e.g. crypto has no ESX account, blackmoney has none on QB/QBox). If payoutType resolves to a skipped/invalid type, the bridge falls back to bank, then to the first active type. See Framework Compatibility → Money & Currencies.
Admin Panel
F5Cfg.Admin = {
command = 'marketadmin',
acePermission = 'f5market.admin',
frameworkGroups = { 'admin', 'god' },
auditWebhook = true,
actionCooldownMs = 800,
pageSize = 25,
}
| Option | Type | Default | Description |
|---|---|---|---|
command | string | 'marketadmin' | Chat command that opens the admin panel |
acePermission | string | 'f5market.admin' | ACE permission that grants access. '' disables the ACE path |
frameworkGroups | string[] | { 'admin', 'god' } | Framework groups that grant access. {} disables group-based access |
auditWebhook | boolean | true | Mirror admin actions to Discord (admin_audit webhook) |
actionCooldownMs | number | 800 | Per-admin debounce on mutating actions (ms) |
pageSize | number | 25 | Rows per page in admin tables |
Access is granted if the player passes the ACE permission OR belongs to a listed framework group. See Admin Panel.
Database
F5Cfg.Database = {
autoCreateTables = true,
}
| Option | Type | Default | Description |
|---|---|---|---|
autoCreateTables | boolean | true | Create and migrate the schema on resource start. When false, no tables/columns/indexes/ENUMs are created or migrated — you must manage the schema yourself |
Debug
F5Cfg.Debug = {
enabled = false,
categories = {
INIT = true, BRIDGE = true, UI = true, MARKET = true, BARGAIN = true,
ORDER = true, DELIVERY = true, TARGET = true, PLACER = true, IO = true,
WEBHOOK = true, SECURITY = true, PERF = false, ERROR = true,
},
colors = { --[[ ANSI color per category ]] },
severityPrefix = { info = '', warn = '[WARN] ', err = '[ERR] ' },
perfThresholdMs = 75,
}
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Master switch for all debug output (server, client, NUI). false → nothing prints regardless of category |
categories.<NAME> | boolean | mixed | Per-category toggle |
colors.<NAME> | string | ^1..^8 | FiveM ANSI color code per category |
severityPrefix | table | (see above) | Prefix per severity (info / warn / err) |
perfThresholdMs | number | 75 | DB queries slower than this (ms) log to the PERF category |
| Category | What it logs |
|---|---|
INIT | Resource start, config loading, client readiness |
BRIDGE | Framework / inventory / target / money detection and resolution |
UI | NUI open/close, tablet item use |
MARKET | Listings, bids, purchases |
BARGAIN | Counter-offer flow |
ORDER | Order lifecycle, expiry, returns |
DELIVERY | Package dropoff / pickup / sync |
TARGET | Target system detection and pickup option add/remove |
PLACER | Package placement mode |
IO | Database queries, item use, network events |
WEBHOOK | Discord dispatch / queue |
SECURITY | Rejected actions, admin denials, cooldown hits |
PERF | Slow DB queries above perfThresholdMs |
ERROR | Errors (always logged when the master switch is on) |
The Debug system is unified across server, client and NUI — there are no loose print() calls; everything flows through F5Cfg.Debug.
Placer (Advanced)
Tuning for the package placement mode (the ghost preview the seller controls to drop the package).
F5Cfg.Placer = {
rotationStep = 5.0,
minDist = 1.5,
maxDist = 16.0,
zoomStep = 0.25,
startDist = 8.0,
surfaceOffset = 0.02,
pedDistMargin = 0.5,
ghostAlpha = 150,
ghostTintValid = { r = 255, g = 255, b = 255 },
ghostTintBlocked = { r = 230, g = 70, b = 70 },
raycastFlags = 1 | 16,
modelYawOffset = 0.0,
pitchSign = 1.0,
}
| Option | Type | Default | Description |
|---|---|---|---|
rotationStep | number | 5.0 | Degrees rotated per scroll tick |
minDist / maxDist | number | 1.5 / 16.0 | Min/max prop distance from the camera (m) |
zoomStep | number | 0.25 | ALT+scroll zoom step (m) |
startDist | number | 8.0 | Initial placement distance (m) |
surfaceOffset | number | 0.02 | Extra offset along the surface normal (m) |
pedDistMargin | number | 0.5 | Safety margin subtracted from Delivery.maxDropoffDist when validating placement (m) |
ghostAlpha | number | 150 | Ghost prop transparency (0–255) |
ghostTintValid | rgb | white | Ghost color when placement is legal |
ghostTintBlocked | rgb | red | Ghost color when placement is blocked (too far, blocked surface) |
raycastFlags | number | 1 | 16 | GTA raycast bitmask: 1 = world, 16 = objects |
modelYawOffset | number | 0.0 | Constant yaw correction (degrees) |
pitchSign | number | 1.0 | -1 or 1; flip if the prop renders upside-down |
Controls (Advanced)
Keybinds for the placement mode and the HUD legend shown during placement. Values are FiveM control IDs.
F5Cfg.Controls = {
ids = {
confirm = 24, -- LMB
cancel = 25, -- RMB
scrollUp = 241,
scrollDown = 242,
altModifier = 19, -- ALT
},
hud = {
{ key = 'scroll-rotate', label = 'ui_placer_rotate' },
{ key = 'alt+scroll', label = 'ui_placer_zoom' },
{ divider = true },
{ key = 'mouse-left', label = 'ui_placer_place', accent = 'ok' },
{ key = 'mouse-right', label = 'ui_placer_cancel', accent = 'no' },
},
}
| Field | Description |
|---|---|
ids.confirm | Place the package (default 24, LMB) |
ids.cancel | Cancel placement (default 25, RMB) |
ids.scrollUp / ids.scrollDown | Rotate / zoom (default 241 / 242) |
ids.altModifier | Hold to zoom instead of rotate (default 19, ALT) |
Each hud entry is a key hint row: key (keycap text), label (locale key), optional accent ('ok'/'no'), or { divider = true } for a separator.
See Also
- Framework Compatibility — framework / inventory / target / money detail and custom adapters
- Commands — command modes and the admin command
- Items — registering
market_tablet - Webhooks — the server-only
config_webhooks.lua - Features — what the economy options drive in-game