legacy-shops
Troubleshooting
Known gotchas and non-obvious behavior.
Troubleshooting
Nothing happens when I edit the SQL tables directly
After first boot, live data lives in data/shops.json (via legacy-lib's Store), not in legacy_shops / legacy_shops_categories / legacy_shops_items / legacy_shops_presets. Those SQL tables are only read once, automatically, to migrate a fresh/legacy install. Editing them afterwards has no effect — use the creator UI or the server callbacks instead. legacy_shops_history is the one table that stays live (purchase log).
Creator's preset/template list is empty
server/presets.lua must load before server/server.lua — it defines the globals ShopPresets and ShopPresetLocations consumed by seedDefaultPresets(). If it fails to load, you'll see ^1[legacy-shops] ShopPresets is missing...^0 in console and the template list will simply be empty with no other error. Check your fxmanifest.lua script order.
Editing a preset didn't change anything in-game
A shop only follows a preset if it was placed from that preset (matching preset_id). Editing a preset that no shop references is a silent no-op (console print only, no in-game feedback). Also note: saving a preset overwrites every linked shop's items/categories unconditionally — any shop-local price edits on those shops are discarded.
An old shop doesn't match its preset's current catalogue
Shops created before preset_id linking existed are auto-relinked on boot by matching shop.name == preset.name. This only restores the link — it does not resync item contents. The shop will only catch up the next time that preset is saved.
Job lock / grade fields aren't saving
groups (job lock) is intentionally never taken from the creator form's data for shop or preset updates — it's preserved from the existing record so the form (which has no field for it) can't accidentally wipe it. Only createShop honors data.groups, and only when there's no matching preset.
Job/grade or licence checks always fail
jobOf(src) expects legacy-lib's field names jobName/gradeRank — not a framework's native name/grade. Licence checks read Framework.GetPlayerMetadata(src, 'licences') (British spelling). If your framework bridge uses different field names, checks will silently deny everyone.
Alternate-currency (item-based) purchases are always refused
These are paid by removing an inventory item, not touching a money account, via legacy-lib's Inventory.GetItemsByName/RemoveItem. If that bridge method is missing, balances read as 0 and the purchase fails closed (refused) rather than being allowed for free — check your legacy-lib inventory bridge is implemented for your framework.
Restock/fluctuation never seem to do anything
Every built-in preset uses stock = -1 (unlimited), so the restock loop and purchase-time stock decrement are inert by default. You must set finite stock (stock >= 0, max_stock > 0) on an item via the creator or legacy-shops:saveItem to see restocking take effect.
Shop blips don't respect the all/closest/none toggle
That toggle requires legacy-blipmanager to be running. legacy-shops retries registration every 250ms for up to 10s on boot, and again on legacy-blipmanager:ready. If legacy-blipmanager never starts, blips still work (default closest mode) but without the toggle — this is expected, not a bug.
"No blips visible" isn't a reliable signal
In 'closest' blip mode, if applyNearestBlips() can't find a valid candidate shop (e.g. missing coords), it fails open and shows ALL blips rather than hiding them. Also, shops sharing the same preset_id will have all but the nearest one's blip hidden — this is by design for grouping, even though every ped/target is still active.
Deleting a shop leaves orphaned purchase history
legacy_shops_history has no foreign key back to legacy_shops. Deleting a shop via legacy-shops:deleteShop does not clean up its history rows — this is intentional (history is meant to persist), but be aware of orphaned shop_id references if you query this table directly.
New shop isn't visible to already-connected clients
Clients only (re)spawn shop peds/blips via a fetchAndSpawn() call, which runs once ~2s after resource start and again on legacy-shops:syncShops. If you create a shop through any path other than the provided callbacks, make sure to broadcast legacy-shops:syncShops yourself or clients won't see it.
OpenCreator / OpenStore seem to hang
Both exports call lib.callback.await('legacy-shops:getInitData', ...) synchronously, blocking the calling thread until the server responds. Don't call them from a thread that must stay responsive. Also, only one UI can be open at a time — calling either export while a UI is already open is a silent no-op with no feedback.