Skip to Content

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:

Started

Resource is running:

Stopping

Resource is shutting down:

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:

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:

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:

What Resets During Restart

These are reset when you restart a resource:

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:

  2. Test resource stop:

  3. Test restart:

  4. Check for memory leaks: