Most FiveM problems that look mysterious at first are really communication problems. A garage script cannot find a player’s job. A housing resource fires twice. A client tells the server it has money when it absolutely should not be trusted to do that.
To keep resources working together, FiveM developers commonly use exports, events, and state bags. They overlap a little, but they are not interchangeable. Knowing which tool fits which job makes a server easier to maintain, safer to extend, and far less painful to debug after the next resource update.
This guide explains the practical distinction. Examples are deliberately framework-neutral because the exact APIs and conventions can differ between standalone resources, ESX, QBCore, and other frameworks.
Start with the simple mental model
Think of a FiveM resource as a self-contained package: a script or collection of scripts with its own manifest, files, configuration, and responsibilities. One resource might manage banking, another might run vehicle garages, and another might handle a job system.
- Exports are direct functions another resource can call when it needs an answer or a specific service.
- Events are messages broadcast or sent between parts of the game when something happened.
- State bags are synchronized pieces of state attached to an entity, player, or global state that other scripts can observe.
A reliable rule is: use an export to ask for or perform something, an event to announce that something happened, and a state bag to expose current synchronized state.
When exports are the cleanest option
An export exposes a function from one resource so another resource can call it. This is usually the best choice when the calling resource needs an immediate result.
For example, a garage resource may need to ask a finance resource whether a vehicle has an unpaid balance. The garage needs a clear yes-or-no response before allowing a vehicle to leave. That is a good export-shaped problem.
Good uses for exports
- Checking whether a player has a particular permission or role.
- Getting a formatted character identifier from an identity resource.
- Opening an interface supplied by another resource.
- Requesting a calculation, such as tax or repair cost, from the resource that owns that logic.
- Calling a documented service, such as registering an interaction point or adding an item definition.
Exports make dependencies visible. If your resource requires a particular exported function, it has a real relationship with the resource that provides it. That is helpful, provided the provider keeps a stable, documented interface.
FiveM’s official documentation covers the basic pattern for using exports in resources. The syntax changes by runtime, but the design principle is the same: expose a narrow public interface rather than reaching into another resource’s private files or globals.
Keep exported APIs small and purposeful
A common trap is exporting every internal function “just in case.” That turns a resource into a loose box of undocumented controls, and every outside script can become dependent on implementation details.
Instead, create exports around stable outcomes. GetPlayerBalance is clearer than exposing five separate functions that reveal how your banking resource stores accounts. CanAccessGarage is safer than making every garage script reproduce your permission logic.
If an export changes, treat that change as an API change. Update dependent resources in a test environment before deploying it to players.
When events make more sense
Events are ideal when the sender does not need an immediate answer, or when several resources may care about the same action. A resource can trigger an event; another resource can register a handler and react to it.
Imagine a player completes a delivery. The delivery resource may announce that completion. A reputation resource could award reputation, a logging resource could save an audit entry, and a mission tracker could update progress. The delivery resource should not need to know every system listening for that activity.
Good uses for events
- Notifying other systems that a job shift started or ended.
- Broadcasting that a player was arrested, revived, or entered an owned property.
- Updating a user interface after the server has validated a change.
- Triggering optional integrations, such as logs, achievements, dispatch notifications, or status effects.
- Sending a request from client to server when the server must validate the action.
Events are powerful because they reduce tight coupling, but that flexibility has a cost: tracing the full path can be harder. A single event may have multiple listeners, and an event name that is too generic can collide with another resource or become impossible to understand six months later.
Use clear, namespaced names. A name such as mygarage:server:vehicleStored communicates ownership, side, and intent better than saveCar. Pick a naming convention and use it consistently.
Never trust client-triggered events by default
This is the most important security distinction in everyday FiveM scripting. A client is controlled by the player. Even if your normal UI only triggers an event after a button click, a malicious client can attempt to trigger a networked event with altered values.
That means the server should validate important requests independently. If a client asks to purchase a vehicle, the server should verify the player’s identity, funds, item or vehicle data, location or relevant conditions, and any cooldowns before completing the transaction. Do not accept a client-supplied price, job rank, inventory count, or permission flag as proof.
FiveM’s Secure your events guidance is worth treating as required reading for anyone publishing or configuring server resources. In particular, keep sensitive actions server-authoritative and avoid registering network events that do not need to be callable from clients.
Where state bags fit in
State bags store synchronized state associated with players, entities, or the server’s global state. They can be useful for information that multiple resources need to observe, such as a temporary duty status, a vehicle’s lock state, or an entity’s owner-related metadata.
They are not a replacement for a proper database, an export API, or secure server validation. State is replicated for synchronization and observation; it is not a magic permission system.
Use state bags for current, lightweight state
State bags work best when a value represents what is true right now and other resources or clients may need to react to that fact. Change handlers can make this especially useful for updating prompts, markers, or UI without constantly polling.
A few practical boundaries help:
- Keep values small and simple; do not repeatedly replicate large nested data structures.
- Do not use a replicated value as the sole authority for money, inventories, punishments, or permissions.
- Decide which side owns each value. Competing writes from client and server are a recipe for confusing behavior.
- Use an export or event when you need to request an action, rather than changing state and hoping another resource interprets it correctly.
The official state bags documentation explains replication behavior and change handlers in more detail. Read the runtime-specific examples before building a system around them.
A quick decision guide
Before adding a new connection between resources, ask these questions in order:
- Does the caller need a result now? Use an export if it needs a direct answer or a defined service.
- Did something happen that several systems might care about? Send an event.
- Is this a small piece of current synchronized state that others should observe? Consider a state bag.
- Can a player influence this request or value? Make the server validate it before it changes anything important.
- Would another creator understand this interface from its name and documentation? If not, simplify it before other resources depend on it.
Common patterns that cause trouble
Using events like function calls
If resource A triggers an event and then assumes resource B has already completed work and returned a result, the timing becomes fragile. Events are asynchronous in practice. If you need a defined response, an export—or an intentionally designed callback pattern supplied by your framework—will usually be clearer.
Putting core business logic on the client
Client-side code is excellent for controls, animations, local effects, and interface feedback. It is the wrong place to make final decisions about currency, rewards, ownership, or access. The server should own the outcome.
Hard-coding another resource’s internals
Editing a third-party resource so it reads another script’s local variables, database layout, or private files creates a brittle integration. Use a documented export or event where available. If none exists, request an integration point from the creator or build a small adapter resource rather than modifying both projects beyond recognition.
Creating circular dependencies
If resource A needs B to start, B needs C, and C needs A, startup and maintenance get complicated fast. Define ownership. A core resource may provide a service, while feature resources consume it. Avoid making every system responsible for every other system.
Build an integration layer for larger servers
On a small server, direct integrations can be fine. As the resource list grows, a dedicated bridge or adapter resource can save a lot of future work. For example, a custom jobs resource could expose one clean interface that translates to whichever inventory, banking, notification, or target system the server currently uses.
The feature resource then depends on your bridge, not on a dozen vendor-specific APIs. This does add another component to maintain, so it is not automatically necessary. It becomes worthwhile when you expect to replace resources, support multiple frameworks, or reuse your work across several servers.
Test communication like a player and like an admin
After adding an export, event, or state handler, test more than the happy path. Try reconnecting, changing characters if your framework supports it, restarting the dependent resource in a safe development environment, and attempting the action with missing permissions or insufficient funds.
Check server console output for errors, but also add concise development logging around critical state changes. A log that says a purchase was rejected because the server-calculated price did not match is much more useful than a generic “failed” message.
Looking ahead, it is reasonable to expect that clear APIs, server-side validation, and careful dependencies will remain valuable habits in future GTA modding ecosystems. Specific GTA VI modding tools, multiplayer support, and workflows are not confirmed here, however; any comparison to future GTA VI development is speculative and might not accurately represent current or future events.
Make the next integration boring—in the best way
The best resource integrations are not flashy. They are predictable: one resource owns its data, public interfaces have clear names, the server validates important actions, and optional systems can listen without becoming mandatory dependencies.
Use exports for direct services, events for meaningful notifications, and state bags for lightweight shared state. Your future self—and the creator trying to diagnose a 2 a.m. server issue—will appreciate the restraint.
Looking for resources to learn from or a place to compare implementation approaches? Check out the available mod downloads and create a free account to join the conversation in the SixMods Community Forums.
Responses