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 myresourcestart
Start resource (if stopped):
start myresourcestop
Stop resource:
stop myresourcerestart
Restart resource:
restart myresourceLifecycle 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
CreateThreadandWaitthreads 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 persistence5. 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 commandCommon Pitfalls
-
Assuming state persists:
-- ❌ WRONG local counter = 0 -- Counter resets to 0 on every restart! -- ✅ CORRECT -- Store in database or use framework -
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) -
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:
-
Test resource start:
- Check server logs for initialization messages
- Verify data loads correctly
- Confirm exports are registered
-
Test resource stop:
- Use
stop myresourcecommand - Verify cleanup code executes
- Check that data is saved
- Use
-
Test restart:
- Use
restart myresourcecommand - Verify state is preserved (if using database)
- Confirm resource starts cleanly after restart
- Use
-
Check for memory leaks:
- Restart resource multiple times
- Monitor server memory usage
- Ensure old event handlers are cleaned up