Build your own FiveM loading screen from scratch

15 min read7 sections · 4 questions answered
Short answer

Create a resource folder with fxmanifest.lua (declaring loadscreen 'index.html', loadscreen_cursor 'yes' and all files under files), an index.html with a background, logo, progress bar and audio element, a style.css using viewport units and object-fit: cover, and a script.js that listens for loadProgress and onLogLine messages. Add ensure for it, disable any other loading screen and restart.

You can build a good loading screen with nothing but a text editor: one manifest, one HTML file, one stylesheet and one script. This tutorial builds a complete one — responsive background, logo, a real progress bar, a status line, music with a mute button and a personal greeting — and explains each part so you can change it afterwards.

The folder

resources/[ui]/my_loadscreen/
my_loadscreen/
├── fxmanifest.lua
├── index.html
├── style.css
├── script.js
├── img/
│   ├── bg.webp
│   └── logo.png
└── music/
    └── theme.mp3

Step 1: fxmanifest.lua

fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

name 'my_loadscreen'
description 'Custom loading screen'

loadscreen 'index.html'
loadscreen_cursor 'yes'

files {
    'index.html',
    'style.css',
    'script.js',
    'img/*',
    'music/*',
}

Each directive is explained in the loading screen manifest guide.

Step 2: index.html

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <img class="bg" src="img/bg.webp" alt="">
  <div class="shade"></div>

  <main class="safe">
    <img class="logo" src="img/logo.png" alt="My City">
    <p class="welcome" id="welcome">Welcome to My City</p>

    <div class="bar"><div class="fill" id="fill"></div></div>
    <p class="status" id="status">Connecting…</p>
  </main>

  <button class="mute" id="mute" aria-label="Mute music">🔊</button>
  <audio id="music" src="music/theme.mp3" autoplay loop></audio>

  <script src="script.js"></script>
</body>
</html>

Step 3: style.css

style.css
html, body { margin: 0; height: 100%; overflow: hidden; background: #05070d; font-family: system-ui, sans-serif; color: #fff; }

.bg { position: fixed; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.shade { position: fixed; inset: 0; background: linear-gradient(180deg, rgba(5,7,13,.15), rgba(5,7,13,.8)); }

.safe {
  position: fixed; inset: 0; margin: auto;
  width: min(100vw, calc(100vh * 16 / 9)); aspect-ratio: 16 / 9;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  gap: 1.6vh; padding-bottom: 9vh; box-sizing: border-box;
}
.logo { width: clamp(180px, 20vw, 560px); height: auto; margin-bottom: auto; margin-top: 18vh; }
.welcome { font-size: clamp(16px, 1.3vw, 28px); opacity: .9; margin: 0; }
.bar { width: min(560px, 60vw); height: 6px; border-radius: 99px; background: rgba(255,255,255,.15); overflow: hidden; }
.fill { height: 100%; width: 0; background: #5b6af0; transition: width .4s ease; }
.status { font-size: clamp(12px, .85vw, 18px); opacity: .7; margin: 0; }

.mute { position: fixed; right: 2vw; bottom: 2vw; width: 44px; height: 44px; border-radius: 12px; border: 1px solid rgba(255,255,255,.2); background: rgba(0,0,0,.4); color: #fff; font-size: 18px; cursor: pointer; }

Viewport units and a 16:9 safe area keep this correct on every resolution — the reasoning is in responsive loading screens.

Step 4: script.js

script.js
const fill = document.getElementById('fill');
const status = document.getElementById('status');
const music = document.getElementById('music');
const mute = document.getElementById('mute');
let shown = 0;

// Greeting from the server, if it sends one (deferrals.handover)
const hand = window.nuiHandoverData || {};
if (hand.name) document.getElementById('welcome').textContent = `Welcome, ${hand.name}`;

// Loading events from FiveM
window.addEventListener('message', (e) => {
  const d = e.data || {};
  if (d.eventName === 'loadProgress') {
    shown = Math.max(shown, Math.min(1, d.loadFraction));
    fill.style.width = (shown * 100).toFixed(1) + '%';
  }
  if (d.eventName === 'onLogLine') status.textContent = d.message;
});

// Music: gentle volume, remembered mute
music.volume = 0.3;
music.muted = localStorage.getItem('muted') === '1';
mute.textContent = music.muted ? '🔇' : '🔊';
mute.addEventListener('click', () => {
  music.muted = !music.muted;
  localStorage.setItem('muted', music.muted ? '1' : '0');
  mute.textContent = music.muted ? '🔇' : '🔊';
});

Step 5: install and test

  1. 1Open index.html in Chrome or Edge and check the layout at a few window sizes.
  2. 2Put the folder in resources/ and add ensure my_loadscreen to server.cfg.
  3. 3Disable any other loading screen resource (qb-loadingscreen, esx_loadingscreen or similar) — only one may declare loadscreen.
  4. 4Restart the server and connect.

Where to take it next

Common questions

How do I make my own FiveM loading screen?

Create a resource with a manifest that declares loadscreen 'index.html' and lists every file under files, write the HTML, CSS and JavaScript, then ensure it and disable any other loading screen.

Do I need to know JavaScript?

Only a little: the script in this tutorial is enough for a progress bar, status line and music. Everything else is HTML and CSS.

Why does my loading screen show but images are missing?

The image files are not listed under files in the manifest, so the client never downloads them.

Can I test the loading screen without FiveM?

Yes, open index.html in a desktop browser. Only the loading events need the game; you can fake them from the 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

More guides