A real progress bar for your FiveM loading screen
Listen for message events on window. loadProgress carries loadFraction (0–1) — the simplest source for a bar. For finer detail, startInitFunctionOrder gives a count, initFunctionInvoking gives the current idx, startDataFileEntries gives a count of map files and performMapLoadFunction fires per file; onLogLine gives a message to show under the bar. Keep the displayed value monotonic so it never goes backwards.
Players forgive a long load if they can see it moving. A frozen screen for ninety seconds feels broken; a bar that creeps forward with a line of text underneath feels like progress. FiveM tells the loading screen exactly what it is doing through a stream of events — this guide shows what each one means and how to turn them into a progress bar that never jumps backwards.
The events FiveM sends
While the game loads, FiveM posts messages to the loading screen page. These are the ones documented by Cfx and used by the official example loading screen:
| eventName | Data you get | Meaning |
|---|---|---|
loadProgress | loadFraction (0–1) | Overall progress estimate |
startInitFunction | type | An init phase begins |
startInitFunctionOrder | type, order, count | A group of count init functions starts |
initFunctionInvoking | type, name, idx | Init function number idx is running |
initFunctionInvoked | type, name, idx | It finished |
endInitFunction | type | The phase ended |
startDataFileEntries | count | count map data files will load |
onDataFileEntry | file details | A data file is being processed |
performMapLoadFunction | idx | One map load step ran |
endDataFileEntries | — | Map data loaded |
onLogLine | message | A human-readable status line |
The init type is one of INIT_BEFORE_MAP_LOADED, INIT_AFTER_MAP_LOADED or INIT_SESSION, which lets you label the phase for the player.
The simplest working bar
<div class="bar"><div class="fill" id="fill"></div></div>
<p class="status" id="status">Connecting…</p>const fill = document.getElementById('fill');
const status = document.getElementById('status');
let shown = 0;
function setProgress(fraction) {
shown = Math.max(shown, Math.min(1, fraction)); // never go backwards
fill.style.width = (shown * 100).toFixed(1) + '%';
}
window.addEventListener('message', (e) => {
const d = e.data;
if (d.eventName === 'loadProgress') setProgress(d.loadFraction);
if (d.eventName === 'onLogLine') status.textContent = d.message;
});.bar { width: min(520px, 60vw); height: 6px; border-radius: 99px; background: rgba(255,255,255,.12); overflow: hidden; }
.fill { height: 100%; width: 0; background: #5b6af0; transition: width .4s ease; }A more detailed bar from the init phases
loadFraction can sit still for a while and then leap. Combining it with the init and map events gives smoother motion and better labels. The pattern below mirrors the official example loading screen: remember the current count, then fill proportionally as idx or map steps advance.
// assumes <div id="phase"> (thin bar) and <p id="detail"> in the page
const phase = document.getElementById('phase');
const detail = document.getElementById('detail');
const LABEL = {
INIT_BEFORE_MAP_LOADED: 'Preparing the game',
INIT_AFTER_MAP_LOADED: 'Loading the city',
INIT_SESSION: 'Joining the session',
};
let count = 0;
let mapSteps = 0;
const handlers = {
loadProgress: (d) => setProgress(d.loadFraction),
startInitFunctionOrder: (d) => {
count = d.count;
status.textContent = LABEL[d.type] || 'Loading';
},
initFunctionInvoking: (d) => {
if (count) phase.style.width = ((d.idx / count) * 100) + '%';
},
startDataFileEntries: (d) => { count = d.count; mapSteps = 0; },
performMapLoadFunction: () => {
mapSteps += 1;
if (count) phase.style.width = ((mapSteps / count) * 100) + '%';
},
onLogLine: (d) => { detail.textContent = d.message; },
};
window.addEventListener('message', (e) => {
(handlers[e.data.eventName] || (() => {}))(e.data);
});Testing the bar in a browser
Outside the game nothing sends these events. Paste this into the DevTools console of your page to simulate a load:
let f = 0;
const t = setInterval(() => {
f += Math.random() * 0.04;
window.postMessage({ eventName: 'loadProgress', loadFraction: Math.min(f, 1) }, '*');
window.postMessage({ eventName: 'onLogLine', message: 'Loading resource ' + Math.round(f * 100) }, '*');
if (f >= 1) clearInterval(t);
}, 250);What happens at 100%
By default FiveM closes the loading screen when the game is ready to spawn the player. If your framework shows a character selector, there may be a gap between the bar reaching the end and the screen closing. You can keep the loading screen up until your own scripts are ready with manual shutdown — see loadscreen_manual_shutdown.
Common questions
How do I make a progress bar on a FiveM loading screen?
Listen for message events on window and use event.data.loadFraction from the loadProgress event (0 to 1) to set the bar’s width.
Why does my loading bar jump backwards?
Different events report progress for different phases. Keep the highest value shown so far and never set the bar lower.
Can I show which resource is loading?
Show the message from the onLogLine event. It describes what the client is currently doing.
Why does the bar reach 100% but the screen stays?
The game is ready but spawning has not finished, or a resource uses manual shutdown. The screen closes when the loading process or your script shuts it down.
How do I test loading events outside FiveM?
Post fake messages with window.postMessage({ eventName: 'loadProgress', loadFraction: 0.5 }, '*') from the browser console.
Want a loading screen you never have to debug?
Build it in the browser, export once, and edit it whenever — no HTML, no re-uploads.
Start free