Skip to Content
ScriptingResource Lifecycle

Resource Lifecycle

Resources in FiveM follow a specific lifecycle managed by the server.

fxmanifest.lua

Every resource needs an fxmanifest.lua file:

fx_version 'cerulean' game 'gta5' author 'Your Name' description 'Resource Description' version '1.0.0' -- Server scripts server_scripts { 'server.lua' } -- Client scripts client_scripts { 'client.lua' } -- Shared scripts shared_scripts { 'config.lua' }

Resource States

Stopped

Resource is not loaded. Default state.

Starting

Resource is being loaded:

  • Files are read
  • Scripts are compiled
  • Dependencies checked

Started

Resource is running:

  • Scripts executing
  • Events registered
  • Exports available

Stopping

Resource is shutting down:

  • Cleanup code runs
  • Connections closed
  • State saved

Commands

ensure

Ensures resource is started:

ensure myresource

start

Start resource (if stopped):

start myresource

stop

Stop resource:

stop myresource

restart

Restart resource:

restart myresource

Lifecycle Events

Lifecycle events allow your resource to respond to start and stop events. Always check if the event is for your resource to avoid executing code for other resources.

Server-Side Events

onResourceStart

Fired when a resource starts (including your own). Use this for initialization logic:

-- server.lua -- Initialize database connection local dbReady = false AddEventHandler('onResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then print('^2[MyResource]^7 Starting initialization...') -- Load configuration Config = require 'config' -- Initialize database connection MySQL.ready(function() dbReady = true print('^2[MyResource]^7 Database connected') end) -- Load data from database MySQL.query('SELECT * FROM my_table', {}, function(result) if result then print('^2[MyResource]^7 Loaded ' .. #result .. ' records') end end) -- Register server exports exports('myFunction', function() return 'Hello from server' end) print('^2[MyResource]^7 Resource started successfully') end end)

Common initialization tasks:

  • Load configuration files
  • Connect to database
  • Load persistent data
  • Register server exports
  • Start background threads/timers
  • Initialize framework integrations

onResourceStop

Fired when a resource stops (including your own). Use this for cleanup:

-- server.lua -- Store active connections local activeConnections = {} AddEventHandler('onResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then -- Initialize connections activeConnections = {} end end) AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then print('^3[MyResource]^7 Stopping resource, cleaning up...') -- Save data to database for playerId, data in pairs(activeConnections) do MySQL.update('UPDATE players SET data = ? WHERE id = ?', { json.encode(data), playerId }) end -- Close database connections (if using connection pool) -- MySQL.close() -- Only if you manage connections manually -- Cancel all timers -- (FiveM automatically cleans up timers, but you can track them) -- Clear exports -- (FiveM automatically removes exports) print('^3[MyResource]^7 Cleanup complete') end end)

Common cleanup tasks:

  • Save player data to database
  • Close database connections
  • Cancel active timers
  • Clean up temporary files
  • Notify connected clients
  • Release resources

Client-Side Events

onClientResourceStart

Fired when a resource starts on the client. Use this for client initialization:

-- client.lua local isResourceReady = false AddEventHandler('onClientResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then print('^2[MyResource]^7 Client starting...') -- Initialize NUI SetNuiFocus(false, false) -- Load configuration Config = require 'config' -- Request server data TriggerServerEvent('myresource:requestData') -- Register key mappings RegisterKeyMapping('myresource:open', 'Open My Resource', 'keyboard', 'F5') -- Initialize blips or markers CreateThread(function() while true do Wait(0) -- Draw markers, blips, etc. end end) isResourceReady = true print('^2[MyResource]^7 Client ready') end end)

onClientResourceStop

Fired when a resource stops on the client. Use this for client cleanup:

-- client.lua AddEventHandler('onClientResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then print('^3[MyResource]^7 Client stopping...') -- Close NUI if open SetNuiFocus(false, false) SendNUIMessage({ type = 'close' }) -- Remove blips if DoesBlipExist(myBlip) then RemoveBlip(myBlip) end -- Clean up markers/threads -- (FiveM automatically stops threads, but clean up state) -- Save local state (if needed) -- TriggerServerEvent('myresource:saveState', localState) print('^3[MyResource]^7 Client cleanup complete') end end)

Complete Example

Here’s a complete example showing proper lifecycle management:

-- server.lua local resourceName = GetCurrentResourceName() local playerData = {} local cleanupTasks = {} -- Initialize on start AddEventHandler('onResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then print(string.format('^2[%s]^7 Initializing...', resourceName)) -- Load configuration Config = require 'config' -- Initialize database MySQL.ready(function() print(string.format('^2[%s]^7 Database ready', resourceName)) end) -- Start periodic task local taskId = CreateThread(function() while true do Wait(60000) -- Every minute -- Periodic task end end) table.insert(cleanupTasks, taskId) -- Register exports exports('getPlayerData', function(playerId) return playerData[playerId] end) print(string.format('^2[%s]^7 Started successfully', resourceName)) end end) -- Cleanup on stop AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then print(string.format('^3[%s]^7 Stopping...', resourceName)) -- Save all player data for playerId, data in pairs(playerData) do MySQL.update('UPDATE players SET data = ? WHERE id = ?', { json.encode(data), playerId }) end -- Cleanup tasks are handled automatically by FiveM -- But you can track and cancel them if needed print(string.format('^3[%s]^7 Stopped', resourceName)) end end)

Hot Reloading (restart)

When you use the restart command, FiveM reloads the resource without fully stopping the server. Understanding what persists and what resets is crucial.

What Persists During Restart

These remain unchanged when you restart a resource:

  • Database data - All database records persist
  • File system - Files on disk remain unchanged
  • Other resources - Other running resources continue normally
  • Player connections - Players remain connected to the server
  • Global server state - Server-wide variables in other resources

What Resets During Restart

These are reset when you restart a resource:

  • All variables - Local and global variables in the resource are reset
  • Event handlers - All registered event handlers are removed and re-registered
  • Timers/Threads - All CreateThread and Wait threads are stopped
  • Exports - All exports are removed and re-registered
  • NUI state - Client-side NUI state is reset
  • Client-side state - All client variables and state are reset

Best Practices for Hot Reloading

1. Always Load Persistent Data on Start

-- ❌ BAD: Data lost on restart local playerMoney = {} -- Lost on restart! -- ✅ GOOD: Load from database on start local playerMoney = {} AddEventHandler('onResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then -- Load from database MySQL.query('SELECT id, money FROM players', {}, function(result) for _, player in ipairs(result) do playerMoney[player.id] = player.money end end) end end)

2. Save Important State Before Restart

-- Save state function local function saveAllState() for playerId, data in pairs(playerData) do MySQL.update('UPDATE players SET data = ? WHERE id = ?', { json.encode(data), playerId }) end end -- Save on stop AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then saveAllState() end end) -- Also save periodically CreateThread(function() while true do Wait(300000) -- Every 5 minutes saveAllState() end end)

3. Handle Resource Restart Gracefully

-- Notify clients before restart (if possible) AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then -- Notify all players TriggerClientEvent('myresource:restarting', -1) -- Small delay to let clients process Wait(1000) -- Then do cleanup -- ... end end)

4. Use Framework Events When Available

-- If using QBCore/ESX, use framework events for player data -- These persist across resource restarts -- ❌ BAD: Storing player data in resource local playerData = {} -- Lost on restart -- ✅ GOOD: Use framework exports local QBCore = exports['qb-core']:GetCoreObject() -- Framework handles persistence

5. Test Restart Behavior

Always test that your resource handles restarts correctly:

-- Add a test command RegisterCommand('testrestart', function(source, args) if source == 0 then -- Console only print('Testing restart behavior...') -- Save current state -- Restart resource ExecuteCommand('restart ' .. GetCurrentResourceName()) end end, true) -- Restricted command

Common Pitfalls

  1. Assuming state persists:

    -- ❌ WRONG local counter = 0 -- Counter resets to 0 on every restart! -- ✅ CORRECT -- Store in database or use framework
  2. Not cleaning up on stop:

    -- ❌ WRONG: Data lost if server crashes -- (No cleanup handler) -- ✅ CORRECT: Save on stop AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then -- Save everything end end)
  3. Race conditions on start:

    -- ❌ WRONG: Using data before it's loaded AddEventHandler('onResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then MySQL.query('SELECT * FROM table', {}, function(result) -- This is async, might not be ready immediately end) -- Using result here will fail! end end) -- ✅ CORRECT: Wait for data to load AddEventHandler('onResourceStart', function(resourceName) if resourceName == GetCurrentResourceName() then MySQL.ready(function() -- Now database is ready MySQL.query('SELECT * FROM table', {}, function(result) -- Use result here end) end) end end)

Validation

After implementing lifecycle events:

  1. Test resource start:

    • Check server logs for initialization messages
    • Verify data loads correctly
    • Confirm exports are registered
  2. Test resource stop:

    • Use stop myresource command
    • Verify cleanup code executes
    • Check that data is saved
  3. Test restart:

    • Use restart myresource command
    • Verify state is preserved (if using database)
    • Confirm resource starts cleanly after restart
  4. Check for memory leaks:

    • Restart resource multiple times
    • Monitor server memory usage
    • Ensure old event handlers are cleaned up
Last updated on