ESX is nil: fixing esx:getSharedObject in ESX Legacy

attempt to index a nil value (global 'ESX')? The old esx:getSharedObject event is gone from ESX Legacy. Here is the one-line fix, and what to do with scripts you cannot edit.

You start your server, open the F8 console and there it is:

text
SCRIPT ERROR: @my_script/client/main.lua:12: attempt to index a nil value (global 'ESX')

Or the server-side twin, attempt to index a nil value (upvalue 'ESX'). The script is fine, ESX is running, and still ESX is nil. This is the most common ESX error there is, and it almost always has the same cause.

Why ESX is nil

Older ESX scripts get the ESX object like this:

lua
ESX = nil

TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)

That event was how ESX 1.1 and 1.2 handed out its shared object. ESX Legacy deprecated it, and current releases no longer answer it. The script triggers the event, nobody replies, and ESX stays nil until the first line that uses it crashes.

You will see this mostly with scripts written years ago, or copied from old tutorials.

The fix: ask es_extended directly

Replace those lines with the export ESX Legacy provides:

lua
ESX = exports['es_extended']:getSharedObject()

It works the same on the client and on the server. Do it in every file that uses ESX. If the script also had a waiting loop like this one, delete it; the export answers straight away:

lua
-- old code: remove it
Citizen.CreateThread(function()
    while ESX == nil do
        TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
        Citizen.Wait(0)
    end
end)

Even simpler: imports.lua

es_extended ships a small file that sets ESX up for you. Add it to the script's fxmanifest.lua:

lua
fx_version 'cerulean'
game 'gta5'
lua54 'yes'

shared_script '@es_extended/imports.lua'

client_scripts { 'client/*.lua' }
server_scripts { 'server/*.lua' }

Now every client and server file has a global ESX, and on the client ESX.PlayerData stays up to date as the player's job and money change. You can delete the getSharedObject lines entirely.

Tip: use one approach per script. Either the export at the top of each file, or imports.lua in the manifest; mixing both works, but it is noise.

Still nil? Check the start order

If you already use the export and ESX is still nil, or you get No such export getSharedObject in resource es_extended, the script is starting before es_extended. Your server.cfg decides the order:

cfg
ensure oxmysql
ensure ox_lib
ensure es_extended
ensure [esx]

ensure my_script

Two more things to check in the server console, from the very top:

  • es_extended started without errors. If it fails (a bad mysql_connection_string, a missing oxmysql), every script that depends on it fails too.
  • The folder is really called es_extended. A renamed or duplicated copy breaks the export name, and on Linux the name is case-sensitive.

We go through that error in detail in No such export getSharedObject in resource es_extended.

Player data on the client

Old scripts also read the player like this:

lua
-- old
ESX.GetPlayerData()
RegisterNetEvent('esx:playerLoaded')
AddEventHandler('esx:playerLoaded', function(xPlayer) PlayerData = xPlayer end)

That still works in ESX Legacy, with one detail: wait until the player is loaded before you read job or money at startup.

lua
CreateThread(function()
    while not ESX.IsPlayerLoaded() do Wait(250) end
    local job = ESX.GetPlayerData().job
    print(('job: %s (%s)'):format(job.name, job.grade))
end)

RegisterNetEvent('esx:setJob', function(job)
    -- the job changed: refresh whatever shows it
end)

With imports.lua, ESX.PlayerData.job is kept current for you.

Scripts you cannot edit

If the script is escrowed (encrypted) and still uses the old event, you cannot change its code. In order of preference:

  1. Ask its author for an update. Any script still sold for ESX should use the export.
  2. Answer the old event from ESX yourself. In es_extended, add this to a server file and to a client file:
lua
AddEventHandler('esx:getSharedObject', function(cb)
    cb(ESX)
end)

Warning: this is a patch to es_extended itself. Updating ESX overwrites it, so you have to add it again after every update. Keep it as a stopgap, not as the fix.

Quick checklist

Symptom Fix
attempt to index a nil value (global 'ESX') Replace TriggerEvent('esx:getSharedObject'…) with exports['es_extended']:getSharedObject()
Still nil after the change Start es_extended before the script in server.cfg
No such export getSharedObject es_extended is not started, failed, or is named differently
ESX.PlayerData.job is nil at startup Wait for ESX.IsPlayerLoaded() first
Escrowed script with the old event Ask the author; meanwhile answer the event from es_extended

Quick answers

Why is ESX nil in my script?

The script asks for ESX with the old TriggerEvent('esx:getSharedObject') call, which current ESX Legacy no longer answers. Replace it with ESX = exports['es_extended']:getSharedObject(), or add shared_script '@es_extended/imports.lua' to the fxmanifest.

Do I need to change both client and server files?

Yes. Every file that uses ESX needs the object, on the client and on the server. The imports.lua line in the fxmanifest covers all of them at once.

What if the script is escrowed and I cannot edit it?

Ask its author for an update first. As a last resort you can answer the old event from es_extended, but you have to add that back every time you update ESX.

Scripts that skip this problem

Advanced BoostingTablet-driven vehicle boosting: contracts from class D to S+, crews and a live queue.View script →Item Creator V2Create usable items with animations, props, effects and more — without writing code.View script →Shop CreatorBuild a shop in under a minute — owners, employees, vaults and robberies included.View script →

Keep reading