DEV Community

Meteo Studios
Meteo Studios

Posted on

Building a 100+ Script FiveM Server Package: Architecture Decisions That Mattered

When you have over 100 Lua scripts that need to share state, talk to each other, and run at under 0.5ms total CPU, architecture stops being optional. Here is how we structured the Meteo FiveM Server V2.0 and what we would do differently next time.

Context

I run Meteo Studios. We build premade FiveM server packages and individual scripts for QBCore and QBox. Our V2.0 is a complete rewrite. Not a patch on top of the old codebase. New architecture, new patterns, new everything.

The server ships with 100+ custom scripts covering crime systems, civilian jobs, economy, housing, vehicles, and more. Every script is exclusive to this package. The challenge was making all of them work together without turning the codebase into spaghetti.

The Core Problem With Premade FiveM Servers

Most premade servers are not engineered. They are assembled. A seller grabs 50 free scripts from different creators, drops them in a resources folder, and sells the zip. The scripts have no shared architecture. No consistent event patterns. No unified config system. When one breaks, three others break with it.

We built V1 this way ourselves and learned the hard way why it does not scale.

Single Config Source of Truth

The first architectural decision was centralizing server-wide configuration. In V2.0, a single meteo.cfg file controls the server name, logo, currency symbol, speed unit, group size, and other global settings.

Every script reads from this file using FiveM's GetConvar system. No script has its own copy of the server name. No script hardcodes a currency symbol. Change it once in meteo.cfg and every script picks it up on next restart.

-- Any script can read global config
local currency = GetConvar('meteo:currency', '$')
local serverName = GetConvar('meteo:servername', 'My Server')
Enter fullscreen mode Exit fullscreen mode

This also enabled a feature our customers kept asking for: full script renaming. Because no script hardcodes the brand name, one rename operation updates everything. The rename system is documented and takes about 2 minutes.

Event-Driven Over Polling

FiveM scripts commonly use CreateThread with Wait loops to check state. This works for simple scripts but at 100+ resources, polling loops add up fast.

We replaced most polling with event-driven patterns using lib.points from ox_lib for proximity detection and lib.onCache for state changes. The server only does work when something actually changes.

-- Instead of polling player position every 500ms
-- Use lib.points for proximity-based loading
local point = lib.points.new({
    coords = vec3(x, y, z),
    distance = 15,
})

function point:onEnter()
    -- Load targets, spawn entities
end

function point:onExit()
    -- Cleanup
end
Enter fullscreen mode Exit fullscreen mode

Job-specific targets (armory, lockers, garages) only load for players with that job. Spawned entities get cleaned up when players leave areas, change jobs, or disconnect. The result is 0.42ms total client CPU with all 100+ scripts running.

Connected Systems Through a Shared Tablet

The crime ecosystem was the most complex part. Instead of standalone crime scripts that do not know about each other, everything connects through a single crime tablet interface.

Organizations feed into territory control. Territory control affects drug turf zones. Boosting contracts affect crime rank. Crime rank unlocks higher tier heists. Crypto from heists flows into the blackmarket. Everything shares the same economy and progression.

The alternative was separate scripts with their own databases, their own UI, and their own progression. That is what most servers do. But then you get situations where a player maxes out one crime activity and has no reason to touch the others. Connected systems create natural gameplay loops.

Security as Architecture, Not Afterthought

Most FiveM servers bolt on an anti-cheat and call it secure. The problem is that if your server code has exploits, no external anti-cheat will save you.

We followed every official FiveM security guideline at the architecture level:

  • Server-side authority on all critical actions. The client never decides how much money to add or what items to give.
  • Validated net events. Every event checks source player exists and has permission.
  • NUI callback strict mode enabled. Other resources cannot trigger your NUI callbacks.
  • No exposed exports that external code can abuse.
  • Rate limiting on sensitive operations.

The full security breakdown is in our documentation.

Translations Without Code Changes

FiveM communities are global. We needed the entire server to be translatable without touching Lua files.

We used the ox_lib locale system. Every user-facing string is a locale key. One config change in ox.cfg switches the server language. 14+ languages ship out of the box. Adding a new language is one JSON file per script.

-- Every string is a locale key
lib.locale('crime_tablet_title')
lib.locale('job_level_up', { level = newLevel })
Enter fullscreen mode Exit fullscreen mode

No UI edits. No codebase changes. No find-and-replace across 100 files. This was only possible because we enforced locale keys from day one. Retrofitting translations onto an existing codebase is painful. Designing for it from the start is easy.

What We Track Publicly

One decision we made with V2.0 was full transparency on development. Every update is logged in a public changelog. The development roadmap is visible on the development page.

We also run a free showcase server where anyone can test every feature before purchasing. Each feature has a video tutorial. The full script list is documented at docs.meteofivem.net.

What We Would Do Differently

If we started V2.0 again today:

Stricter typing from day one. We use ox_lib's type checking but should have been more aggressive with it early on. Catching type mismatches at dev time instead of runtime saves hours.

Better test tooling. FiveM does not have great unit testing support. We ended up doing most testing manually on the showcase server. Building a lightweight test harness early would have saved time.

Modular UI components. Our NUI layers are custom built per script. Extracting shared UI components into a reusable library earlier would have reduced duplication.

Performance Numbers

For those who care about benchmarks: the full server runs at 0.42ms total client CPU with all 100+ scripts loaded. We did a comparison against a default QBox server to show that our scripts do not bloat the base framework.

The optimization approach is documented in the security and optimization docs if you want the technical details.

Try It

If you want to see how all of this works in practice, the showcase server is free to join. Documentation for every script is at docs.meteofivem.net. The server itself is at meteofivem.net.


I build custom FiveM servers and scripts at Meteo Studios. More at meteofivem.net.

Top comments (0)