FiveM & QBCore · Practical guide

How to Add a Crafting System to Your QBCore FiveM Server

A crafting system lets players combine items to make new ones — perfect for drug processing, weapon modifications and survival mechanics on your FiveM server.

Stellar AI · Updated 8 September 2026 · 7 min read

A practical guide to how to add a crafting system to your qbcore fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.

Overview

This guide walks through adding a robust, server-authoritative crafting system to a QBCore-based FiveM server and outlines parallel concerns for Roblox/Luau. You will learn architectural patterns, item and recipe modeling, validation and anti-cheat measures, integration options (ESX/ox_lib considerations), how to test safely, and how to deploy and maintain the system. If you want to prototype or coordinate tasks with designers and developers, consider using the Stellar AI collaboration tool: https://trystellarai.com/app.

Design and Architecture

Crafting systems are stateful: they change player inventories, consume items, produce items, and may trigger cooldowns or skill checks. The most important architectural principle is server authority. The server must be the only component that confirms recipe success, updates inventory, and persists results.

Core components:

  • Item database: canonical definitions for item IDs, stack size, metadata, and crafting flags.
  • Recipe registry: a server-side table of recipes mapping ingredients to results, cost, and optional metadata (time, skill checks, chance).
  • Server validation layer: checks availability, concurrency locks, cooldowns, and anti-duplication safeguards.
  • Client UI: displays recipes and submits craft requests, but never performs final validation.
  • Persistence/storage: inventory database or player items storage (QBCore SQL, ESX DB, Roblox DataStore).

Choice considerations: QBCore, ESX, ox_lib

QBCore provides utilities like QBCore.Functions.GetPlayer and inventory helpers that simplify server-side work. ESX has its own player object patterns. ox_lib is a client-side helper library (menus, contexts). When using ox_lib for the client UI, ensure it only communicates with the server for authoritative operations. Architect so that the server can operate independently of the client UI resource.

Data Model and Recipe Management

Model recipes as immutable server-side records. A minimal recipe entry:

{
  id = "stone_pickaxe",
  ingredients = { {item = "wood", amount = 2}, {item = "stone", amount = 3} },
  result = { item = "stone_pickaxe", amount = 1 },
  craftingTime = 5000, -- milliseconds
  chance = 1.0 -- 0.0-1.0 optional
}

Store items and recipes in a single shared Lua table on the server and expose read-only callbacks to clients for UI rendering. Avoid sending mutable recipe objects to the client; send only read-only metadata and localized strings.

Server Validation and Security

Never trust client inputs. All craft requests must be validated by the server using these checks:

  • Existence: recipe ID exists in the server-side registry.
  • Inventory verification: the server checks the player's inventory for required quantities using canonical inventory APIs (QBCore.Functions.GetPlayer, ESX.GetPlayerFromId).
  • Concurrency: lock player inventory (or use transaction semantics) to prevent race conditions or double-spend.
  • Cooldowns and rate limits: server-enforced per-player cooldown or craft queue to prevent farm exploits.
  • Randomization and fairness: compute any chance/hit rate server-side using a secure RNG; do not rely on client RNG.
  • Logging and auditing: log craft events for anomaly detection (item ID, player ID, timestamp).

Example anti-cheat pattern: when a craft request arrives, immediately acquire a server-side lock (e.g., a per-player boolean busy flag). If locked, reject. After validation and applying inventory changes atomically, release the lock. Use database transactions where possible for persistence.

Implementation Patterns

Below are concise implementation examples showing server-side validation for FiveM (QBCore) and for Roblox (Luau). The goal is to demonstrate correct authority flow.

QBCore (FiveM) - Server-side event

-- server.lua (FiveM, QBCore)
RegisterNetEvent('mycraft:requestCraft', function(recipeId)
  local src = source
  local Player = QBCore.Functions.GetPlayer(src)
  if not Player then return end

  -- simple per-player lock
  if Player.metadata.isCrafting then
    TriggerClientEvent('mycraft:craftResult', src, false, "busy")
    return
  end
  Player.metadata.isCrafting = true

  local recipe = Recipes[recipeId]
  if not recipe then
    Player.metadata.isCrafting = false
    TriggerClientEvent('mycraft:craftResult', src, false, "invalid_recipe")
    return
  end

  -- Validate inventory
  for _, ing in ipairs(recipe.ingredients) do
    if Player.Functions.GetItemByName(ing.item) == nil or Player.Functions.GetItemByName(ing.item).amount < ing.amount then
      Player.metadata.isCrafting = false
      TriggerClientEvent('mycraft:craftResult', src, false, "missing_ingredients")
      return
    end
  end

  -- Remove ingredients atomically then add result
  for _, ing in ipairs(recipe.ingredients) do
    Player.Functions.RemoveItem(ing.item, ing.amount)
  end
  -- Optionally delay for craftingTime, but state must remain server-authoritative
  Citizen.SetTimeout(recipe.craftingTime, function()
    Player.Functions.AddItem(recipe.result.item, recipe.result.amount)
    Player.metadata.isCrafting = false
    TriggerClientEvent('mycraft:craftResult', src, true, "success")
  end)
end)

Key points: all inventory checks and changes happen server-side. The client only requests craft and receives the result.

Roblox (Luau) - Server-authoritative RemoteEvent

-- ServerScript in ServerScriptService
local RemoteEvent = game.ReplicatedStorage:WaitForChild("CraftRequest")
local DataStoreService = game:GetService("DataStoreService")
local playerInventoryStore = DataStoreService:GetDataStore("PlayerInventory")

RemoteEvent.OnServerEvent:Connect(function(player, recipeId)
  -- Validate input types
  if type(recipeId) ~= "string" then
    RemoteEvent:FireClient(player, false, "invalid_input")
    return
  end

  local recipe = Recipes[recipeId]
  if not recipe then
    RemoteEvent:FireClient(player, false, "invalid_recipe")
    return
  end

  -- Obtain player's inventory server-side (cache or DataStore)
  local ok, inventory = pcall(function()
    return playerInventoryStore:GetAsync(player.UserId)
  end)
  if not ok then
    RemoteEvent:FireClient(player, false, "datastore_error")
    return
  end

  -- Validate ingredients
  for _, ing in ipairs(recipe.ingredients) do
    if (inventory[ing.item] or 0) < ing.amount then
      RemoteEvent:FireClient(player, false, "missing_ingredients")
      return
    end
  end

  -- Deduct and write back with pcall + UpdateAsync pattern and retry
  local success = false
  local attempts = 0
  while not success and attempts < 5 do
    attempts = attempts + 1
    local updateOk, newInv = pcall(function()
      return playerInventoryStore:UpdateAsync(tostring(player.UserId), function(old)
        old = old or inventory
        for _, ing in ipairs(recipe.ingredients) do
          old[ing.item] = (old[ing.item] or 0) - ing.amount
        end
        old[recipe.result.item] = (old[recipe.result.item] or 0) + recipe.result.amount
        return old
      end)
    end)
    if updateOk then
      success = true
    else
      wait(2 ^ attempts * 0.1) -- exponential backoff
    end
  end

  if success then
    RemoteEvent:FireClient(player, true, "success")
  else
    RemoteEvent:FireClient(player, false, "save_failed")
  end
end)

UI, Feedback, and Crafting Stations

Client UI should be purely presentational: show recipes, amounts, and enable the "craft" button which sends the request. If using ox_lib for contextual menus or progress UI, ensure progress bars are cosmetic: treat them as indicators of server progress, not as authoritative timers. For visible feedback to the player, use server push events (TriggerClientEvent/RemoteEvent:FireClient) when the server changes player state.

For stationary crafting stations (workbenches), spawn them server-side and check proximity on the server or validate a token the client sends that proves the player is near a station (e.g., server tracks position or uses zone flags). Never accept a craft simply because the client says "I am at bench X".

Testing and Quality Assurance

Testing must verify race conditions, persistence failures, and exploits. Recommended tests:

  • Unit test recipe validation logic (server-only functions).
  • Simulate concurrent craft requests for same player to ensure locks prevent double-spend.
  • Simulate disconnects during crafting and verify inventory integrity.
  • Test datastore/database failures and recovery flows (for Roblox DataStore or SQL reconnects).
  • Run load tests for automated crafting farms and implement rate-limits accordingly.

Track suspicious patterns with logs and create automated alerts for sudden inventory creation spikes. Publish a concise bug report template and use it during testing cycles; if you use collaborative tooling, you can centralize artifact tracking with tools like https://trystellarai.com/app. For design reads and implementation notes consider writing developer posts on your team blog: https://trystellarai.com/blog.

Deployment and Maintenance

Roll out changes carefully. Recommended deployment approach:

  1. Feature-flag new recipes and crafting logic on the server to enable quick rollback.
  2. Deploy server-side code first. Clients should gracefully accept server changes (e.g., missing recipe IDs ignored).
  3. Monitor logs for unexpected item creation and increased datastore errors.
  4. Use database migrations for any persistent schema changes; back up inventory tables prior to large updates.

Maintenance checklist (short version):

Task Frequency Responsible
Audit recipe integrity and balance Monthly Game Designer
Inspect server logs for anomalies Daily SRE / DevOps
Backup inventories and database Daily DB Admin
Security review of RPC/Remote events Quarterly Security Engineer

Checklist: Practical Steps to Implement

  • Define canonical item IDs and server-side recipe registry.
  • Create server-side API for: listRecipes (read-only), requestCraft (server validates and performs changes), craftStatus (optional polling or push notifications).
  • Implement per-player locking and rate-limiting.
  • Use database transactions or UpdateAsync/persistent atomic patterns to avoid dupes.
  • Log craft events and create detection for abnormal creation patterns.
  • Gracefully handle persistence failures: report to client, retry with exponential backoff where safe, and avoid silent loss of items.
  • Test edge cases: disconnects mid-craft, simultaneous crafts, exploit simulations.

Testing Examples and Verification

Verify the following during QA:

  1. Try to craft using forged client packets that reference nonexistent items; server should reject.
  2. Queue multiple craft requests concurrently; server should serialize or reject redundant requests.
  3. Simulate database downtime and ensure the system fails safely (no item duplication and informative client errors).
  4. Check that crafting stations and proximity checks are validated server-side.

Build your next system with Stellar AI

Describe one feature, get organized project files, then bring back your errors to keep improving. Start free with no card required. Test generated code in a private development environment before release.

Create your first script free →