Server
Server hooks in hooks/server.lua.
Register these in hooks/server.lua. See Hooks for how gates, filters and notifies work.
Opening a store
canOpenStore
Gate. May this player open this store? Runs after the built-in store, job lock and distance checks.
- source:
number - storeId:
string— store id - store:
table— the store's config block
Hooks.on('canOpenStore', function(source, storeId, store)
if exports.qbx_policejob:isPlayerCuffed(source) then
return false, 'no_access'
end
end)storePayload
Filter. The data sent to the store UI when it opens. Return a changed copy to hide outfits, rename them, and so on.
- payload:
table— includesoutfits, the list shown in the store - source:
number - storeId:
string
Hooks.on('storePayload', function(payload, source, storeId)
local kept = {}
for index = 1, #payload.outfits do
local outfit = payload.outfits[index]
if outfit.collection ~= 'staff' or IsPlayerAceAllowed(source, 'fashion.staff') then
kept[#kept + 1] = outfit
end
end
payload.outfits = kept
return payload
end)The contents of outfits the player doesn't own are already stripped out. Don't put them back — that's what keeps unbought outfits sealed.
Money
discount
Filter. The final discount rate, from 0 to 1. What you return replaces the discount worked out from the editor's Discounts settings, including the Largest total discount cap — this is the only way past it. The result is clamped to 0–1.
- rate:
number—0–1 - source:
number - storeId:
string - sources:
table[]—{ label, rate }for each discount that applied
Hooks.on('discount', function(rate, source, storeId, sources)
if exports.my_loyalty:tier(source) == 'gold' then
return math.max(rate, 0.25)
end
end)canBuy
Gate. The last check before the player is charged. Refusing refuses the whole cart.
- source:
number - order:
table
| Field | Type | Description |
|---|---|---|
identifier | string | the character the outfits would go to |
store | string | store id |
account | string | 'bank' or 'cash' |
total | integer | what they're about to be charged |
discount | number | 0–1 |
outfits | string[] | catalog ids in the cart |
Hooks.on('canBuy', function(source, order)
if order.total > 50000 and not IsPlayerAceAllowed(source, 'fashion.bigspender') then
return false, 'no_access'
end
end)bought
Notify. A purchase went through: paid for, owned and logged.
- source:
number - receipt:
table— same fields asorderincanBuy, withoutfitsbeing what was granted
Hooks.on('bought', function(source, receipt)
exports.my_stats:add(source, 'clothes_spent', receipt.total)
end)Player listings
canList
Gate. May this player list this outfit? Runs after validation, before the fee is taken.
- source:
number - draft:
table
| Field | Type | Description |
|---|---|---|
label | string | outfit name |
description | string | outfit description |
collection | string | collection id |
gender | 'male' | 'female' | ped the outfit was captured on |
price | integer | the seller's asking price, before store multipliers |
pieces | integer | how many components and props it has |
Hooks.on('canList', function(source, draft)
if draft.pieces < 4 then
return false, 'empty_outfit'
end
end)listed
Notify. The listing is live and the fee has been taken.
- source:
number - outfit:
table— thedraftfromcanListplusidandauthor(the lister's identifier)
Hooks.on('listed', function(source, outfit)
exports.my_discord:post(('%s listed %s for %d'):format(GetPlayerName(source), outfit.label, outfit.price))
end)earningsCollected
Notify. A seller pressed Collect in a store and their banked sales income was paid out.
- source:
number - amount:
number— what was paid intoListing.feeAccount
Hooks.on('earningsCollected', function(source, amount)
exports.my_stats:add(source, 'fashion_income', amount)
end)