lation scripts
scriptsmembershipdocs
see the scripts
Skip to Content
lation ui
lation interact
lation banking
lation shops
lation jobs
lation weed
Admin panelGrowingLabsStrainsConfigurationDevelopersChangelog
lation chop shop

classics

money laundering
drug sales
coke processing
meth crafting
metal detecting
scuba diving
lation weed

Developers

The lation weed server and client exports, the events it fires and its server and client hooks, with signatures, return shapes and Lua examples.

Exports, events and hooks for connecting lation weed to other resources. Every export is called on exports.lation_weed. A player is their server id (source), and a lab is its id: the number purchaseLab returns, which is also the routing bucket of the lab and the bucketId of its plants.

Server exports

ExportReturns
GetRuntimeConfig(section?)The live configuration
purchaseLab(source, labKey, options?){ ok, labId?, error? }
getLabBalance(labId)The lab wallet's balance
getPlayerData(source, field?)A player's level, XP and stats
addXP(source, amount){ ok, xp?, level?, error? }
removeXP(source, amount){ ok, xp?, level?, error? }
setLevel(source, level){ ok, xp?, level?, error? }
addPlayerData(source, dataType, amount)true, or nil when refused
getLevelData()The XP for each level
getPlayerPlantCount(source)How many plants a player owns
getPlantData(plantId, property?)A plant, or one field of it
getAllPlants()Every plant
getPlantsByOwner(owner)A player's plants
getPlantsByLab(labId)The plants in a lab
setPlantData(plantId, values){ ok, plant?, error?, field? }

The exports that change something answer { ok = true, ... } when it worked, and { ok = false, error = '<code>' } when nothing changed and the code says why. The plants and player records the exports hand out are copies: changing one changes nothing in the game.

GetRuntimeConfig

Server export

exports.lation_weed:GetRuntimeConfig(section)

Parameters

sectionstringoptionaldefault nil
setup, growth, planting, dealer, strains, fertilizers, soils, logs, labs, farms, experience, tables, shop or joints; every section when left out

Returns

table | nil
That section, keyed as on the configuration page, or a table of every section keyed by name. nil for an unknown section.

The table is the live configuration, not a copy: read it, and change settings in /weedadmin. It is the only export in PascalCase. The logs section holds your Discord webhook, so don't pass it to clients.

server.lua
local planting = exports.lation_weed:GetRuntimeConfig('planting')
print(('players can own %d plants'):format(planting.max))

purchaseLab

Server export

local result = exports.lation_weed:purchaseLab(source, labKey, options)

Parameters

sourcenumber
The server id of an online player
labKeystring
The lab's key, such as mission_row_1
options.skipPaymentbooleanoptionaldefault false
true grants the lab without charging the player

Returns

table
{ ok = true, labId = number }, or { ok = false, error = string }

Buys or grants a lab, for selling labs through your own script. It ignores the lab's Purchase interaction and Marketplace listing switches, but still checks the player's level, the labs-per-player cap and whether someone already owns the lab. Unless skipPayment is set, it charges the lab's price from the Labs > Accounts > Purchase account. The new owner starts with an empty stash.

The error is one of no-identifier, invalid-key, lab-not-found, level-too-low, max-per-player, already-owned, insufficient-funds or insert-failed.

A lab granted with skipPayment still records its price as the price paid, so selling it later refunds the Sell refund share of that price.

server.lua
local result = exports.lation_weed:purchaseLab(source, 'mission_row_1', { skipPayment = true })
if not result.ok then
    print(('lab purchase failed: %s'):format(result.error))
    return
end
print(('granted lab %d'):format(result.labId))

getLabBalance

Server export

exports.lation_weed:getLabBalance(labId)

Parameters

labIdnumber
The lab's id, from purchaseLab or a plant's bucketId

Returns

number
The lab wallet's balance, or 0 when no lab has that id

There is no export that sells or takes back a lab. However a lab ends (sold, removed by an admin, or reclaimed for inactivity), its wallet goes back to its owner and its stash is emptied; see selling a lab.

server.lua
local plant = exports.lation_weed:getPlantData(plantId)
if plant and plant.bucketId > 0 then
    print(('that lab holds $%d'):format(exports.lation_weed:getLabBalance(plant.bucketId)))
end

getPlayerData

Server export

exports.lation_weed:getPlayerData(source, field)

Parameters

sourcenumber
The server id of an online player
fieldstringoptionaldefault nil
One field to return; the whole record when left out

Returns

table | any | nil
The record (below), or the value of field. nil when the player has no identifier.

A player with no record gets one at level 1 on the first call. The record is the cached one, so treat it as read-only and change XP with addXP, removeXP and setLevel. The level is always the one the player's XP reaches on the live level ladder.

{
    identifier = 'ABC12345',
    level = 2,
    exp = 3150,
    plants_searched = 12,  -- farm searches that found seeds
    plants_grown = 9,      -- harvests
    bud_produced = 84,
    joints_rolled = 30,
    weed_bagged = 15,
}
server.lua
local level = exports.lation_weed:getPlayerData(source, 'level')
if level and level >= 3 then
    -- let them in
end

addXP

Server export

local result = exports.lation_weed:addXP(source, amount)

Parameters

sourcenumber
The server id of an online player
amountnumber
XP to give, above 0. It is rounded to a whole number.

Returns

table
{ ok = true, xp = number, level = number } with the new total and level, or { ok = false, error = string }

Gives XP the way a harvest does: the player moves up the level ladder and gets the level up notification through lation_weed:levelUp. The XP is taken as given: no strain's XP multiplier applies. Like gameplay XP, it is saved when the player leaves and every 3 minutes.

server.lua
local result = exports.lation_weed:addXP(source, 250)
if result.ok then
    print(('now level %d with %d XP'):format(result.level, result.xp))
end

removeXP

Server export

local result = exports.lation_weed:removeXP(source, amount)

Parameters

sourcenumber
The server id of an online player
amountnumber
XP to take, above 0. It is rounded to a whole number.

Returns

table
Like addXP's

Takes XP away, stopping at 0. A player whose XP falls below their level's threshold drops to the level the ladder puts them at and is told through lation_weed:levelDown.

server.lua
exports.lation_weed:removeXP(source, 1000)

setLevel

Server export

local result = exports.lation_weed:setLevel(source, level)

Parameters

sourcenumber
The server id of an online player
levelnumber
A whole level on the ladder, from 1 to the number of levels

Returns

table
Like addXP's

Puts a player at a level. XP already inside that level stays; otherwise it moves to the level's threshold. The player is told when the level goes up or down.

server.lua
exports.lation_weed:setLevel(source, 3)

The error of all three is one of:

errorWhy
invalid-amountaddXP or removeXP: not a number above 0
invalid-levelsetLevel: not a whole level on the ladder
player-not-foundNo such player online, or their record couldn't be loaded

addPlayerData

Server export

exports.lation_weed:addPlayerData(source, dataType, amount)

Parameters

sourcenumber
The server id of an online player
dataTypestring
exp, or one of the counters: plants_searched, plants_grown, bud_produced, joints_rolled, weed_bagged
amountnumber
How much to add. Anything 0 or below does nothing.

Returns

boolean | nil
true, or nil when the arguments don't fit or the player has no record

The lower-level way in, for the lifetime counters. Adding exp levels up and notifies like addXP, which is the one to use for XP.

server.lua
exports.lation_weed:addPlayerData(source, 'joints_rolled', 1)

getLevelData

Server export

exports.lation_weed:getLevelData()

Returns

table
The total XP for each level, in order, starting with level 1 at 0
server.lua
local levels = exports.lation_weed:getLevelData()
-- { 0, 2500, 10000, 20000, 50000, 100000 }

getPlayerPlantCount

Server export

exports.lation_weed:getPlayerPlantCount(source)

Parameters

sourcenumber
The server id of an online player

Returns

number
How many plants the player owns, outdoors and in labs. Plants put down by lab workers belong to the lab and aren't counted.
server.lua
if exports.lation_weed:getPlayerPlantCount(source) >= 10 then
    -- a big grower
end

getPlantData

Server export

exports.lation_weed:getPlantData(plantId, property)

Parameters

plantIdnumber
The plant's id
propertystringoptionaldefault nil
One field to return; the whole plant when left out

Returns

table | any | nil
A copy of the plant (below), the value of property, or nil when there is no such plant

The plant:

{
    id = 42,
    owner = 'ABC12345',          -- the identifier of who put it down; 'lab_<labId>' for a lab worker's plant
    strainId = 'og_kush',        -- nil until a seed is planted
    model = 'shoe_shuffler_prop_weed_med_green_01a',
    x = 0.0, y = 0.0, z = 0.0, w = 0.0,
    stage = 5,                   -- 1 empty pot, 2 pot with soil, 3 to 8 growing
    growth = 40,                 -- 0 to 100
    health = 100,                -- 0 to 100
    water = 55,                  -- 0 to 100
    fertilizer = 30,             -- 0 to 100
    soil = 'ls_basic_soil',      -- nil until soil is added
    fertilizerType = 'ls_basic_fertilizer',
    isCovered = false,           -- under cover, so no rain
    deathTimer = nil,            -- seconds left while the plant is dying
    bucketId = 0,                -- 0 outdoors; the lab's id inside a lab
}
server.lua
local health = exports.lation_weed:getPlantData(plantId, 'health')

getAllPlants

Server export

exports.lation_weed:getAllPlants()

Returns

table
Every plant on the server, keyed by plant id, each a copy shaped like getPlantData's
server.lua
for plantId, plant in pairs(exports.lation_weed:getAllPlants()) do
    if plant.growth >= 100 then
        print(('plant %d is ready'):format(plantId))
    end
end

getPlantsByOwner

Server export

exports.lation_weed:getPlantsByOwner(owner)

Parameters

ownernumber | string
The player's server id while they are online, or their identifier as text: the citizenid on Qbox and QBCore, the identifier on ESX, tostring(charId) on ox_core

Returns

table
The plants the player owns, as a list in id order, each a copy shaped like getPlantData's. Empty when they own none.
server.lua
local mine = exports.lation_weed:getPlantsByOwner(source)
print(('%d plants'):format(#mine))

getPlantsByLab

Server export

exports.lation_weed:getPlantsByLab(labId)

Parameters

labIdnumber
The lab's id, the same one getLabBalance takes

Returns

table
The plants inside the lab, as a list in id order, each a copy shaped like getPlantData's, the lab workers' plants included
server.lua
for _, plant in ipairs(exports.lation_weed:getPlantsByLab(labId)) do
    print(plant.id, plant.strainId, plant.growth)
end

setPlantData

Server export

local result = exports.lation_weed:setPlantData(plantId, values)
local result = exports.lation_weed:setPlantData(plantId, field, value)

Parameters

plantIdnumber
The plant's id
valuestable
The fields to set (below), or the name of one field followed by its value

Returns

table
{ ok = true, plant = table } with a copy of the plant as it is now, or { ok = false, error = string, field = string }

Sets one or more fields of a plant, checked the way the admin panel's plant editor checks them, then saved, put back in the growth loop where it belongs and shown to the players who can see the plant, like a change the game makes itself.

FieldAccepts
water, fertilizer, health, growthA number, kept within 0 to 100 and rounded
stage1 to 8. Stage 3 and above needs a strain.
strainIdA strain's key; '' or false empties it
soilA soil's key; '' or false empties it
fertilizerTypeA fertilizer's key; '' or false empties it
isCoveredtrue or false

What the game keeps in step for you:

  • Setting growth on a growing plant without a stage moves the stage, and the model, to the one that growth shows, as the growth loop does. At 100 the plant is ready to harvest.
  • Giving health back to a dying plant stops its death timer.
  • A stage or strain change picks the model that stage shows.

Anything else about a plant (its owner, where it stands, its model, its death timer) can't be set. One refused field refuses the whole call, and nothing changes.

errorWhy
plant-not-foundNo plant with that id
invalid-valuesNo fields given
unknown-fieldA field that can't be set; field names it
invalid-valueA value that doesn't fit: not a number, a stage outside 1 to 8, a key the config doesn't have; field names it
strain-requiredStage 3 or above on a plant with no strain
databaseThe plant couldn't be saved

After every successful call, lation_weed:server:plantChanged fires with the plant.

server.lua
-- a sprinkler: top up every plant in a lab
for _, plant in ipairs(exports.lation_weed:getPlantsByLab(labId)) do
    exports.lation_weed:setPlantData(plant.id, { water = 100 })
end

-- one field
local result = exports.lation_weed:setPlantData(42, 'health', 100)
if not result.ok then
    print(('refused: %s'):format(result.error))
end

-- plant a strain in a pot that has soil
exports.lation_weed:setPlantData(42, { strainId = 'og_kush', stage = 3 })

Client exports

openLabMarketplace

Client export

exports.lation_weed:openLabMarketplace()

Returns

boolean
true when the marketplace opened, false when it couldn't load

Opens the lab marketplace for the local player, from your own phone app or NPC for example. Players buy through it with the usual rules, so labs with their Marketplace listing off aren't shown.

client.lua
if not exports.lation_weed:openLabMarketplace() then
    print('marketplace unavailable')
end

Events

Server

Local server events: listen with AddEventHandler in a server script.

lation_weed:configUpdated

Server event

AddEventHandler('lation_weed:configUpdated', function(snapshot) end)

Parameters

snapshottable
Every section, keyed by name, in the shape an export file has. The logs section is included.

Fires when the configuration loads on start, and after every save, reset or import in the admin panel.

server.lua
AddEventHandler('lation_weed:configUpdated', function(snapshot)
    print(('players can now own %d plants'):format(snapshot.planting.max))
end)

lation_weed:server:plantChanged

Server event

AddEventHandler('lation_weed:server:plantChanged', function(plantId, plant) end)

Parameters

plantIdnumber
The plant's id
planttable | nil
The new plant, shaped like getPlantData's; nil when it was removed

Fires when a plant is created, when one is removed by harvest, destroy, death, cleanup, an admin, a sale or a lab being closed, and after every successful setPlantData. Other changes to a living plant (growth, care, the admin panel's editor) don't fire it. The plant passed is the server's own record, so read it without changing it.

server.lua
AddEventHandler('lation_weed:server:plantChanged', function(plantId, plant)
    if not plant then
        print(('plant %d is gone'):format(plantId))
    end
end)

lation_weed:server:plantNeedsAttention

Server event

AddEventHandler('lation_weed:server:plantNeedsAttention', function(plantId, reason) end)

Parameters

plantIdnumber
The plant's id
reasonstring
harvest: the plant reached 100% growth
server.lua
AddEventHandler('lation_weed:server:plantNeedsAttention', function(plantId, reason)
    if reason ~= 'harvest' then return end
    local owner = exports.lation_weed:getPlantData(plantId, 'owner')
    print(('plant %d owned by %s is ready'):format(plantId, owner))
end)

Client

lation_weed:runtimeReady

Client event

AddEventHandler('lation_weed:runtimeReady', function() end)

Fires when the configuration reaches this client: after joining, after a restart, and after every save in the admin panel. Listen with AddEventHandler.

lation_weed:levelUp

Client event

RegisterNetEvent('lation_weed:levelUp', function(newLevel) end)

Parameters

newLevelnumber
The level the player reached

Sent by the server when the player levels up, from gameplay, addXP, setLevel or a level ladder edited in the admin panel; lation weed shows its own notification as well. Listen with RegisterNetEvent.

client.lua
RegisterNetEvent('lation_weed:levelUp', function(newLevel)
    print(('reached weed level %d'):format(newLevel))
end)

lation_weed:levelDown

Client event

RegisterNetEvent('lation_weed:levelDown', function(newLevel) end)

Parameters

newLevelnumber
The level the player dropped to

Sent by the server when removeXP or setLevel moves the player down a level; lation weed shows its own notification as well. A level ladder edited in the admin panel moves players down without it.

lation_weed:notify

Client event

TriggerClientEvent('lation_weed:notify', source, message, type)

Parameters

messagestring
The text
typestringoptionaldefault nil
success, error, info and the like, as your notification system reads them

Send it from the server to show a notification through the system set under Setup > Integrations > Notifications.

server.lua
TriggerClientEvent('lation_weed:notify', source, 'Your harvest is ready', 'success')

Hooks

Hooks are functions in files that ship open: server/utils/hooks.lua and client/utils/hooks.lua. Edit them in place.

onLabRaidAttempt

Server hook

function Hooks.onLabRaidAttempt(data) end

Parameters

data.raiderSourcenumber
The raider's server id
data.raiderIdentifierstring | nil
The raider's identifier
data.raiderNamestring
The raider's character name
data.isPoliceboolean
The raider holds a police job
data.ownerIdentifierstring
The owner's identifier
data.ownerSourcenumber | nil
The owner's server id, while they are online
data.labIdnumber
The lab's id
data.labKeystring
The lab's key
data.labLabelstring
The lab's name
data.coordsvector3 | nil
The lab's entrance

Runs when a player confirms a break-in and the server accepts it, before the skill check. Empty by default; use it for dispatch alerts.

server/utils/hooks.lua
function Hooks.onLabRaidAttempt(data)
    if data.isPolice then return end
    exports['ps-dispatch']:CustomAlert({
        message = 'Possible break in detected',
        coords = data.coords,
        sprite = 499,
        color = 1,
        scale = 1.0,
        length = 3,
        jobs = { 'police' },
    })
end

onLabRaidBreached

Server hook

function Hooks.onLabRaidBreached(data) end

Parameters

data.messagestring
The alert text, already translated
data.notifyOwnerboolean
Labs > Raids > Notify the owner is on
data.ownerIdentifierstring
The owner's identifier
data.ownerSourcenumber | nil
The owner's server id, while they are online
data.labIdnumber
The lab's id
data.labKeystring
The lab's key
data.labLabelstring
The lab's name
data.raiderSourcenumber
The raider's server id
data.raiderIdentifierstring | nil
The raider's identifier
data.raiderNamestring
The raider's character name
data.isPoliceboolean
The raider holds a police job

Runs after every successful break-in, whatever Labs > Raids > Notify the owner is set to and whether or not the owner is online, so a dispatch, phone or mail script keeps working with the in-game notice off. By default it sends the owner a notification when notifyOwner is on and they are online.

server/utils/hooks.lua
function Hooks.onLabRaidBreached(data)
    if data.notifyOwner and data.ownerSource then
        TriggerClientEvent('lation_weed:notify', data.ownerSource, data.message, 'error')
        return
    end
    -- the owner is offline, or the in-game notice is off: send your own here
    -- exports['my-phone']:SendMail(data.ownerIdentifier, data.labLabel, data.message)
end

If you edited this hook before 4.7.0, check data.notifyOwner before sending your own in-game notice.

onJointSmoked (server)

Server hook

function Hooks.onJointSmoked(data) end

Parameters

data.sourcenumber
The player's server id
data.identifierstring | nil
The player's identifier
data.strainIdstring
The strain's key
data.strainLabelstring | nil
The strain's name
data.jointItemstring
The joint item
data.stressReliefnumber
The joint's stress relief, 0 when it is off
data.durationnumber
How long the smoke lasts, in milliseconds

Runs on the server when a player finishes smoking and the joint is taken from their inventory. Empty by default.

server/utils/hooks.lua
function Hooks.onJointSmoked(data)
    print(('%s smoked a %s joint'):format(data.identifier, data.strainLabel or data.strainId))
end

onJointSmoked (client)

Client hook

function Hooks.onJointSmoked(data) end

Parameters

data.strainIdstring
The strain's key
data.strainLabelstring | nil
The strain's name
data.jointItemstring | nil
The joint item
data.stressReliefnumber
The joint's stress relief, 0 when it is off
data.durationnumber | nil
How long the smoke lasted, in milliseconds

Runs on the player's client once they finish smoking and the effects apply. By default it relieves stress with the hud:server:RelieveStress event that QBCore and Qbox HUDs listen for:

client/utils/hooks.lua
function Hooks.onJointSmoked(data)
    if not data then return end

    local amount = tonumber(data.stressRelief) or 0
    if amount <= 0 then return end

    TriggerServerEvent('hud:server:RelieveStress', amount)
end

If your HUD handles stress another way, change this function.

Open files

These files ship open, so you can fit the script to your server:

FileWhat it holds
bridge/server.lua, bridge/client.luaFramework and inventory support: players, jobs, money, items and usable items, each with a slot for a custom one
server/utils/perms.luaIsAdmin, the check for /weedadmin
server/utils/stash.lua, client/utils/stash.luaThe lab stash on each inventory, and ClearStash, which empties it when a lab is sold or closed. A custom inventory needs both.
server/utils/weather.luaRain detection
server/utils/logging.luafivemanage and Discord logging
server/utils/hooks.lua, client/utils/hooks.luaThe hooks above
client/utils/ui.luaNotifications, menus, dialogs, progress bars, text UI and skill checks, with the custom slots
client/utils/interact.luaThe interaction systems with a custom slot, and how close players must be to use a prompt (InteractDistance, 2 m)
shared/defaults.luaThe shipped defaults (server only); see configuration
server/settings.luaWhat the admin panel can edit: every option's type and limits, and the four marked restart
config/icons.lua, config/animations.luaIcons and animations
locales/*.jsonEvery string

Still stuck? Ask in our Discord: the people who write the scripts answer.

Last updated November 10, 2026

Configuration

Every lation weed setting with its default and limits, as edited in game under /weedadmin > Config, plus the files you still edit by hand.

Changelog

Every lation weed release, newest first, with what it added, changed and fixed.

On this page

Server exportsGetRuntimeConfigpurchaseLabgetLabBalancegetPlayerDataaddXPremoveXPsetLeveladdPlayerDatagetLevelDatagetPlayerPlantCountgetPlantDatagetAllPlantsgetPlantsByOwnergetPlantsByLabsetPlantDataClient exportsopenLabMarketplaceEventsServerlation_weed:configUpdatedlation_weed:server:plantChangedlation_weed:server:plantNeedsAttentionClientlation_weed:runtimeReadylation_weed:levelUplation_weed:levelDownlation_weed:notifyHooksonLabRaidAttemptonLabRaidBreachedonJointSmoked (server)onJointSmoked (client)Open files
lation scripts

FiveM scripts for Qbox, QBCore and ESX servers, made to look and work like one product.

store

  • all scripts
  • membership
  • classics

help

  • docs
  • discord
  • email support

legal

  • terms
  • refunds
  • privacy
© 2026 Lation Labslationscripts.com
  • home
  • scripts
  • membership
  • docs