DevelopmentJuly 12, 20266 min read

"FiveM Development Workflow: Git, Testing, and Deployment Best Practices"

"Production-grade workflow for FiveM resource development — git branching strategy, automated testing with qb-test, CI/CD pipeline setup, and zero-downtime deployment."

ZZiadLead Developer
Share

Shipping FiveM resources without a structured workflow leads to broken servers, angry players, and late-night rollbacks. Whether you're a solo developer or part of a team, a repeatable workflow saves time and prevents disasters.

This guide covers the full pipeline: version control with Git, automated testing, CI/CD integration, and zero-downtime deployment.

Git Branching Strategy

Use a simplified Git Flow tailored for FiveM development.

main ──●────────────●──────────●── \ / \ / develop ●──●──●──● ●──●──●─ \ / \ feature/ ●──● ●──●

Branch Rules

  • main — production. Merges only from develop. Tag every merge with a release number
  • develop — integration branch. All features merge here first
  • feature/<name> — individual features. Branch from develop, merge back
  • hotfix/<name> — emergency fixes. Branch from main, merge to both main and develop

Commit Convention

Standardize commit messages for automatic changelogs:

type(scope): description feat(resource): add bank robbery alarm system fix(shop): prevent negative price exploit refactor(core): extract payment logic to shared module docs(install): update ox_lib requirement

.gitignore for FiveM Projects

gitignore
# FiveM
cfx-server-data/
server-data/
[YOUR_SERVER].cfg
*.log

# IDE
.vscode/
.idea/
*.swp

# OS
.DS_Store
Thumbs.db

# Dependencies
[resources]/[framework]/
node_modules/

Resource Versioning

Every resource should have a meaningful version string. Use semantic versioning in your fxmanifest.lua:

lua
fx_version 'cerulean'
game 'gta5'

author 'Your Name'
description 'qb-banking: Comprehensive banking system'
version '2.1.0'

Tag releases in Git:

bash
git tag -a v2.1.0 -m "Release v2.1.0 - Add loan system and payment scheduler"
git push --tags

Automated Testing with qb-test

QBCore's built-in testing framework (qb-test) lets you run integration tests inside a headless server environment.

Writing Tests

lua
-- tests/test_banking.lua
local QBCore = exports['qb-core']:GetCoreObject()

return {
    name = "Banking System Tests",
    before = function()
        -- Setup: create a test character with known money
        local Player = QBCore.Functions.GetPlayer(1)
        Player.Functions.SetMoney("bank", 10000, "test-setup")
        Player.Functions.SetMoney("cash", 500, "test-setup")
    end,

    ["Should deposit cash to bank"] = function()
        local Player = QBCore.Functions.GetPlayer(1)
        local result = Player.Functions.AddMoney("bank", 500, "deposit")

        assert(result == true, "Deposit should succeed")
        assert(Player.PlayerData.money["bank"] == 10500, "Bank balance should be 10500")
        assert(Player.PlayerData.money["cash"] == 0, "Cash should be deducted")
    end,

    ["Should reject overdraft withdrawal"] = function()
        local Player = QBCore.Functions.GetPlayer(1)
        local result = Player.Functions.RemoveMoney("cash", 999999, "test")

        assert(result == false, "Overdraft should fail")
        assert(Player.PlayerData.money["cash"] == 500, "Cash should remain unchanged")
    end,
}

Running Tests

bash
# Run all tests
node qb-test/run.js

# Run specific test file
node qb-test/run.js tests/test_banking.lua

# Run with verbose output
node qb-test/run.js --verbose

What to Test

Test typeExample
Money operationsDeposit, withdraw, transfer, overflow
InventoryAdd item, remove item, stack limits
PermissionsJob gates, admin checks, ownership
Edge casesNegative amounts, zero quantities, missing players

CI/CD with GitHub Actions

Automate testing and deployment so you ship confidently.

GitHub Actions Workflow

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [develop, main]
  pull_request:
    branches: [develop]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup FiveM server
        run: |
          wget https://runtime.fivem.net/artifacts/fivem/build_proot_linux/master/LATEST_VERSION
          # Extract and configure headless server

      - name: Run tests
        run: node qb-test/run.js --ci

      - name: Lint Lua files
        uses: lunarmodules/luacheck@v1
        with:
          args: . --global

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to production
        run: |
          rsync -avz --delete \
            --exclude '.git' \
            --exclude 'tests' \
            ./ root@${{ secrets.SERVER_IP }}:/path/to/resources/
          ssh root@${{ secrets.SERVER_IP }} 'screen -S fivem -X stuff "ensure qb-banking\r"'

Luacheck Configuration

lua
-- .luacheckrc
globals = {
    "QBCore",
    "ESX",
    "Citizen",
    "RegisterNetEvent",
    "AddEventHandler",
    "TriggerEvent",
    "TriggerClientEvent",
    "MySQL",
    "exports",
}

Zero-Downtime Deployment

FiveM doesn't support hot-reloading resources gracefully. Use this pattern to minimize disruption:

  1. Prepare on staging — deploy to a staging server first
  2. Run integration tests — verify the resource works with live data
  3. Sync files — rsync to production (this takes ~10 seconds)
  4. Restart with ensure — use a screen session to ensure the resource
  5. Monitor logs — watch for errors for 5 minutes after deployment
bash
#!/bin/bash
# deploy.sh

RESOURCE="qb-banking"
SERVER="[email protected]"
REMOTE_PATH="/path/to/server-data/resources"

echo "=== Deploying $RESOURCE ==="

# Step 1: Sync files (exclude tests)
rsync -avz --delete \
    --exclude '.git' \
    --exclude 'tests' \
    --exclude '.github' \
    ./ $SERVER:$REMOTE_PATH/$RESOURCE/

# Step 2: Ensure on production
ssh $SERVER "screen -S fivem -X stuff \"ensure $RESOURCE\r\""

echo "=== Deployed $RESOURCE ==="

Database Migrations

Track schema changes in version control. Use a simple migration script:

lua
-- migrations/001_add_bank_accounts.sql
CREATE TABLE IF NOT EXISTS bank_accounts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    citizenid VARCHAR(50) NOT NULL,
    account_type VARCHAR(20) DEFAULT 'checking',
    balance INT DEFAULT 0,
    is_frozen BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY unique_account (citizenid, account_type)
);

Run migrations on resource start:

lua
-- server.lua
local function RunMigrations()
    local migrations = {
        "001_add_bank_accounts.sql",
        "002_add_transaction_log.sql",
    }

    for _, file in ipairs(migrations) do
        local version = file:match("^(%d+)")
        local result = MySQL.scalar.await("SELECT COUNT(*) FROM schema_migrations WHERE version = ?", { version })

        if result == 0 then
            local sql = LoadResourceFile(GetCurrentResourceName(), "migrations/" .. file)
            MySQL.query(sql)
            MySQL.insert("INSERT INTO schema_migrations (version, applied_at) VALUES (?, NOW())", { version })
            print("^2[Migration] Applied " .. file)
        end
    end
end

Monitoring Production

After deployment, monitor key metrics:

  • Server FPS — should not drop after your resource loads
  • Database query time — slow queries indicate missing indexes
  • Player spawn time — your resource should not delay spawn
  • Error logs — watch for unhandled events or nil values

Use FiveM's built-in performance metrics:

lua
Citizen.CreateThread(function()
    while true do
        Citizen.Wait(60000)
        local fps = GetServerFps()
        local players = GetNumPlayerIndices()
        print(("^3[Monitor] FPS: %d | Players: %d"):format(fps, players))

        if fps < 30 then
            print("^1[Monitor] WARNING: Server performance critical!")
        end
    end
end)

Summary Checklist

[ ] Git repository with main/develop branches [ ] Semantic versioning in fxmanifest.lua [ ] .gitignore configured for FiveM [ ] Luacheck passing with no errors [ ] qb-test or equivalent test suite [ ] CI pipeline (GitHub Actions) [ ] Deployment script with rsync [ ] Database migration system [ ] Production monitoring

Build your workflow once, and every resource you ship will be safer, faster, and more reliable.

ZiadLead Developer

Ziad is the lead developer of FiveForge. He builds AI systems that read codebases and get work done.

Stay updated

Get the latest AI development guides and FiveForge updates delivered to your inbox.

Build faster with FiveForge

An AI agent for complex work. Plan, build, debug, and automate projects — all from your desktop.