A FiveM / Cfx.re Lua resource template used as a starting point for Scuffed Labs resources. It includes a startup validator, configuration loading, localization, shared logging, client/server utilities, and a modular bridge layer for third-party integrations.
This README is intentionally written for both human developers and LLMs. It documents the template structure, the expected loading order, the bridge contract, and the conventions that should be followed when generating or modifying code in this resource.
fxmanifest.luawith common FiveM metadata, dependencies, escrow ignore rules, and bridge module declarations.init.luastartup validation for dependency checks, file checks, resource metadata checks, and optional version checking.data/configuration.luafor runtime configuration.locales/*.jsonfor ox_lib localization.shared/main.luafor shared logger helpers.bridge/loader.luafor lazy-loading adapter modules.client/utils.luaandserver/utils.luafor reusable utility helpers.client/main.lua,server/main.lua, andshared/main.luaas the main entry points for resource logic._INSTALL/for installation snippets such as inventory items.
.
├── _INSTALL/ # Install snippets, SQL, item definitions, migration notes, etc.
├── bridge/ # Bridge loader, hooks, and third-party integration modules
│ ├── loader.lua # Bridge resolver and lazy module loader
│ ├── hooks/ # Optional integration extension points
│ └── modules/ # Adapter implementations grouped by module/adapter/context
├── client/ # Client entry points, modules, and utilities
├── data/ # Configuration and static data files
├── locales/ # Localization JSON files loaded by ox_lib
├── server/ # Server entry points, modules, and utilities
├── shared/ # Shared code loaded on both client and server
├── fxmanifest.lua # Resource manifest and dependency declarations
├── init.lua # Startup checks and global initialization
└── README.md # Developer and LLM documentationThe manifest currently loads shared scripts first:
shared_scripts {
"@ox_lib/init.lua",
"shared/logger.lua",
"init.lua",
"bridge/loader.lua",
}Expected order:
@ox_lib/init.luaprovideslib,cache,locale, and ox_lib helpers.shared/logger.luacreates the sharedLoggertable.init.luavalidates the resource, loadsConfig, runs version checks, and marks the resource as loaded.bridge/loader.luacreatesBridgeand loads required/optional bridge modules.- Client and server scripts then load their context-specific utilities and main files.
Each resource declares which bridge modules it needs directly in fxmanifest.lua.
required_modules {
"framework",
"notify"
}
optional_modules {
"inventory",
"target",
"fuel",
"dispatch"
}Use required_modules for integrations that must work for the resource to function correctly. If a required module cannot resolve or load, startup should fail loudly.
Use optional_modules for integrations that add compatibility or quality-of-life behavior but are not required for the resource to operate. Optional modules should soft-fail and return a noop object when unavailable.
Use FiveM metadata function syntax, not assignment syntax:
required_modules {
"framework",
"notify"
}Do not use:
required_modules = {
"framework",
"notify"
}The loader reads manifest metadata using GetNumResourceMetadata and GetResourceMetadata, which expects metadata entries created by manifest function calls.
The bridge allows resource code to call one consistent API while supporting multiple third-party resources behind the scenes.
Example usage:
Bridge.notify.send(source, {
title = locale("common.title"),
description = locale("common.success"),
type = "success"
})
Bridge.inventory.addItem(source, "water", 1)The bridge loader resolves three separate concerns:
- What the resource needs: declared in
fxmanifest.luathroughrequired_modulesandoptional_modules. - Which adapter should be used: selected by
Configor auto-detected from started resources. - Where the adapter file lives: built from
bridge/modules/<module>/<adapter>/<context>.lua.
moduleDefs should describe how a module is configured and loaded. It should not decide whether the current resource requires that module.
Good:
framework = {
configKey = "framework",
client = false,
fallback = "standalone"
}Avoid putting requirement state here:
framework = {
configKey = "framework",
required = true, -- avoid this
client = false,
fallback = "standalone"
}Requirement state belongs in fxmanifest.lua so every resource can declare only the integrations it actually uses.
Adapter selection is controlled by data/configuration.lua.
Example:
config.framework = "auto"
config.inventory = "auto"
config.interaction = "auto"
config.notification = "auto"
config.progressbar = "auto"
config.contextMenu = "auto"Common values:
"auto": detect the best available adapter from started resources."none": disable the integration."custom": load the custom adapter path.- adapter name such as
"ox","qbx","qb","esx","lation", etc.
When auto is selected, autoDetectMap checks known resource names with GetResourceState and maps them to adapter names.
Bridge modules should follow this path convention:
bridge/modules/<module>/<adapter>/<context>.luaExamples:
bridge/modules/framework/qbx/server.lua
bridge/modules/framework/qbx/client.lua
bridge/modules/inventory/ox/server.lua
bridge/modules/notify/ox/client.luaEach adapter should return a table of functions unless it is intentionally a single callable function.
Preferred:
local Notify = {}
function Notify.send(source, data)
-- implementation
end
return NotifyFor client-only integrations, set client = true in moduleDefs. The loader should skip those modules on the server context.
When an optional module is unavailable, the bridge returns a noop object. This lets optional integrations fail safely without forcing every call site to check for existence.
Example:
Bridge.dispatch.createAlert(data)If dispatch is optional and unavailable, the noop object prevents the call from crashing.
The noop should remain table/callable based instead of a plain function() end because many modules are used as method tables:
Bridge.dispatch.createAlert(...)
Bridge.target.addLocalEntity(...)A plain function would only support:
Bridge.dispatch(...)and would break method-style calls.
init.lua currently performs startup checks such as:
- ox_lib exists.
- ox_lib is at least the expected version.
Configloads successfully.- resource name and manifest metadata match expected values.
- required files exist.
- version check runs on the server when enabled.
This protects paid or distributed resources from partial installs, renamed folders, missing files, and outdated dependencies.
When creating a new resource from this template, update:
local resourceCfg = {
expectedName = "your_resource_name",
expectedAuthor = "Scuffed Labs",
expectedDescription = " by Scuffed Labs",
fileCheck = {
"locales/en.json",
"LICENSE.md",
"data/configuration.lua"
},
}Also update the manifest metadata:
name "your_resource_name"
description "Your resource description by Scuffed Labs"
version "v1.0.0"Use data/configuration.lua for user-editable settings. Keep it simple and stable.
Recommended sections:
config.debug = false
config.enableVersionCheck = true
config.framework = "auto"
config.inventory = "auto"
config.interaction = "auto"
config.notification = "auto"
config.progressbar = "auto"
config.contextMenu = "auto"
config.commands = {}
config.permissions = {}
config.locations = {}
config.rewards = {}Avoid putting complex runtime logic in configuration files. Configuration should describe behavior, not execute behavior.
Localization files live in locales/*.json and are loaded by ox_lib.
Example locales/en.json:
{
"common.title": "Scuffed Labs",
"common.success": "Success",
"common.error": "Something went wrong"
}Use locale keys instead of hardcoded strings in notifications, menus, context menus, and prompts:
locale("common.title")Recommended key naming:
common.*
error.*
success.*
menu.*
interaction.*
command.*Hooks should be used for integration-specific extension points that do not belong in core resource logic.
Example uses:
- Add custom dispatch behavior.
- Add framework-specific player metadata behavior.
- Add inventory-specific item metadata formatting.
- Allow server owners to customize reward logic without editing core files.
Suggested hook shape:
local Hooks = {}
function Hooks.onRewardGiven(source, reward)
return reward
end
function Hooks.canInteract(source, data)
return true
end
return HooksCore code can call hooks safely:
if Hooks.canInteract and not Hooks.canInteract(source, data) then
return
endUtilities should be reusable helpers that are not specific to a single feature.
Good utility examples:
Utils.debugPrintUtils.formatNumberUtils.trimStringUtils.notifySuccessUtils.spawnVehicleUtils.createBlip
Avoid placing one-off business logic in Utils. Feature-specific logic should live in client/modules/* or server/modules/*.
Load fewer files directly in fxmanifest.lua and let guarded boot files load internal modules with lib.load. This reduces repeated LoadedResource guards and makes startup order easier to reason about.
Add:
shared/const.luaUse it for immutable values such as event names, state bag keys, command names, default distances, and permission names.
Prevent event-name collisions with a small helper:
local resource = GetCurrentResourceName()
Events = {
server = function(name)
return ("%s:server:%s"):format(resource, name)
end,
client = function(name)
return ("%s:client:%s"):format(resource, name)
end
}Then use:
RegisterNetEvent(Events.client("sync"), function(data)
-- ...
end)Standardize ox_lib callbacks:
Callbacks = {}
function Callbacks.register(name, cb)
lib.callback.register(("%s:%s"):format(cache.resource, name), cb)
end
function Callbacks.await(name, source, ...)
return lib.callback.await(("%s:%s"):format(cache.resource, name), source, ...)
endAdd one place to check framework, ACE, and custom permissions:
function Utils.hasPermission(source, permission)
if IsPlayerAceAllowed(source, permission) then return true end
return Bridge.framework.hasPermission(source, permission)
endAdd server-side validation for common unsafe inputs:
- source exists
- player is near coordinates
- item name is valid
- amount is positive
- entity exists
- player has permission
- cooldown has not expired
This is especially useful for LLMs because it gives a standard pattern for safe event handling.
Add a generic cooldown utility for server events:
local cooldowns = {}
function Utils.checkCooldown(source, key, seconds)
local id = ("%s:%s"):format(source, key)
local now = os.time()
if cooldowns[id] and cooldowns[id] > now then
return false, cooldowns[id] - now
end
cooldowns[id] = now + seconds
return true, 0
endTrack entities, blips, zones, targets, and handlers so they can be removed on resource stop.
Cleanup = {
handlers = {}
}
function Cleanup.add(fn)
Cleanup.handlers[#Cleanup.handlers + 1] = fn
end
AddEventHandler("onResourceStop", function(resourceName)
if resourceName ~= cache.resource then return end
for i = #Cleanup.handlers, 1, -1 do
pcall(Cleanup.handlers[i])
end
end)When Config.debug is true, register useful commands such as:
- print current bridge adapters
- reload config where safe
- inspect player framework data
- test notifications
- test dispatch
- test inventory add/remove in development
Keep debug commands disabled in production.
Add a data/config_version.lua or Config.version value so support staff can tell whether a user has updated their config after a resource update.
config.version = 1When an LLM modifies this resource, it should follow these rules:
- Do not bypass
init.luavalidation. - Do not hardcode framework-specific logic outside the bridge unless unavoidable.
- Prefer
Bridge.*calls over direct third-party exports. - Keep server authority for money, items, rewards, and permissions.
- Validate all client-triggered server events.
- Use locale keys for user-facing text.
- Use
Config.debugfor diagnostic output. - Put feature code in
client/modulesorserver/modules, not in utility files. - Add new third-party compatibility through
bridge/modules, not by scattering checks throughout feature code. - Keep
moduleDefsas a loader registry only; usefxmanifest.luato declare required and optional modules.
- Add a module definition to
moduleDefsinbridge/loader.lua. - Add auto-detection entries to
autoDetectMapif applicable. - Add config keys to
data/configuration.lua. - Create adapter files under
bridge/modules/<module>/<adapter>/<context>.lua. - Declare the module in
required_modulesoroptional_modulesinfxmanifest.lua. - Use the module through
Bridge.<module>in resource code.
Example module definition:
banking = {
configKey = "banking",
client = false,
fallback = "none"
}Example config:
config.banking = "auto"Example manifest declaration:
optional_modules {
"banking"
}Wrong:
required_modules = {
"framework"
}Right:
required_modules {
"framework"
}Do not mark modules as required in moduleDefs. Requirement status should be per resource.
Avoid this:
exports.ox_inventory:AddItem(source, item, count)Prefer this:
Bridge.inventory.addItem(source, item, count)Never trust client-provided rewards, prices, coordinates, or permissions without server validation.
Utilities should be generic. Feature logic belongs in modules.
The template expects:
- FiveM artifact compatible with the manifest constraints.
- OneSync enabled.
ox_libinstalled and started before this resource.oxmysqlinstalled and started before this resource when server database access is needed.
Manifest dependencies currently include:
dependencies {
"/gameBuild:3095",
"/server:26389",
"/onesync",
"ox_lib",
"oxmysql"
}Remove oxmysql only if the resource has no database usage.
- Rename the resource folder and update
resourceCfg.expectedName. - Update manifest metadata.
- Configure
data/configuration.lua. - Declare bridge modules in
fxmanifest.lua. - Add adapter files for missing integrations.
- Keep new feature logic in
client/modulesandserver/modules. - Add localization keys before shipping.
- Test with
Config.debug = true. - Test startup failure cases by disabling dependencies.
- Set
Config.debug = falsebefore production release.
See LICENSE.md.