The EndCore library is the toolkit that ships inside en-core. Every en-* resource is built on it, and your own resources should be too. It gives you server and client callbacks, commands, rebindable keybinds, notifications and progress bars, asset loading, world helpers, and one interface each for inventory, targeting, minigames, the radial menu, skills and parties.
Because resources talk to those systems through the library instead of calling each other directly, you can run a server without one of them, start resources in any order, or swap in a replacement. When a backing resource is missing, the call returns a safe "nothing happened" value instead of throwing.
Importing the library
Add @en-core/lib/init.lua as the first shared script in your fxmanifest.lua:
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
shared_scripts {
'@en-core/lib/init.lua', -- must come before any script that uses `encore`
'config/shared.lua',
}
client_scripts { 'client/main.lua' }
server_scripts { 'server/main.lua' }
dependencies {
'en-core',
'en-ui', -- needed if you call encore.notify / progress / inputDialog / prompts
}This is the same pattern every shipped en-* resource uses. en-core serves the library files to clients itself, so you don't list them in your own files {}.
What you get
Loading init.lua defines one global table, encore:
| Field | Value |
|---|---|
encore.__libLoaded | true. The file returns early if the library is already loaded, so it only loads once per resource |
encore.context | 'server' or 'client' |
encore.resource | The name of the resource that imported the library |
The modules load in this order on both sides: util, callback, command, ui, inventory, party, skills, content. The client also loads streaming, world, keybind, target, minigame and radial.
| Module | Side | Reference |
|---|---|---|
Logging, strings, maths, tables, waitFor, export bridging | both | Utilities |
encore.callback | both | Callbacks |
encore.addCommand | both | Registering commands |
encore.addKeybind | client | Keybinds |
encore.notify, progress, inputDialog, prompts, encore.dialog | both / client | UI services |
encore.requestModel and friends, nearby entities, vehicles, text | client | Streaming and world helpers |
encore.content | both | Content API |
encore.inventory | both | Inventory interface |
encore.target | client | Targeting interface |
encore.minigame, encore.radial | client | Minigames and radial menu |
encore.skills, encore.party | both | Skills and party interfaces |
The library runs in your resource
init.lua runs inside the Lua state of the resource that imports it, not inside en-core. So:
- Callbacks, commands, keybinds, target options and radial items you register belong to your resource, and are cleaned up when it stops.
GetCurrentResourceName()andencore.resourcereturn your resource's name.- Settings such as
encore.callback.timeoutapply only to your resource.
Resource-aware require
init.lua replaces the global require with a loader that reads Lua files out of resources.
| Call | Loads |
|---|---|
require 'config.server' | <this resource>/config/server.lua |
require 'modules.utils' | <this resource>/modules/utils.lua, falling back to modules/utils/init.lua |
require '@en-core.shared.jobs' | en-core/shared/jobs.lua (the @resource. prefix reads from another resource) |
How it behaves:
- Dots become slashes. The loader tries
path.luafirst, thenpath/init.lua. - Each module runs once per resource and the result is cached under the exact name string. A module that returns nothing is cached as
true. - A missing file raises
module "<name>" not found (looked for <resource>/<path>.lua). - A circular require raises
circular require: "<name>" is already being loaded. - Compile and runtime errors are re-raised, and the cache entry is cleared so the next call can try again.
- On the server, files are read straight off disk, so server-only modules need no manifest entry.
- On the client, the file must be listed in your resource's
files {}so the client has downloaded it.
Don't also list a required file under client_scripts, server_scripts or shared_scripts. It would run twice: once as a script and once through require.
-- fxmanifest.lua
files { 'config/client.lua' }
-- server/main.lua
local Config = require 'config.server' -- read from disk, no manifest entry needed
local jobs = require '@en-core.shared.jobs' -- a file from another resource
-- client/main.lua
local ClientConfig = require 'config.client' -- must be in files {}Nothing throws at runtime
The library follows one design rule: a runtime failure never takes down the thread that called it. Callers check results instead.
- A callback that times out returns nothing.
- A model that doesn't exist or won't load returns
nil. - A missing backing resource logs a warning once and returns a fallback value.
- An export that errors is caught, logged once per export name, and turned into the fallback value.
Programmer mistakes still raise an error so you find them straight away: require with a bad or missing module name, encore.addKeybind without a name, and encore.radial.addItem without a string id.
Interfaces instead of dependencies
EndCore resources never call inventory, targeting, minigame, radial, skills or party resources directly. They call encore.*, and the library forwards the call to a fixed resource name:
| Interface | Library module | Resource it calls | Without it |
|---|---|---|---|
| Inventory | encore.inventory | en-inventory | Item operations report failure (false, 0, nil) |
| Targeting | encore.target | en-target | Nothing is shown; registrations are remembered |
| Minigames | encore.minigame | en-minigames | A cancellable progress bar stands in |
| Radial menu | encore.radial | en-radialmenu | Nothing is shown; items are remembered |
| Skills | encore.skills | en-skills | Level 1, bonus 0, no perks, no XP recorded |
| Party | encore.party | en-party | Every player is a party of one |
Every forwarded call first checks GetResourceState(<name>) == 'started'. The export lookup and the call both run inside pcall. If you want to replace one of these resources, see the contracts on Inventory interface and Targeting interface.
UI calls work the same way against en-ui. See UI services.
Security basics
- On the server,
sourceis the only value you can trust. Anything a client sends you through a callback, net event, NUI callback orserverEventtarget option can be forged. Check its type, range, ownership and distance before acting on it. - Content placed in game, item labels, character names and chat are typed by people. Validate them in Lua and display them in NUI as text only. See Design system.
- Restrict admin commands with
restrictedon the server. Client commands can never be restricted.