Exports & Integration

Three things your script does to plug into jj-perks: award XP so players level up, and (optionally) read the level or check owned perks to apply a bonus. All of this is server-side through exports['jj-perks'].

source/src is the player server id. The category string must match the id you set in config.

Award XP

Call this whenever the player does the activity (sells ore, finishes a run, completes a heist). It handles levelling up and the level-up notification for you.

exports['jj-perks']:addXP(src, 'mining', 25)
⚠️

Always award from the server. XP is server-authoritative — there is no client-triggered XP event, so a cheat menu can't inject levels. If the activity finishes on the client, route it to your own server event and call the export there.

Apply a perk bonus

Ask whether the player owns a specific perk, then do whatever that perk promises. This is where the gameplay effect actually happens.

local amount = 1
if exports['jj-perks']:hasPerk(src, 'mine_r2') then          -- "Double Drops I"
    if math.random(100) <= 5 then amount = 2 end             -- 5% chance, as the perk says
end
exports.ox_inventory:AddItem(src, 'iron_ore', amount)

Gate something behind a level

local level = exports['jj-perks']:getPlayerLevel(src, 'mining')   -- 0-10
if level < 5 then
    -- not deep-mine ready yet
    return
end

getPlayerProgress(src, 'mining') returns { level, xp, minXp, maxXp } if you want to show a progress bar somewhere.

Full example

A small mining handler that does all three:

RegisterNetEvent('my-mining:server:oreMined', function()
    local src = source

    -- 1. reward progression
    exports['jj-perks']:addXP(src, 'mining', 25)

    -- 2. faster gather if they own the speed perk (your timer reads this)
    local gatherTime = 5000
    if exports['jj-perks']:hasPerk(src, 'mine_r1') then
        gatherTime = gatherTime * 0.98
    end

    -- 3. double drop chance from the drops perk
    local amount = 1
    if exports['jj-perks']:hasPerk(src, 'mine_r2') and math.random(100) <= 5 then
        amount = 2
    end

    exports.ox_inventory:AddItem(src, 'iron_ore', amount)
end)

Safety

If jj-perks might be stopped while your script runs, wrap the calls so a missing export can't error your resource:

local function ownsPerk(src, perk)
    local ok, owned = pcall(function() return exports['jj-perks']:hasPerk(src, perk) end)
    return ok and owned
end

That's the whole loop: define the category in config, call addXP when the activity happens, and check hasPerk / getPlayerLevel wherever you want the perk to actually do something. House Robbery is a working reference — it awards houserobbery XP on a completed contract and gates its MEDIUM/GARAGE/OFFICE/HARD/EXTREME tiers behind jjhr_r3, jjhr_r5 and jjhr_r6/jjhr_r10.

In-game admin tools

Open the tablet and switch to the Admin tab. From there you can add or remove XP, set a category to an exact level, grant or revoke individual perks, refund a category, and fully reset an online player. No commands or DB edits needed.

FrameworkCounts as admin
QBCoreadmin/god permission, or ace command
QBoxace command or group.admin
ESXgroup admin/superadmin/mod, or ace command
ox_coreadmin group, or ace command
ND_Coreace command or group.admin

Server exports

-- give XP (auto level-up + notify)
exports['jj-perks']:addXP(source, 'houserobbery', 50)

-- read current level (0-10)
local level = exports['jj-perks']:getPlayerLevel(source, 'houserobbery')

-- full progress: { level, xp, minXp, maxXp }
local p = exports['jj-perks']:getPlayerProgress(source, 'houserobbery')

-- does the player own a perk?
local owned = exports['jj-perks']:hasPerk(source, 'jjhr_r3')

-- list every unlocked perk id
local perks = exports['jj-perks']:getPlayerPerks(source)

-- admin tooling, both return (ok, newXp|errMsg)
exports['jj-perks']:setLevel(source, 'houserobbery', 5)
exports['jj-perks']:removeXP(source, 'houserobbery', 100)

Client-side XP can also be awarded via the server event:

TriggerServerEvent('jj-perks:server:addXP', 'houserobbery', 25)

Client exports

For embedding the perk system into another UI (a phone app, tablet, etc.):

-- open the tablet (also the ox_inventory item export 'jj-perks.openTablet')
exports['jj-perks']:openTablet()

-- the same payload the tablet NUI receives: config, levels, maxLevel, playerData, isAdmin, branding
local ui = exports['jj-perks']:getPerksUiData()

-- unlock a perk for the calling player (runs the same server validation)
exports['jj-perks']:externalUnlockPerk('jjhr_r3')

-- refund a category for the calling player
exports['jj-perks']:externalRefundPerks('houserobbery')
⚠️

When integrating from another resource, wrap calls in pcall() so a dependent doesn't error if jj-perks is stopped.

Database

Three tables, created on first boot:

  • jj_perks_players
  • jj_perks_unlocked
  • jj_perks_category_xp

An optional install.sql is included if you'd rather create them yourself. The citizenid column holds whatever per-character id your framework uses.