Standalone

Agency-Vehicleshop

A premium vehicle dealership for FiveM. Browse a fully animated showroom, take test drives and buy cars through a sleek glassmorphism NUI. Salesperson job with commissions, category & price filters, live in-world preview, test-drive timer, financing, dealer management dashboard and Discord webhook logging. 13 languages, MySQL persistence & ACE permissions. Works with QBCore, ESX & Standalone.

v1.0.0Paid3 Pages
Agency-Vehicleshop

01 Configuration

Complete config.lua for Agency-Vehicleshop. All options documented via inline comments.

Download config.lua
--[[
    ╔══════════════════════════════════════════════════════════════════╗
    ║                      AGENCY  VEHICLE SHOP                          ║
    ║                  Premium Buy & Sell Car Dealership                 ║
    ║                        (c) Agency Scripts                          ║
    ╠══════════════════════════════════════════════════════════════════╣
    ║  Plug & Play  ·  QBCore / ESX / Standalone  ·  Multi-Phone ready   ║
    ╚══════════════════════════════════════════════════════════════════╝

    This file is the ONLY thing you need to touch as a server owner.
    Everything is commented. No coding knowledge required.

    Framework options (Config.Framework):
      'auto'        -> Auto-detect QBCore or ESX (recommended)
      'qb'          -> Force QBCore
      'esx'         -> Force ESX
      'standalone'  -> No framework. Vehicles are managed by this script.
]]

Config = {}

-- =====================================================================
-- AGENCY SCRIPTS INTEGRATION  ⭐
-- =====================================================================
-- Official integration with other Agency Scripts.
-- true   = Always use (throws error if script is missing)
-- false  = Never use (even if installed)
-- 'auto' = Auto-detect if the script is running on the server (recommended)
--
Config.AgencyNotify      = 'auto'          -- Agency-Notify   → Premium notifications
Config.AgencyGarage      = 'auto'          -- Agency-Garage   → Bought vehicles appear directly in the garage
Config.AgencyVehiclekeys = 'auto'          -- Agency-Vehiclekeys → Receive keys automatically after purchase

-- =====================================================================
-- FRAMEWORK & AUTO-DETECTION
-- =====================================================================
Config.Framework = 'auto'                  -- 'auto' | 'qb' | 'esx' | 'standalone'
Config.QBCoreResource = 'qb-core'
Config.ESXResource    = 'es_extended'
Config.Debug = true                        -- TEMP: set to false after debugging
Config.AutoSetupDatabase = true            -- creates standalone table / ESX columns on start

-- =====================================================================
-- LANGUAGE
-- =====================================================================
Config.Locale = 'en'                       -- 'en','de','fr','es','pt','it','nl','da','sv','no','fi','pl','cs','sk','hu','ro','bg','tr','ru','el','ar','zh','ja','ko'  (texts live in locales.lua)

-- =====================================================================
-- BRIDGES (auto-detected — only force if needed)
-- =====================================================================
-- These settings are ONLY used if the Agency integrations above
-- are NOT active. With 'auto', Agency is checked first, then these.
Config.Notify = 'auto'                     -- 'auto' | 'ox' | 'qb' | 'esx' | 'native'
Config.TextUI = 'auto'                     -- 'auto' | 'ox' | 'jg' | 'qb' | 'native'
Config.VehicleKeys = 'auto'                -- 'auto' | 'qb' | 'qs' | 'wasabi' | 'mk' | 'none'

-- =====================================================================
-- MULTI-PHONE INTEGRATION  ⭐
-- =====================================================================
-- A bought vehicle is inserted into the standard table (player_vehicles for
-- QB, owned_vehicles for ESX) with state = 1 (stored). Every phone garage app
-- and any garage script then sees the new car automatically.
Config.PhoneSync = true
Config.PhoneSyncEvent = 'vehicleshop:vehiclePurchased'

-- =====================================================================
-- VEHICLE CATALOG  ⭐ (where the buyable cars come from)
-- =====================================================================
-- 'auto'   -> use qb-core/shared/vehicles.lua if it exists, else the Config.Vehicles list
-- 'qbcore' -> always read qb-core/shared/vehicles.lua (model/name/brand/price/category/shop)
-- 'config' -> always use the Config.Vehicles list below (best for ESX / standalone)
-- On QBCore you do NOT have to list any vehicles — they are all loaded for you.
Config.VehicleSource = 'auto'

-- Manual catalog (used when source = 'config' or as a fallback). Add your own here.
-- shop = which shop it shows up in (matches a Config.Shops 'type')
Config.Vehicles = {
    { model = 'sultan',   name = 'Sultan',    brand = 'Karin',     price = 12000,   category = 'sports',      shop = 'pdm' },
    { model = 'kuruma',   name = 'Kuruma',    brand = 'Karin',     price = 95000,   category = 'sports',      shop = 'pdm' },
    { model = 'futo',     name = 'Futo',      brand = 'Karin',     price = 9000,    category = 'sports',      shop = 'pdm' },
    { model = 'adder',    name = 'Adder',     brand = 'Truffade',  price = 1000000, category = 'super',       shop = 'luxury' },
    { model = 'zentorno', name = 'Zentorno',  brand = 'Pegassi',   price = 725000,  category = 'super',       shop = 'luxury' },
    { model = 'bati',     name = 'Bati 801',  brand = 'Pegassi',   price = 15000,   category = 'motorcycles', shop = 'moto' },
}

-- =====================================================================
-- BUYING & SELLING
-- =====================================================================
-- Payment methods the player can choose from IN the shop menu.
-- 'bank' = card, 'cash' = cash. Both enabled = player picks in the UI.
Config.PaymentMethods = { 'bank', 'cash' }  -- available: 'bank', 'cash'
Config.DefaultPayment = 'bank'              -- pre-selected method when the shop opens

-- AgencyPay (phone wallet) checkout. The purchase is verified SERVER-SIDE via
-- agency-phone's VerifyAgencyPayCharge export, so a forged result cannot grant a
-- free vehicle. Requires agency-phone to be running.
Config.EnableAgencyPay = true

Config.Sell = {
    enabled       = true,
    account       = 'bank',                -- where the money goes when selling
    returnPercent = 50,                    -- % of the catalog price you get back when selling
}

-- =====================================================================
-- DEALERSHIP OWNERSHIP  ⭐ (players can buy & own shops as a business)
-- =====================================================================
Config.Ownership = {
    enabled           = true,               -- master switch for the ownership system
    maxPerPlayer      = 2,                  -- max dealerships one player can own
    sellBackPercent   = 50,                 -- % of purchasePrice you get back when selling the business
    defaultCommission = 0.10,               -- 10% of each vehicle sale goes into the business account
    maxEmployees      = 10,                 -- max employees per dealership
    ranks = {
        { name = 'owner',       label = 'Owner',       canHire = true, canFire = true, canWithdraw = true },
        { name = 'manager',     label = 'Manager',     canHire = true, canFire = true, canWithdraw = false },
        { name = 'employee',    label = 'Employee',    canHire = false, canFire = false, canWithdraw = false },
    },

    -- ===== STOCK & SUPPLY (only applies to OWNED dealerships) =====
    -- An UNOWNED dealership sells everything at the normal price with infinite
    -- stock. The moment a player buys it the showroom starts EMPTY — the owner
    -- has to order vehicles wholesale (an up-front investment), drive a delivery
    -- run for each one, and only then can sell them to customers at the normal
    -- (higher) price. The margin lands in the business account.
    stock = {
        enabled         = true,
        maxOrderAtOnce  = 5,        -- max units that can be ordered in one order (your "max 5")
        wholesaleFactor = 0.55,     -- order price = 55% of catalog price → ~45% margin on resale
        maxPerModel     = 10,       -- max delivered units a dealership can hold per model
        requireDelivery = true,     -- ordered units must be delivered before they can be sold
    },
}

-- =====================================================================
-- DELIVERY MISSIONS  ⭐  (owners drive ordered vehicles to the dealership)
-- =====================================================================
-- After ordering stock, ONE delivery run is required per ordered unit. The
-- driver picks the vehicle up at one of the warehouse points below and drives
-- it to the dealership's spawn point. The game alternates pickup points so the
-- route differs each time. Fully configurable — add/remove points freely.
Config.Delivery = {
    payOnComplete = 0,             -- optional extra cash per delivery (profit normally comes from sales)
    pickupMarker  = { type = 1, size = 3.0, color = { 0, 212, 255, 140 } },
    dropMarker    = { type = 1, size = 3.0, color = { 0, 255, 136, 140 } },
    
    -- Transporter vehicles used when ordering multiple vehicles at once
    truckSmall    = 'speedo',  -- Changed from 'mule' because mule is often blacklisted
    truckLarge    = 'benson',  -- Changed from 'pounder' because pounder is often blacklisted

    pickups = {
        vector4(1730.03, 3307.72, 41.22, 195.0),   -- Sandy Shores Airfield (Runway)
        vector4(-192.51, 6224.71, 31.49, 134.42),  -- Paleto Bay (Open rest stop parking)
        vector4(-2184.28, 4272.78, 49.03, 130.0),  -- Highway 1 parking (near Zancudo)
        vector4(-341.36, -2600.32, 6.0, 275.0),    -- Elysian Island (Open container lot)
        vector4(-1058.46, -2824.93, 27.7, 325.0),  -- LSIA (Outer gate open parking)
        vector4(2112.35, 1925.15, 78.4, 130.0),    -- Wind Farm (Open dirt track)
        vector4(-3173.34, 1079.22, 20.83, 240.0),  -- Chumash (Open dirt lot next to highway)
        vector4(-392.21, 1177.3, 325.64, 340.0),   -- Galileo Observatory (Empty parking lot)
        vector4(695.53, 580.4, 130.4, 270.0),      -- Vinewood Bowl (Large parking area)
        vector4(-1203.25, -1355.22, 4.3, 120.0),   -- Vespucci Beach (Giant open parking lot)
        vector4(-1678.5, -960.5, 7.6, 135.0),      -- Del Perro Pier (Parking lot)
        vector4(890.3, -2200.5, 30.5, 180.0),      -- Cypress Flats (Industrial open road)
        vector4(1420.4, -1890.6, 73.0, 290.0),     -- El Burro Heights (Oil fields dirt track)
        vector4(1950.4, 3770.6, 32.2, 210.0),      -- Harmony / Route 68 (Dirt parking)
        vector4(-735.6, 5835.4, 17.3, 225.0),      -- Mount Chiliad (Cable car parking at bottom)
        vector4(2450.4, 4950.6, 41.5, 45.0),       -- Grapeseed (Farms dirt road)
        vector4(-545.5, 5320.5, 74.5, 340.0),      -- Paleto Forest (Lumber yard open space)
    },
}

-- =====================================================================
-- SHOWCARS  ⭐  (/avehicle — staff place display vehicles on fixed spots)
-- =====================================================================
-- Anyone with the internal job at a dealership can drive a car onto one of its
-- showcar spots and run /avehicle to display it (frozen showroom piece). Run
-- /avehicle clear on a spot to empty it. Customers just look — no buy here.
Config.Showcars = {
    enabled = true,
    command = 'avehicle',          -- chat command for staff
    freeze  = true,                -- display vehicles are frozen & invincible
    setRange = 4.0,                -- how close a spot must be to set/clear it
    -- Display spots per shop id. Add as many vector4(x,y,z,heading) as you like.
    spots = {
        pdm = {
            vector4(-43.92, -1097.62, 26.42, 25.0),
            vector4(-47.83, -1100.30, 26.42, 25.0),
            vector4(-51.74, -1102.98, 26.42, 25.0),
        },
        luxury = {
            vector4(133.2, -138.5, 54.85, 250.0),
            vector4(129.6, -141.0, 54.85, 250.0),
        },
        moto = {
            vector4(-1250.6, -351.2, 36.91, 120.0),
        },
        planes = {},
        boats  = {},
    },
}

Config.TestDrive = {
    enabled       = true,
    duration      = 45,                         -- seconds, then the test car despawns and you return
    stopKey       = 73,                         -- [X] key to end the test drive early
    routingBucket = true,                       -- true = puts player in a private world during test drive
    hudPosition   = 'bottom-center',            -- 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'
    spawn         = vector4(-1732.17, -2900.2, 13.94, 330.0), -- Default okok Test Track (Airport)
}

-- =====================================================================
-- GENERAL BEHAVIOUR
-- =====================================================================
Config.InteractKey       = 38              -- [E]
Config.UseTarget         = false           -- true = ox_target / qb-target instead of marker+key
Config.DrawDistance      = 25.0
Config.InteractDistance  = 2.5
Config.SpawnInVehicle    = true            -- sit in the car after buying
Config.WarpDelay         = 250

-- MARKER (animated icon at the shop)
Config.Marker = {
    type   = 36,
    size   = 0.55,
    color  = { 255, 215, 0, 200 },         -- yellow
    bob    = false, rotate = true, pulse = false, zOffset = 0.0,
}
-- SELL MARKER (where you sell your car)
Config.SellMarker = {
    type   = 36,                           -- 36 = Car icon marker
    size   = 0.6,
    color  = { 255, 50, 50, 200 },         -- vibrant red
    bob    = false, rotate = true, pulse = false, zOffset = 0.0,
}
-- BOSS MARKER (management menu access)
Config.BossMarker = {
    type   = 36,                           -- 36 = car icon on the ground (like the shop marker)
    size   = 0.55,
    color  = { 0, 212, 255, 200 },         -- cyan = management (only staff see it)
    bob    = false, rotate = true, pulse = true, zOffset = 0.0,
}

-- OPEN HINT
Config.OpenHint = { mode = 'notify' }      -- 'notify' | 'textui' | 'both'

-- =====================================================================
-- VEHICLE PREVIEW (3D showroom camera, mouse-drag to rotate)
-- =====================================================================
Config.Preview = {
    enabled          = true,
    autoRotate       = false,               -- false = drag with the mouse · true = spins by itself
    mouseSensitivity = 0.35,
    rotateSpeed      = 18.0,
    heightOffset     = 25.0,                -- fallback only
    hotspots         = true,                -- show interactive dots (doors, hood, trunk) on the preview car
}

-- =====================================================================
-- VEHICLE IMAGES FOR THE UI
-- =====================================================================
-- 'spawn'  -> html/img/vehicles/<model>.png (e.g. adder.png)
-- 'class'  -> one image per category
-- 'single' -> always default.png
-- Missing image -> clean placeholder icon (never a broken image).
Config.VehicleImages = 'spawn'

-- =====================================================================
-- BLIPS
-- =====================================================================
Config.Blips = { enabled = true }

-- =====================================================================
-- CATEGORY DISPLAY NAMES (GTA vehicle classes)
-- =====================================================================
Config.CategoryLabels = {
    compacts = 'Compacts', sedans = 'Sedans', suvs = 'SUVs', coupes = 'Coupes',
    muscle = 'Muscle', sportsclassics = 'Classics', sports = 'Sports', super = 'Super',
    motorcycles = 'Motorcycles', offroad = 'Off-Road', industrial = 'Industrial',
    utility = 'Utility', vans = 'Vans', cycles = 'Cycles', boats = 'Boats',
    helicopters = 'Helicopters', planes = 'Planes', service = 'Service',
    emergency = 'Emergency', military = 'Military', commercial = 'Commercial',
}

-- =====================================================================
-- SHOPS  (positions imported from the old shop)
-- =====================================================================
-- type:   matches the 'shop' field of the vehicles (which cars are shown)
-- access: vector3 marker to open the shop
-- spawn:  vector4 where the preview / bought car appears
-- sell:   vector3 sell point (drive your car here, press [E] to sell)
Config.Shops = {
    ['pdm'] = {
        label  = 'Premium Deluxe Motorsport',
        type   = 'pdm',
        access = vector3(-57.5, -1096.73, 26.42),
        spawn  = vector4(-47.4, -1093.4, 26.42, 150.0),
        spawns = {
            vector4(-47.4, -1093.4, 26.42, 150.0),
            vector4(-50.8, -1089.2, 26.42, 150.0),
    },
        sell      = vector3(-45.3, -1082.91, 26.27),
        blip     = { sprite = 225, color = 3, scale = 0.9 },
        -- Ownership
        ownable       = true,
        purchasePrice = 1000000,
        bossMenu      = vector3(-55.0, -1098.0, 26.42),
        deliveryDrop  = vector4(-31.84, -1090.72, 26.42, 340.0), -- Default drop for PDM
    },

    ['moto'] = {
        label  = 'Motorcycle Shop',
        type   = 'moto',
        access = vector3(-1253.95, -349.37, 36.91),
        spawn  = vector4(-1245.0, -353.0, 36.9, 30.0),
        spawns = {
            vector4(-1245.0, -353.0, 36.9, 30.0),
            vector4(-1242.0, -351.0, 36.9, 30.0),
            vector4(-1238.0, -348.0, 36.9, 30.0),
        },
        sell      = vector3(-1242.49, -345.42, 37.33),
        blip     = { sprite = 226, color = 3, scale = 0.8 },
        -- Ownership
        ownable       = true,
        purchasePrice = 500000,
        bossMenu      = vector3(-1250.0, -349.0, 36.91),
        deliveryDrop  = vector4(-1256.6403, -335.6497, 36.9134, 299.5139),
    },
    ['planes'] = {
        label  = 'Airplane Shop',
        type   = 'planes',
        access = vector3(-949.5, -2946.55, 13.95),
        spawn  = vector4(-975.0, -2965.0, 13.95, 60.0),
        spawns = {
            vector4(-975.0, -2965.0, 13.95, 60.0),
    },
        sell   = vector3(-959.5, -2946.55, 12.76),
        blip   = { sprite = 359, color = 3, scale = 0.8 },
        -- Ownership
        ownable       = true,
        purchasePrice = 3000000,
        bossMenu      = vector3(-955.0, -2943.0, 13.95),
        deliveryDrop  = vector4(-975.0, -2965.0, 13.95, 60.0),
    },
    ['boats'] = {
        label  = 'Boat Shop',
        type   = 'boats',
        access = vector3(-720.77, -1324.92, 1.6),
        spawn  = vector4(-735.0, -1338.0, 0.2, 140.0),
        spawns = {
            vector4(-735.0, -1338.0, 0.2, 140.0),
    },
        sell   = vector3(-721.56, -1306.7, 3.82),
        blip   = { sprite = 427, color = 3, scale = 0.8 },
        -- Ownership
        ownable       = true,
        purchasePrice = 1500000,
        bossMenu      = vector3(-724.0, -1322.0, 1.6),
        deliveryDrop  = vector4(-735.0, -1338.0, 0.2, 140.0),
    },
}

-- =====================================================================
-- BRANDING (subtle — paid script)
-- =====================================================================
Config.Branding = { showFooter = true, label = 'Agency Vehicle Shop' }

-- =====================================================================
-- TEXT / TRANSLATIONS
-- =====================================================================

-- =====================================================================
-- TEXT / TRANSLATIONS
-- =====================================================================
-- All translatable strings live in locales.lua (24 languages) and are
-- loaded right after this file (see fxmanifest.lua). Edit texts there.

Need help? Join our Discord community for support!

Join Discord