shared/config.lua and the files in data/ hold the factory values. On the first start they are written to the database, and from then on almost everything is edited live from /tabletadmin. Only a few options are read from the file on every start.
After the first start, editing shared/config.lua or data/*.lua does not change what players see. Edit from the admin panel, or use Factory on a section of the panel to load the file's values again. The exceptions are the static options.
Static options
Read from shared/config.lua on every start and not shown in the panel.
| Option | Type | Default | What it does |
|---|---|---|---|
debug | boolean | false | true prints queries and events to the console. The cuxial_tablet_debug convar set to 1 does the same. |
ui.accent | string (hex) | configurable | Accent colour of the interface. |
ui.sounds | boolean | true | true plays interface sounds on actions. |
commands.admin | string | 'tabletadmin' | Command that opens the admin panel, without the slash. |
admin.permission | string | 'admin' | Permission required for the panel, the operations and the moderation of news, streams and the gallery. See Permissions. |
The name shown in the header of the tablet comes from the UI_SHELL_BRAND, UI_SHELL_BRAND_A and UI_SHELL_BRAND_B keys of locales/en.json and locales/es.json.
Tablet · tablet
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false stops everyone from opening the tablet. The admin panel keeps working. |
keybind | string | 'F1' | Default key. Changing it requires players to reconnect; each player can remap it in the GTA key settings. Mouse buttons are accepted as MOUSE_BUTTON. |
startPage | string | 'player' | Page shown on opening: player, news, jobsOnline, jobsCity, crosshair or vip. |
rememberPage | boolean | true | true reopens on the last page visited. |
prop.model | string | 'prop_cs_tablet' | Prop held in the hand. |
prop.bone | number | 60309 | Bone it is attached to. 60309 is the left hand. |
prop.offset | table | { x = 0.0, y = 0.0, z = 0.03 } | Offset from the bone. |
prop.rotation | table | { x = 10.0, y = 160.0, z = 0.0 } | Rotation of the prop. |
anim.dict, anim.clip | string | tablet animation | Animation played while the tablet is open. |
anim.flag | number | 50 | Animation flag. 50 is upper body and loop. |
canOpen.dead | boolean | false | true allows opening while dead or injured. |
canOpen.swimming | boolean | false | true allows opening while swimming. |
canOpen.driving | boolean | false | true allows opening as the driver. |
canOpen.passenger | boolean | true | true allows opening as a passenger. |
canOpen.cuffed | boolean | false | true allows opening while cuffed. |
pages.player, pages.news, pages.jobsOnline, pages.jobsCity, pages.crosshair, pages.vip | boolean | true | false removes that page from the tablet. |
VIP · vip
| Option | Type | Default | What it does |
|---|---|---|---|
account | string | 'coins' | Money account of the framework that holds the coins. It must exist. See Installation. |
cooldownMs | number | 2000 | Milliseconds a player must wait between two purchases. |
stockMode | string | 'perItem' | 'perItem' keeps a global stock per item and discounts each sale. 'none' ignores stock. |
newDays | number | 7 | Days an item shows the new label after entering the catalogue. |
topSellersCount | number | 5 | Items marked top sellers, by sales of the last 30 days. |
cart.maxItems | number | 10 | Maximum lines in the cart. |
gift.enabled | boolean | true | true allows gifting items to online players. |
gift.feePct | number | 0 | Percent added to the price when gifting. |
gift.messageMaxLen | number | 120 | Maximum length of the gift message. |
wallet.transfer.enabled | boolean | true | true allows coin transfers between players. |
wallet.transfer.feePct | number | 5 | Percent lost as a fee on each transfer. |
wallet.transfer.min | number | 10 | Minimum coins per transfer. |
wallet.transfer.maxPerDay | number | 5000 | Coins a player can send per day. 0 removes the cap. |
wallet.historyLimit | number | 50 | Movements shown in the wallet. |
codes.prefix | string | 'VIP' | Prefix of redeemable codes, as in VIP-XXXX-XXXX. Up to 12 characters. |
codes.redeemPerHour | number | 10 | Redeem attempts per hour and player. |
loyalty.enabled | boolean | true | true turns on the loyalty tiers. |
loyalty.tiers | table[] | four tiers | Tiers ordered by spent, see below. |
Each loyalty tier:
| Field | Type | What it does |
|---|---|---|
id | string | Internal name. |
label | string | Name shown to the player. |
spent | number | Coins spent needed to reach the tier. The first tier is 0. |
discountPct | number | Discount on shop prices. |
extraDailyDay | boolean | true adds an extra day at the end of each daily cycle. |
accent | string (hex) | Colour of the tier. |
The default tiers are Bronze (0 coins, no discount), Silver (2,500 coins, 3 %), Gold (10,000 coins, 6 %, extra day) and Diamond (30,000 coins, 10 %, extra day).
Daily reward · daily
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false hides the daily tab. |
cycleDays | number | 7 | Days in the cycle. Must match the days in data/daily.lua. |
claimWindowHours | number | 24 | Hours between two claims. |
breakStreakOnMiss | boolean | true | true restarts the streak at 1 when a player claims after more than graceHours without claiming. false never breaks it. |
graceHours | number | 48 | Hours without claiming before the streak is at risk. Never less than claimWindowHours. |
streakBonusPct | number | 5 | Percent of extra coins per streak day, on coin rewards only. |
streakBonusMaxPct | number | 50 | Cap of the streak bonus. |
rescue.enabled | boolean | true | true lets a player pay to keep a streak at risk. |
rescue.price | number | 150 | Coins per rescue. |
rescue.maxPerCycle | number | 1 | Rescues allowed per cycle. |
Season pass · pass
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false hides the pass tab and stops XP. |
season.name | string | 'SEASON 1' | Name shown. |
season.number | number | 1 | Season number. Changing the name or the number starts a new season for everyone. |
season.endsAt | number | 0 | Unix time, in seconds, of the end of the season shown in the pass. 0 shows no date. |
totalTiers | number | 15 | Levels. Must match the levels in data/pass.lua. |
xpPerLevel | number | 1000 | XP per level. |
premiumPrice | number | 3000 | Coins to activate premium for the season. |
xpSources.daily | number | 100 | XP per daily claim. |
xpSources.shopPerCoin | number | 1 | XP per coin spent in the shop or on cases. |
xpSources.jobComplete | number | 150 | XP per completed job. See EmitJobCompleted. |
xpSources.playtimePerMin | number | 2 | XP per minute online. 0 turns it off. |
missions.weeklyCount | number | 3 | Missions active each week, picked from data/missions.lua. |
missions.resetDay | number | 1 | Day the week resets: 1 Monday to 7 Sunday. |
Cases · cases
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false hides the cases tab. |
showOdds | boolean | true | true shows the odds per rarity on each tier. |
multiOpen | number[] | { 1, 3, 5 } | Amounts a player can open at once. |
stripLength | number | 40 | Cards in the roulette strip. |
winnerIndex | number | 32 | Position of the winning card in the strip. At most stripLength. |
pity.enabled | boolean | true | true guarantees a legendary after a number of openings without one. |
pity.legendaryAfter | number | 50 | Openings without a legendary before one is forced. |
News · news
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false turns the news off; the page shows nothing and nobody can publish. |
trendingCount | number | 3 | Entries shown as trending. |
autoTrendingLikeThreshold | number | 500 | Likes from which an entry becomes trending. |
publisherJobs | string[] | { 'journalist', 'reporter' } | Jobs that can publish. Staff always can. |
allowAnonLikes | boolean | false | true accepts likes from players without a loaded character. |
pageSize | number | 20 | Entries per page of the feed. |
categories | string[] | seven categories | Categories offered when writing. Categories already used by saved entries are added to the list. |
Live streams · live
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false turns live streams off. |
broadcasterJobs | string[] | { 'journalist', 'reporter', 'police' } | Jobs that can go live. Staff always can. |
maxDurationSec | number | 14400 | Maximum length of a stream, in seconds. |
maxViewers | number | 50 | Viewers per stream. |
enc.width, enc.bitrate, enc.fps | number | 960, 1500000, 20 | Video profile used when the relay is configured. |
chat.enabled | boolean | true | true enables the chat of the stream. |
chat.maxLen | number | 140 | Maximum length of a chat message. |
chat.perMinute | number | 20 | Messages per minute and viewer. |
camera.prop | string | 'prop_v_cam_01' | Camera prop held by the broadcaster. |
camera.bone | number | 28422 | Bone it is attached to. 28422 is the right hand. |
camera.offset, camera.rotation | table | see file | Position and rotation of the prop. |
camera.animDict, camera.animClip, camera.animFlag | string, string, number | shoulder camera animation | Animation played while broadcasting. |
camera.lensForward | number | 0.30 | Metres in front of the prop where the stream camera sits. |
camera.zoomMin, camera.zoomMax | number | 20.0, 60.0 | Field of view range of the zoom. |
camera.stopKey | string | 'BACK' | Default key that stops the stream. Changing it requires players to reconnect. |
Crosshair · crosshair
| Option | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | false turns the whole feature off. |
hideNative | boolean | true | true hides the game's reticle while the custom crosshair is on. |
allowHitmarker | boolean | true | false removes the hitmarker and the damage numbers for everyone. |
allowDamageNumbers | boolean | true | false removes the damage numbers for everyone. Needs allowHitmarker. |
tickMs | number | 50 | Milliseconds between updates of the crosshair state. |
maxLayers | number | 6 | Layers per crosshair. |
maxSize | number | 64 | Maximum size of a layer, in pixels at 1080p. |
gallery.enabled | boolean | true | true enables the community gallery. |
gallery.maxPerPlayer | number | 5 | Entries a player can publish. |
gallery.nameMaxLen | number | 24 | Maximum length of an entry name. |
gallery.pageSize | number | 30 | Entries per page. |
defaults.enabled | boolean | false | true turns the custom crosshair on for players who have not set anything. |
defaults.preset | string | 'classic_cross' | Starting preset, by id from data/crosshair_presets.lua. |
defaults.color | string (hex) | '#ffffff' | Starting colour. |
defaults.alwaysShow | boolean | false | true draws it without aiming. |
defaults.dynamic | table | { enabled = true, moveGap = 4, sprintGap = 8, vehicleGap = 6, aimGap = -2, smooth = 0.2 } | Starting dynamic spread: extra gap while moving, sprinting and driving, reduction while aiming, and smoothing. |
defaults.perWeapon.enabled | boolean | false | true starts with one profile per weapon group. |
hitmarkerDefaults.enabled | boolean | true | Hitmarker on for players who have not set anything. |
hitmarkerDefaults.style | string | 'cross' | cross, x, ring or dot. |
hitmarkerDefaults.sound | boolean | true | true plays a tick on hit. |
hitmarkerDefaults.volume | number | 0.35 | Volume of the tick, 0 to 1. |
hitmarkerDefaults.showDamage | boolean | true | true shows the damage number. |
hitmarkerDefaults.durationMs | number | 220 | Duration of the marker, 100 to 600. |
weaponGroups | string[] | eight groups | Weapon groups offered for per-weapon profiles: melee, pistol, smg, rifle, shotgun, sniper, heavy, throwable. |
data/crosshair_presets.lua holds the 146 presets and the defaults of each layer kind. It is read from the file on every start and is not edited from the panel.
Jobs · jobs
| Option | Type | Default | What it does |
|---|---|---|---|
onlineRefreshSec | number | 5 | Seconds the on-duty counts are cached. |
city.startMode | string | 'waypoint' | 'waypoint' marks the job's coordinates on the map. 'event' fires a client event with the job id. |
city.startEvent | string | '' | Client event fired when startMode = 'event' and the job has no event of its own. |
Catalogues · data/
The files in data/ are the factory catalogues. They are delivered with sample rows that reference common inventory items; replace them with your own from the admin panel. Every row has the fields below.
Shop · data/shop.lua
| Field | What it does |
|---|---|
id | Unique and stable. Never reuse an id: stock and history point to it. |
enabled | false hides the item. |
itemKey | Item name in the inventory. |
name, description | Shown on the card. |
category | One of the categories listed at the end of the file (shopCategories). |
rarity | common, rare, epic or legendary. |
price | Coins. |
amount | Units of the main item. |
stock | Units for sale with stockMode = 'perItem'. -1 is unlimited. |
image | Full URL or path inside the inventory's images folder. Empty uses the inventory's image of itemKey. |
bundle | Extra items delivered with the main one: { itemKey, amount, label, metadata }. |
metadata | Metadata of the main item. |
featured | true shows it in the featured sort. |
discount.pct, discount.untilAt | Flash offer: percent and Unix time of its end. 0 is no offer. |
limited.untilAt | Unix time after which the item leaves the shop. 0 is permanent. |
requires.loyaltyTier | Loyalty tier id required to buy. Empty is none. |
addedAt | Unix time the item entered the catalogue, for the new label. The panel fills it. |
sort | Manual order, lowest first. |
theme | Theme of the card. Empty or one of shopThemes. |
preview3d | true shows the View in 3D button. |
previewModel | Model shown in 3D. Empty uses itemKey as a weapon model. |
Daily · data/daily.lua
One row per day of the cycle: day, enabled, kind (item, coins or cash), itemKey, amount, label, milestone and metadata.
Pass · data/pass.lua
One row per level: level, free and premium. Each reward has kind (item, coins, cash or none), itemKey, amount, label, milestone and metadata.
Cases · data/cases.lua
tiers:id,enabled,label,accent,price,image(empty usesimages/cases/<id>.webpfrom the interface) andweights, the probability ofcommon,rare,epicandlegendary, adding up to 1.pool: the drops, shared by every tier:id,enabled,kind,itemKey,name,rarity,amount,imageandmetadata.
Missions · data/missions.lua
id, enabled, label, type, target, xp and icon. The type decides what target counts:
| Type | Target |
|---|---|
jobs | Completed jobs |
playtime | Minutes online |
daily | Daily claims |
shop | Coins spent |
distance | Metres travelled |
cases | Cases opened |
Jobs online · data/jobs_online.lua
id, enabled, job (job name in the framework), name, image, description, category, color and priority (highest first).
City jobs · data/jobs_city.lua
id, enabled, name, image, description, coords and event (client event for this job when startMode = 'event'; empty uses jobs.city.startEvent). Coordinates can be picked in the world from the admin panel.
Images of jobs and cases are paths inside the web/dist folder of the resource, such as images/cityjobs/trucker.webp.
Hooks
The folder hooks/ holds Lua adapters that connect the tablet to your own resources. Edit them freely; keep the function names and the shape of what they return.
| File | Function | Returns | Out of the box |
|---|---|---|---|
hooks/server/vehicles.lua | GetVehicles(src) | { name, model, plate, class?, status }[] with status one of garage, out, destroyed, impound | Reads qbx_vehicles |
hooks/server/properties.lua | GetProperties(src) | { name, address, type }[] with type one of house, apartment, garage, business | Reads nolag_properties |
hooks/server/playtime.lua | GetWorkedHours(citizenid), GetSessionMinutes(src) | Whole hours, minutes | Hours from the player_jobs_activity table when it exists; session minutes counted by the tablet |
hooks/server/stats.lua, hooks/client/stats.lua | GetStats(src), GetStats() | { label, value, max?, icon? }[] extra stats for the profile | Empty |
hooks/server/xp.lua | OnJobCompleted(cb), EmitJobCompleted(src, info) | — | Listens to cuxial_jobs:server:JobCompleted |
hooks/server/images.lua | GetItemImage(itemKey), GetItemLabel(itemKey) | Image URL and label of an inventory item | Detects ox_inventory or qb-inventory |
When a source resource is not running, the adapter returns an empty list and the tablet works the same.
function Hooks.GetVehicles(src)
local player = CuxialTablet.getPlayer(src)
if not player then return {} end
local rows = MySQL.query.await('SELECT plate, vehicle FROM my_vehicles WHERE owner = ?', { player.citizenid })
local out = {}
for i, v in ipairs(rows or {}) do
out[i] = { name = v.vehicle, model = v.vehicle, plate = v.plate, status = 'garage' }
end
return out
endCommon changes
Use an existing money account for the coins
From the admin panel, section VIP, set account to bank. Or, before the first start:
vip = {
account = 'bank',
...
},Start city jobs from your own job resource
Section Jobs of the panel: city.startMode = event and city.startEvent = myjobs:client:start. The event receives the job id.
RegisterNetEvent('myjobs:client:start', function(jobId)
-- start the job
end)Turn off the VIP economy and keep the rest
Section Tablet of the panel: pages.vip = false. The page disappears and no coins are needed.
Change the tablet key
Section Tablet of the panel: keybind = F2. Players who had already joined keep their binding and can change it in the GTA key settings.
