FiveM & QBCore · Practical guide

How to Add a Car Dealership to Your QBCore FiveM Server

A car dealership lets players buy and own vehicles on your FiveM server. This guide covers setting up a QBCore vehicle shop with test drives, financing and ownership.

Stellar AI · Updated 8 September 2026 · 6 min read

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

Overview

Adding a car dealership to a QBCore FiveM server is a multi-layer task: it touches client UI, server-side validation, persistent storage, and anticheat/logging. This guide focuses on a secure, maintainable architecture that never trusts client input and uses QBCore patterns (and optional ox_lib/ox_target integrations) to create a robust purchase-and-ownership flow. For deployment and analytics integration, consider tools such as the Stellar AI app for team collaboration and monitoring — see the Stellar AI app and projects list for quick setup: Stellar AI app.

Architecture and Components

Core components

  • Client UI: dealership menus, preview camera, purchase confirmation (purely visual; no trust)
  • Server controller: request handling, validation, funds management, vehicle creation record
  • Database: persistent vehicle ownership table and transaction log
  • Anti-cheat and audit: rate limiting, anomaly detection, logs

Typical data flow

  1. Client opens dealership UI and selects a model (client-side only).
  2. Client requests purchase by calling a server RPC/event with chosen model id.
  3. Server validates model against a maintained whitelist, checks price, and verifies funds.
  4. Server applies transaction (atomic where possible), inserts ownership record, and responds with success/failure.
  5. Client receives confirmation and spawns owned vehicle only after server approves and returns spawn token/plate.

Implementation Choices

QBCore integration

Use the QBCore core object on the server: local QBCore = exports['qb-core']:GetCoreObject(). Implement server callbacks or server events that perform all checks. Avoid placing pricing or model whitelist in client files; client may cache read-only copies for UI, but server must hold authoritative config.

Optional libraries

  • ox_lib for menus and notifications (client-side UX).
  • ox_target for interaction points (approach dealership sign to open catalog).
  • oxmysql or MySQL Async for robust DB operations and transaction management.

Server-Side Validation and Security

Never trust the client. All financial and ownership decisions must be done on the server. Do not accept client-supplied price, plate, or ownership flags. Maintain a server-side vehicle catalog with model, display name, and price and validate purchases against that catalog.

Key validation steps

  • Confirm model exists in server whitelist and retrieve authoritative price.
  • Check that the requesting player exists (QBCore.Functions.GetPlayer(source)) and that they have sufficient funds in the correct wallet type.
  • Use database transactions where available; if insertion fails, refund immediately and log the incident.
  • Rate-limit purchases per player to prevent automated abuse.
  • Log purchase attempts including mismatched price, invalid model, or DB errors for later review.

Example server flow (Lua, QBCore)


local QBCore = exports['qb-core']:GetCoreObject()
local vehicleCatalog = {
  ['t20'] = { price = 1_500_000, name = 'T20' },
  ['zentorno'] = { price = 2_000_000, name = 'Zentorno' },
  -- server-authoritative list
}

QBCore.Functions.CreateCallback('qb-dealer:server:purchase', function(source, cb, model)
  local src = source
  local Player = QBCore.Functions.GetPlayer(src)
  if not Player then return cb(false, 'no_player') end

  local catalogItem = vehicleCatalog[model]
  if not catalogItem then
    -- invalid model attempted
    return cb(false, 'invalid_model')
  end

  local price = catalogItem.price
  if not Player.Functions.RemoveMoney('bank', price) then
    return cb(false, 'insufficient_funds')
  end

  -- generate plate and ownership metadata server-side
  local plate = 'DLR' .. tostring(math.random(1000,9999))

  -- insert into owned_vehicles with a safe DB call
  exports.oxmysql:insert('INSERT INTO owned_vehicles (owner, plate, model, metadata) VALUES (?, ?, ?, ?)', {
    Player.PlayerData.citizenid,
    plate,
    model,
    json.encode({ state = 'stored', purchasePrice = price })
  }, function(insertId)
    if not insertId then
      -- DB error: refund and log
      Player.Functions.AddMoney('bank', price)
      -- log warning to server logs
      return cb(false, 'db_error')
    end
    -- success: return plate and model, server-authoritative
    return cb(true, { plate = plate, model = model })
  end)
end)
  

Database Schema and Transactions

Keep a minimal and indexed ownership table. Use transactions when possible to ensure atomicity between money removal and ownership creation.

TableKey ColumnsNotes
owned_vehicles id (pk), owner (citizenid), plate, model, metadata (json), created_at index on owner and plate for fast lookups
dealer_transactions id, owner, model, price, status, error_code, created_at transaction history for audits and refunds

Best practices

  • Wrap multi-step DB modifications (debit + insert) in transactions if your driver supports them.
  • Retain a transaction log with error_code to enable automatic reconciliation scripts.
  • Sanitize data and use prepared statements or parameterized queries (oxmysql handles parameters).

UI & Interaction Patterns

The client UI should provide previews and selection UX only. Use server callbacks to confirm availability and final price before performing a purchase.

Approach patterns

  • Use ox_target to trigger a "Open Catalog" client interaction when the player approaches a dealer NPC or showroom.
  • On selection, call a server callback to get the latest price and availability; show a final confirmation dialog client-side.
  • On final confirm, call the purchase server callback — do not pass sensitive values like price or plate from client side.

Testing and QA

The testing phase should include unit, integration, and abuse testing. Test both normal and failure paths: DB downtime, insufficient funds, and malformed client messages. Use the following checklist to validate behavior.

TestPurposeExpected Result
Purchase success Standard buy flow Funds removed, owned_vehicles entry created, client receives plate
Insufficient funds Ensure server denies and no DB insert No DB entry, no funds removed
Invalid model Client submits model not in server list Purchase rejected, logged
DB failure during insert Simulate DB timeout/error Funds refunded, alert logged
Rate limit abuse Repeated rapid requests Requests throttled with warning and logs

Deployment and Maintenance

Deploy the dealership as a versioned resource with migrations for DB schema changes. Keep config in a separate server-side file and support remote-reload of catalogs if your workflow requires live catalog updates.

Operational tips

  • Run weekly automated backups of DB and validate restores.
  • Monitor transaction failure rates and set alerts for consecutive DB errors.
  • Maintain an audit trail (dealer_transactions) that includes error_code and stack traces when available.
  • Use CI to lint Lua, run unit tests, and verify that server-side callbacks cover all edge cases.
  • For collaboration and issue tracking, integrating a project workspace such as the Stellar AI app can centralize logs and tasks: Stellar AI app. For editorial and tutorial updates, review the team blog page: Stellar AI blog.

Roblox (Luau) Considerations — Brief Notes

If you implement a similar dealership pattern on Roblox, the same server-authority principle applies. Use RemoteEvents/RemoteFunctions carefully: all purchase validation must occur in a server script that checks player currency in a server-side datastore. Do not trust client-sent prices or item IDs.

Roblox best practices

  • Perform DataStore saves with retry/backoff and handle failure by queueing reconciliation or notifying the player.
  • Use server-only modules for catalog config; clients only request previews. Provide limited time tokens for spawns only after server confirms purchase.
  • Gracefully handle DataStore throttling/failure: inform the player, and implement a retry or manual support flow.

Monitoring, Logging, and Anti-Cheat

Logging is essential. Track suspicious purchase attempts, mismatched prices, and repeated failures. Integrate server logs with your monitoring platform or use a centralized workspace to triage issues. Consider:

  • Request rate and pattern analysis per player
  • Automated alerts for DB insert failures above a threshold
  • Manual review workflows for refunds or disputes

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 →