QBCore Scripting Guide
Learn how to create custom scripts and resources for the QBCore framework. This comprehensive guide covers everything from basic script structure to advanced development techniques.
Table of Contents
- Getting Started
- Basic Script Structure
- QBCore API Integration
- Creating Your First Script
- Advanced Concepts
- Best Practices
- Deployment and Testing
Getting Started
Prerequisites
Before you start scripting for QBCore, ensure you have:
- QBCore Framework: Latest version installed
- Development Environment: VS Code or similar editor
- Basic Lua Knowledge: Understanding of Lua programming language
- FiveM Server: Development server for testing
- Database Access: For data persistence
Development Environment Setup
-
Install VS Code Extensions:
- Lua Language Server
- FiveM Development Tools
- GitLens (for version control)
-
Create Development Workspace:
mkdir qbcore-scripts cd qbcore-scripts
-
Set up Version Control:
git init git add .gitignore
Basic Script Structure
Standard QBCore Resource Structure
resource-name/
├── fxmanifest.lua # Resource manifest
├── config.lua # Configuration file
├── server/
│ ├── main.lua # Server-side logic
│ └── callbacks.lua # Server callbacks
├── client/
│ ├── main.lua # Client-side logic
│ └── events.lua # Client events
├── shared/
│ └── config.lua # Shared configuration
└── html/ # NUI files (if needed)
├── index.html
├── style.css
└── script.js
Essential Files
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
description 'QBCore Custom Script'
version '1.0.0'
author 'YourName'
shared_scripts {
'shared/config.lua',
'config.lua'
}
client_scripts {
'client/main.lua',
'client/events.lua'
}
server_scripts {
'@oxmysql/lib/MySQL.lua', -- If using database
'server/main.lua',
'server/callbacks.lua'
}
-- NUI (if using HTML interface)
ui_page 'html/index.html'
files {
'html/index.html',
'html/style.css',
'html/script.js'
}
dependencies {
'qb-core'
}
lua54 'yes'
QBCore API Integration
Getting QBCore Object
Always get the QBCore object at the beginning of your scripts:
local QBCore = exports['qb-core']:GetCoreObject()
Player Data Access
Client-Side Player Data
-- Get local player data
local PlayerData = QBCore.Functions.GetPlayerData()
-- Listen for player data updates
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
PlayerData = QBCore.Functions.GetPlayerData()
-- Initialize your script here
end)
RegisterNetEvent('QBCore:Client:OnPlayerUnload', function()
PlayerData = {}
-- Clean up when player logs out
end)
RegisterNetEvent('QBCore:Player:SetPlayerData', function(val)
PlayerData = val
-- Handle player data updates
end)
Server-Side Player Data
-- Get player by source
local Player = QBCore.Functions.GetPlayer(source)
if not Player then return end
-- Get player by citizen ID
local Player = QBCore.Functions.GetPlayerByCitizenId(citizenId)
-- Get player by phone number
local Player = QBCore.Functions.GetPlayerByPhone(phone)
Creating Your First Script
Let’s create a simple “Daily Bonus” script:
Step 1: Create the Resource Structure
mkdir qb-dailybonus
cd qb-dailybonus
Step 2: Create fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
description 'QBCore Daily Bonus System'
version '1.0.0'
author 'YourName'
shared_script 'config.lua'
client_script 'client/main.lua'
server_script 'server/main.lua'
dependencies {
'qb-core',
'oxmysql'
}
lua54 'yes'
Step 3: Create Configuration
-- config.lua
Config = {}
Config.DailyBonus = {
money = 1000, -- Cash reward
bank = 500, -- Bank reward
items = { -- Item rewards
{name = 'bread', amount = 5},
{name = 'water', amount = 3}
}
}
Config.ResetTime = 24 * 60 * 60 * 1000 -- 24 hours in milliseconds
Step 4: Server-Side Logic
-- server/main.lua
local QBCore = exports['qb-core']:GetCoreObject()
-- Database table creation
MySQL.ready(function()
MySQL.query([[
CREATE TABLE IF NOT EXISTS player_dailybonus (
citizenid VARCHAR(50) PRIMARY KEY,
last_claim TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
]])
end)
-- Server callback for checking eligibility
QBCore.Functions.CreateCallback('qb-dailybonus:server:canClaim', function(source, cb)
local Player = QBCore.Functions.GetPlayer(source)
if not Player then return cb(false) end
local citizenId = Player.PlayerData.citizenid
MySQL.single('SELECT last_claim FROM player_dailybonus WHERE citizenid = ?', {
citizenId
}, function(result)
if not result then
-- First time claim
cb(true)
else
local lastClaim = result.last_claim
local currentTime = os.time() * 1000
local lastClaimTime = tonumber(lastClaim)
if (currentTime - lastClaimTime) >= Config.ResetTime then
cb(true)
else
cb(false)
end
end
end)
end)
-- Claim daily bonus
RegisterNetEvent('qb-dailybonus:server:claimBonus', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local citizenId = Player.PlayerData.citizenid
-- Give rewards
Player.Functions.AddMoney('cash', Config.DailyBonus.money)
Player.Functions.AddMoney('bank', Config.DailyBonus.bank)
-- Give items
for _, item in pairs(Config.DailyBonus.items) do
Player.Functions.AddItem(item.name, item.amount)
end
-- Update database
MySQL.insert('INSERT INTO player_dailybonus (citizenid, last_claim) VALUES (?, ?) ON DUPLICATE KEY UPDATE last_claim = ?', {
citizenId, os.time() * 1000, os.time() * 1000
})
TriggerClientEvent('QBCore:Notify', src, 'Daily bonus claimed successfully!', 'success')
end)
Step 5: Client-Side Logic
-- client/main.lua
local QBCore = exports['qb-core']:GetCoreObject()
-- Command to claim daily bonus
RegisterCommand('dailybonus', function()
QBCore.Functions.TriggerCallback('qb-dailybonus:server:canClaim', function(canClaim)
if canClaim then
TriggerServerEvent('qb-dailybonus:server:claimBonus')
else
QBCore.Functions.Notify('You already claimed your daily bonus!', 'error')
end
end)
end)
-- Add command suggestion
TriggerEvent('chat:addSuggestion', '/dailybonus', 'Claim your daily bonus')
Advanced Concepts
Database Integration
Using oxmysql
-- Insert data
MySQL.insert('INSERT INTO table_name (column1, column2) VALUES (?, ?)', {
value1, value2
}, function(insertId)
print('Inserted with ID: ' .. insertId)
end)
-- Select single row
MySQL.single('SELECT * FROM table_name WHERE id = ?', {id}, function(result)
if result then
print('Found: ' .. result.name)
end
end)
-- Select multiple rows
MySQL.query('SELECT * FROM table_name WHERE active = ?', {true}, function(results)
for i = 1, #results do
print('Row: ' .. results[i].name)
end
end)
-- Update data
MySQL.update('UPDATE table_name SET column1 = ? WHERE id = ?', {
newValue, id
}, function(affectedRows)
print('Updated ' .. affectedRows .. ' rows')
end)
Event Handling
Custom Events
-- Server to Client
TriggerClientEvent('custom:event', source, data)
-- Client to Server
TriggerServerEvent('custom:event', data)
-- Register event handler
RegisterNetEvent('custom:event', function(data)
-- Handle the event
print('Received data: ' .. json.encode(data))
end)
QBCore Specific Events
-- Player loaded
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
-- Player finished loading
end)
-- Job change
RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo)
-- Player job changed
end)
-- Gang change
RegisterNetEvent('QBCore:Client:OnGangUpdate', function(GangInfo)
-- Player gang changed
end)
Creating Callbacks
-- Server callback
QBCore.Functions.CreateCallback('script:getData', function(source, cb, ...)
local args = {...}
local Player = QBCore.Functions.GetPlayer(source)
-- Process data
cb(responseData)
end)
-- Client trigger callback
QBCore.Functions.TriggerCallback('script:getData', function(data)
-- Handle response
print(json.encode(data))
end, arg1, arg2)
NUI Integration
HTML Interface
<!-- html/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Custom Interface</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="container">
<h1>Custom Script Interface</h1>
<button id="closeBtn">Close</button>
</div>
<script src="script.js"></script>
</body>
</html>
JavaScript Communication
// html/script.js
window.addEventListener('message', function(event) {
const data = event.data;
switch(data.action) {
case 'open':
$('#container').show();
break;
case 'close':
$('#container').hide();
break;
}
});
$('#closeBtn').click(function() {
$.post('https://resource-name/close', {});
});
Lua NUI Integration
-- Client-side NUI control
RegisterCommand('openui', function()
SetNuiFocus(true, true)
SendNUIMessage({
action = 'open'
})
end)
RegisterNUICallback('close', function(data, cb)
SetNuiFocus(false, false)
SendNUIMessage({
action = 'close'
})
cb('ok')
end)
Best Practices
Code Organization
- Separate Concerns: Keep client and server logic separate
- Use Config Files: Make your scripts configurable
- Error Handling: Always check for nil values and edge cases
- Consistent Naming: Use clear, descriptive variable names
Performance Optimization
-- Cache frequently used values
local PlayerData = QBCore.Functions.GetPlayerData()
-- Use proper thread management
CreateThread(function()
while true do
if PlayerData then
-- Do work
end
Wait(1000) -- Don't run every frame
end
end)
-- Clean up resources
AddEventHandler('onResourceStop', function(resource)
if resource == GetCurrentResourceName() then
-- Cleanup code here
end
end)
Security Considerations
-- Server-side validation
RegisterNetEvent('script:doSomething', function(data)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
-- Always validate player exists
if not Player then return end
-- Validate input data
if not data or type(data) ~= 'table' then
return
end
-- Additional security checks
if not Player.Functions.GetItemByName('required_item') then
TriggerClientEvent('QBCore:Notify', src, 'You need the required item', 'error')
return
end
-- Process request
end)
Deployment and Testing
Local Testing
-
Add to server.cfg:
start qb-dailybonus
-
Monitor for errors:
tail -f server_log.txt
-
Test all functions:
- Join server as different players
- Test edge cases
- Verify database operations
Production Deployment
- Code Review: Review all code for security issues
- Database Backup: Backup database before deployment
- Staged Rollout: Test on staging environment first
- Monitoring: Monitor server performance after deployment
Congratulations! You now have the foundation to create powerful QBCore scripts. Remember to always test thoroughly and follow best practices for security and performance.
For more advanced topics, check out our API documentation or join our community discussions.