How To Create a Custom FiveM Loading Screen

Okay, let’s dive into crafting a unique and engaging entry point for your players.

We’re going to build a Custom FiveM Loading Screen from the ground up.

What is a Loading Screen Resource?

A custom loading screen is often the very first interaction a player has with your specific FiveM server.

It’s a fantastic opportunity to establish your server’s brand, convey important information, and create an immersive atmosphere right from the start.

Forget the generic FiveM visuals; we want players to feel your server’s identity the moment they start connecting.

Here at QBCore Store LLC, we believe in empowering server owners with the tools and knowledge to create truly unique experiences.

This comprehensive guide will walk you through every step, from the basic HTML structure to styling with CSS, adding interactivity with JavaScript, and finally integrating it seamlessly into your FiveM server using Lua.

We’ll even cover how to hide that default FiveM bridge animation for a cleaner transition.

Whether you’re a coding novice or have some web development experience, we’ll break it down clearly.

Let’s get started on making your server stand out.

Why Bother with a Custom FiveM Loading Screen?

You might be wondering if it’s worth the effort.

Absolutely!

Think of it as the lobby or entryway to your virtual world.

First Impressions: It sets the tone and professionalism of your server immediately.

Branding: Reinforce your server’s name, logo, and overall theme.

Information Display: Share crucial information like rules, Discord links, website URLs, or server status updates before players even spawn in.

Engagement: Use music, dynamic messages, or even videos to keep players engaged during the loading process, reducing perceived wait times.

Uniqueness: Differentiate your server from the countless others using default or generic screens.

A well-designed loading screen shows you care about the details and the player experience.

Prerequisites

Before we start coding, let’s make sure you have the necessary tools and basic understanding:

  1. Text Editor: You’ll need a program to write your code.
    • Visual Studio Code (VS Code): Free, powerful, and highly recommended, with many helpful extensions.
    • Sublime Text: Another popular, lightweight option.
    • Notepad++: A solid free choice for Windows users.
    • Avoid using basic Notepad or TextEdit, as they lack features helpful for coding (like syntax highlighting).
  2. Basic Web Development Knowledge (Helpful, Not Essential):
    • HTML (HyperText Markup Language): Understands the basic structure of a web page (tags like <div>, <img>, <p>). We’ll provide the code, but knowing the basics helps.
    • CSS (Cascading Style Sheets): Knows how to style HTML elements (colors, sizes, positions). Again, we’ll guide you, but familiarity is a plus.
    • JavaScript (JS): Understands basic programming concepts for adding interactivity. We’ll keep the JS relatively simple initially.
  3. FiveM Server Access: You need access to your server’s files, specifically the resources folder, to install the loading screen.
  4. Image Editing Software (Optional): Tools like Photoshop, GIMP (free), or even Canva can be useful for creating or editing logos and background images.
  5. Patience and Willingness to Learn: Debugging and tweaking are part of the process!

Don’t worry if you’re not an expert in web development.

We’ll explain each step clearly and provide copy-pasteable code snippets.

Understanding How FiveM Loading Screens Work (NUI)

FiveM utilizes a system called NUI (Native UI) to display web pages inside the game.

Essentially, your custom loading screen is just a standard webpage (built with HTML, CSS, and JavaScript) that FiveM’s NUI system renders while the game assets are loading in the background.

This means we can leverage standard web technologies to create visually rich and interactive experiences.

The core components are:

  • index.html: The main file defining the structure and content of your loading screen.
  • style.css: The file that defines the visual appearance (layout, colors, fonts, etc.).
  • script.js: The file that adds dynamic behavior (like changing text, animations, music playback).
  • fxmanifest.lua (or __resource.lua): A special FiveM file that tells the server this is a resource, specifies it’s a loading screen, and lists all the necessary files.

Now, let’s start building.

Step 1: Creating the Basic HTML Structure (index.html)

First, create a new folder for your loading screen resource. Let’s call it my-loading-screen.

Inside this folder, create a file named index.html.

This file will hold the skeleton of our loading screen.

We need containers for different elements: the background, a logo, loading progress indication, and areas for text messages.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Server Name - Loading...</title>
    <!-- Link to your CSS file -->
    <link rel="stylesheet" href="style.css">
</head>
<body>

    <!-- Main container for the entire screen -->
    <div class="loading-container">

        <!-- Background Element (handled by CSS) -->
        <div class="background"></div>

        <!-- Content Wrapper -->
        <div class="content">

            <!-- Logo Area -->
            <div class="logo-area">
                <img src="images/logo.png" alt="Server Logo" id="server-logo">
                <!-- You can replace img with text if you prefer -->
                <!-- <h1>My Awesome Server</h1> -->
            </div>

            <!-- Message Area -->
            <div class="message-area">
                <p id="loading-message">Initializing connection...</p>
                <p id="dynamic-message">Welcome! Loading server assets...</p>
            </div>

            <!-- Progress Bar Area -->
            <div class="progress-bar-container">
                <div class="progress-bar">
                    <div class="progress-bar-inner" id="progress-bar-inner"></div>
                </div>
                <p id="progress-text">0%</p>
            </div>

             <!-- Music Control (Optional) -->
             <div class="music-control">
                <button id="play-pause-button">Pause Music</button>
                <input type="range" id="volume-slider" min="0" max="1" step="0.01" value="0.5">
            </div>

        </div> <!-- End Content Wrapper -->

    </div> <!-- End Loading Container -->

    <!-- Link to your JavaScript file (place at the end of body) -->
    <script src="script.js"></script>
</body>
</html>

Explanation:

  • <!DOCTYPE html> & <html>: Standard HTML5 boilerplate.
  • <head>: Contains meta-information and links to external resources.
    • charset="UTF-8": Ensures proper character display.
    • viewport: Important for responsive design (adapting to different screen sizes), though less critical for fixed-resolution game loading screens.
    • <title>: Sets the text that might appear in a browser tab (less relevant in FiveM NUI but good practice).
    • <link rel="stylesheet" href="style.css">: Connects our HTML to our CSS file for styling.
  • <body>: Contains the visible content of the page.
  • <div class="loading-container">: The main wrapper for everything. We’ll use this for overall layout.
  • <div class="background">: An empty div we’ll style with CSS to hold our background image or video.
  • <div class="content">: Wraps the actual content (logo, text, progress bar) to help with centering and positioning.
  • <div class="logo-area">: A container for your server’s logo.
    • <img src="images/logo.png" ...>: An image tag. Important: You’ll need to create an images folder inside my-loading-screen and place your logo.png file there. Make sure the filename matches!
  • <div class="message-area">: Holds text messages.
    • We give paragraphs IDs (loading-message, dynamic-message) so we can easily target them with JavaScript later.
  • <div class="progress-bar-container">: Holds the progress bar elements.
    • .progress-bar: The outer container of the bar.
    • .progress-bar-inner: The inner part that will fill up. We give it an ID (progress-bar-inner) for JS control.
    • <p id="progress-text">: Displays the percentage text, also with an ID.
  • <div class="music-control">: (Optional) Basic controls for background music. IDs allow JS interaction.
  • <script src="script.js">: Links our HTML to our JavaScript file. Placing it at the end of the <body> ensures the HTML elements exist before the script tries to interact with them.

Save this file as index.html in your my-loading-screen folder. Create an images subfolder and add a placeholder logo.png for now.

Step 2: Styling the Loading Screen (CSS – style.css)

Now, let’s make it look good!

Create a file named style.css in the same my-loading-screen folder.

This file controls the visual presentation.

/* Basic Reset & Body Styling */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box; /* Makes width/height include padding and border */
}

body, html {
    height: 100%;
    width: 100%;
    overflow: hidden; /* Hide scrollbars */
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Example font */
    color: #ffffff; /* Default text color (white) */
}

/* Main Container */
.loading-container {
    position: relative; /* Needed for absolute positioning of children */
    width: 100%;
    height: 100%;
    display: flex; /* Use flexbox for centering content */
    justify-content: center;
    align-items: center;
    text-align: center;
}

/* Background Styling */
.background {
    position: absolute; /* Take up full screen behind content */
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-image: url('images/background.jpg'); /* CHANGE THIS to your image */
    background-size: cover; /* Scale image to cover the container */
    background-position: center center; /* Center the image */
    background-repeat: no-repeat;
    z-index: -1; /* Place it behind other content */
    filter: brightness(0.6); /* Optional: Darken the background slightly */
}

/* --- OR Use a Solid Color Background --- */
/*
.background {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #1a1a1a;
    z-index: -1;
}
*/

/* Content Wrapper */
.content {
    z-index: 1; /* Ensure content is above the background */
    padding: 20px;
    background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black background */
    border-radius: 10px;
    max-width: 600px; /* Limit content width */
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}

/* Logo Area */
.logo-area {
    margin-bottom: 30px;
}

#server-logo {
    max-width: 200px; /* Adjust max logo width */
    height: auto; /* Maintain aspect ratio */
    display: block; /* Allows margin auto to center */
    margin-left: auto;
    margin-right: auto;
}

/* Message Area */
.message-area {
    margin-bottom: 30px;
}

#loading-message {
    font-size: 1.2em;
    font-weight: bold;
    margin-bottom: 10px;
    color: #cccccc;
}

#dynamic-message {
    font-size: 1em;
    min-height: 40px; /* Prevent layout shifts when message changes */
}

/* Progress Bar Area */
.progress-bar-container {
    width: 80%; /* Width relative to the content container */
    margin: 0 auto; /* Center the container */
    margin-bottom: 20px;
}

.progress-bar {
    width: 100%;
    background-color: #555555; /* Dark grey background */
    border-radius: 5px;
    overflow: hidden; /* Hide overflowing inner bar */
    height: 25px; /* Bar height */
    border: 1px solid #333;
}

.progress-bar-inner {
    height: 100%;
    width: 0%; /* Start at 0% width */
    background-color: #4CAF50; /* Green progress color */
    border-radius: 5px 0 0 5px; /* Keep left radius */
    transition: width 0.5s ease-in-out; /* Smooth transition for width changes */
    text-align: center;
    line-height: 25px; /* Vertically center text if needed inside */
    color: white;
}

#progress-text {
    margin-top: 5px;
    font-size: 0.9em;
}


/* Music Control (Optional) */
.music-control {
    margin-top: 25px;
    display: flex; /* Arrange button and slider side-by-side */
    justify-content: center;
    align-items: center;
    gap: 15px; /* Space between elements */
}

#play-pause-button {
    padding: 8px 15px;
    background-color: #4CAF50;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-size: 0.9em;
    transition: background-color 0.3s ease;
}

#play-pause-button:hover {
    background-color: #45a049;
}

#volume-slider {
    cursor: pointer;
    width: 150px; /* Adjust slider width */
}

/* Add some basic responsiveness if needed, though less critical in NUI */
@media (max-width: 600px) {
    .content {
        max-width: 90%;
    }
    .progress-bar-container {
        width: 90%;
    }
}

Explanation:

  • * { box-sizing: border-box; }: A common reset to make sizing elements more predictable.
  • body, html: Sets the base height/width and hides potential scrollbars. Sets a default font and text color.
  • .loading-container: Uses display: flex to easily center the .content div both horizontally (justify-content) and vertically (align-items). position: relative is crucial for positioning the absolute background.
  • .background:
    • position: absolute: Takes the element out of the normal flow and positions it relative to the nearest positioned ancestor (.loading-container).
    • top: 0; left: 0; width: 100%; height: 100%;: Makes it cover the entire container.
    • background-image: url(...): Crucially, change 'images/background.jpg' to the actual path of your background image. Make sure the image is in the images folder.
    • background-size: cover: Scales the image nicely.
    • z-index: -1: Pushes it behind other elements.
    • filter: brightness(0.6): An optional effect to darken the background, making text more readable. Adjust or remove as needed.
    • Alternative: A commented-out section shows how to use a simple solid background color instead of an image.
  • .content:
    • z-index: 1: Ensures it sits on top of the background.
    • background-color: rgba(0, 0, 0, 0.5): A semi-transparent black background for the content area itself, helping text stand out against complex backgrounds. Adjust the last value (alpha) from 0 (fully transparent) to 1 (fully opaque).
    • border-radius, max-width, box-shadow: Add some visual polish.
  • .logo-area, #server-logo: Styles the logo container and the logo image itself (setting max width, centering).
  • .message-area, #loading-message, #dynamic-message: Styles the text elements (font size, color, margins). min-height prevents the layout from jumping when the dynamic message content changes length.
  • .progress-bar-container, .progress-bar, .progress-bar-inner: Styles the progress bar.
    • The outer container (.progress-bar) sets the background color and shape.
    • The inner bar (.progress-bar-inner) is what grows. It starts at width: 0%. We’ll change this width using JavaScript. transition: width 0.5s ease-in-out; makes the width change smooth.
  • .music-control, #play-pause-button, #volume-slider: Styles the optional music controls using flexbox for layout and adding basic button styling.
  • @media (max-width: 600px): A simple example of a media query for responsiveness. It adjusts the content width on smaller screens (less critical for FiveM but good practice).

Save this as style.css. Remember to create the images folder and add your background.jpg (or whatever you named it) and logo.png.

At this point, you could technically open the index.html file directly in your web browser (like Chrome or Firefox) to preview its static appearance!

Step 3: Adding Interactivity & Dynamic Content (JavaScript – script.js)

Now let’s breathe some life into our static page using JavaScript.

Create a file named script.js in your my-loading-screen folder.

We’ll add functionality for:

  1. Simulating loading progress.
  2. Displaying dynamic/changing messages.
  3. Adding background music with controls.
  4. Handling FiveM NUI events (the proper way to get loading progress).
// Wait for the DOM (Document Object Model - the HTML structure) to be fully loaded
document.addEventListener('DOMContentLoaded', () => {

    // --- Get References to HTML Elements ---
    const progressBarInner = document.getElementById('progress-bar-inner');
    const progressText = document.getElementById('progress-text');
    const dynamicMessage = document.getElementById('dynamic-message');
    const loadingMessage = document.getElementById('loading-message'); // To update stages

    // --- Configuration ---
    const messages = [
        "Loading core systems...",
        "Establishing network connection...",
        "Downloading latest server assets...",
        "Syncing player data...",
        "Parsing map details...",
        "Almost there, preparing the world...",
        "Tip: Visit our Discord at discord.gg/yourinvite",
        "Tip: Check the rules on our website yourwebsite.com",
        "Welcome to Our Awesome Server!"
    ];
    let currentMessageIndex = 0;
    const messageChangeInterval = 5000; // Change message every 5 seconds (5000ms)

    // Background Music (Optional)
    const backgroundMusic = new Audio('audio/background_music.ogg'); // IMPORTANT: Use .ogg for FiveM compatibility
    backgroundMusic.volume = 0.5; // Set initial volume (0.0 to 1.0)
    backgroundMusic.loop = true; // Loop the music

    const playPauseButton = document.getElementById('play-pause-button');
    const volumeSlider = document.getElementById('volume-slider');
    let isPlaying = false; // Track music state

    // --- Functions ---

    // Function to update the progress bar and text
    function updateProgress(percentage) {
        percentage = Math.min(100, Math.max(0, percentage)); // Clamp between 0 and 100
        progressBarInner.style.width = `${percentage}%`;
        progressText.textContent = `${Math.round(percentage)}%`;
    }

    // Function to change the dynamic message
    function changeDynamicMessage() {
        dynamicMessage.style.opacity = 0; // Fade out
        setTimeout(() => {
            currentMessageIndex = (currentMessageIndex + 1) % messages.length;
            dynamicMessage.textContent = messages[currentMessageIndex];
            dynamicMessage.style.opacity = 1; // Fade in
        }, 500); // Wait for fade out transition (0.5s)
    }

     // Function to attempt playing music (handles browser autoplay restrictions)
    function playMusic() {
        backgroundMusic.play().then(() => {
            isPlaying = true;
            playPauseButton.textContent = 'Pause Music';
            console.log("Music started playing.");
        }).catch(error => {
            // Autoplay was prevented, common in browsers until user interaction
            console.log("Music autoplay failed. Waiting for user interaction.", error);
             isPlaying = false;
            playPauseButton.textContent = 'Play Music';
            // We might need a click listener on the body or button to initiate playback
        });
    }

    // --- Initial Setup ---

    // Set initial loading message
    loadingMessage.textContent = "Initializing...";
    updateProgress(0); // Start progress at 0%

    // Start changing dynamic messages
    dynamicMessage.textContent = messages[0]; // Show the first message immediately
    setInterval(changeDynamicMessage, messageChangeInterval);

    // Try to play music automatically
    playMusic(); // Attempt background music playback

     // --- Event Listeners ---

     // Music Controls Event Listeners
    playPauseButton.addEventListener('click', () => {
        if (isPlaying) {
            backgroundMusic.pause();
            isPlaying = false;
            playPauseButton.textContent = 'Play Music';
        } else {
             // Important: Re-trigger play function which handles potential initial failures
            playMusic();
        }
    });

    volumeSlider.addEventListener('input', (event) => {
        backgroundMusic.volume = event.target.value;
    });


    // --- FiveM NUI Event Handling ---
    // This is the CORE of interacting with the FiveM loading process

    /*
       FiveM NUI messages are sent via JavaScript events.
       We listen for 'message' events on the window object.
       The event 'data' property contains the information sent from Lua.
    */
    window.addEventListener('message', function(event) {
        const data = event.data;

        // Check for the specific NUI message type used by FiveM for loading progress
        // The 'loadstatus' event provides overall progress text.
        if (data.type === 'loadstatus') {
            if(data.status) {
                 loadingMessage.textContent = data.status;
            }
        }
        // The 'progress' event provides detailed component progress (use this for the bar)
        else if (data.eventName === 'progress') {
             // data.loadFraction gives a value between 0.0 and 1.0
             const progressPercentage = data.loadFraction * 100;
             updateProgress(progressPercentage);
        }
         // A custom event we might send from Lua when loading is almost done
         else if (data.type === 'loadingComplete') {
             updateProgress(100);
             loadingMessage.textContent = "Loading Complete! Joining server...";
             // You could add fade-out effects here before the screen disappears
        }
    });


    // --- Fallback/Simulated Progress (If NUI events aren't received or for testing) ---
    // Comment this out or remove it if you rely solely on FiveM NUI events
    /*
    let simulatedProgress = 0;
    const interval = setInterval(() => {
        simulatedProgress += Math.random() * 5; // Increment by a random small amount
        if (simulatedProgress >= 100) {
            simulatedProgress = 100;
            clearInterval(interval); // Stop the simulation when 100% is reached
            loadingMessage.textContent = "Loading Complete! Joining server..."; // Update final message
        }
        updateProgress(simulatedProgress);
    }, 300); // Update every 300ms
    */

    // Add a small fade-in effect for the whole screen on load
    document.body.style.opacity = 0;
    setTimeout(() => {
         document.body.style.transition = 'opacity 1s ease-in-out';
         document.body.style.opacity = 1;
    }, 100); // Start fade-in slightly after load

}); // End DOMContentLoaded

Explanation:

  1. document.addEventListener('DOMContentLoaded', () => { ... });: This ensures that the JavaScript code only runs after the entire HTML page structure has been loaded and is ready to be manipulated.
  2. Element References: We get references to the HTML elements we need to interact with using document.getElementById(). This is why having unique IDs in the HTML is important.
  3. Configuration:
    • messages: An array holding the different text strings you want to cycle through in the dynamic message area. Customize these!
    • currentMessageIndex: Keeps track of which message is currently displayed.
    • messageChangeInterval: Sets how often (in milliseconds) the message changes.
  4. Background Music Setup:
    • new Audio('audio/background_music.ogg'): Creates an HTML audio object. Crucially:
      • Create an audio folder inside my-loading-screen.
      • Place your background music file there.
      • Use the .ogg format! MP3 and other formats can be unreliable or not work at all within FiveM NUI. You can easily find online converters to change MP3 to OGG.
    • backgroundMusic.volume: Sets the initial volume (0.0 = silent, 1.0 = full).
    • backgroundMusic.loop = true;: Makes the music repeat.
    • We also get references to the play/pause button and volume slider.
  5. updateProgress(percentage) function: Takes a number (0-100), clamps it to ensure it’s within bounds, updates the width style of the inner progress bar element, and changes the text content of the percentage display.
  6. changeDynamicMessage() function:
    • Uses setInterval in the setup phase to call this function repeatedly.
    • It calculates the index of the next message, wrapping around using the modulo operator (%).
    • Updates the textContent of the dynamicMessage element.
    • Bonus: Includes a simple fade-out/fade-in effect using CSS opacity and setTimeout for a smoother transition. Add transition: opacity 0.5s ease-in-out; to the .message-area p selector in your CSS for this to work visually.
  7. playMusic() function: Attempts to play the music using backgroundMusic.play(). The .then() handles successful playback, while .catch() handles errors, which often occur due to browser autoplay restrictions (requiring user interaction first). It updates the button text accordingly.
  8. Initial Setup: Sets the initial text, resets progress to 0, displays the first dynamic message, and starts the interval timer for message changes. It also calls playMusic() to try and start the audio.
  9. Event Listeners (Music Controls):
    • Listens for clicks on the playPauseButton. If music is playing, it pauses it; otherwise, it calls playMusic() again (important to handle cases where initial autoplay failed).
    • Listens for input events on the volumeSlider (fires continuously as the slider moves) and updates the backgroundMusic.volume.
  10. FiveM NUI Event Handling (window.addEventListener('message', ...)):
    • This is the most important part for real integration. FiveM sends messages to the NUI window (your HTML page) using the postMessage API.
    • We listen for these messages on the window object.
    • event.data contains the payload sent from FiveM’s Lua scripts.
    • We check event.data.type or event.data.eventName (different FiveM versions/contexts might use slightly different structures) to see what kind of message it is.
    • 'loadstatus': Often contains general status text (e.g., “Loading map,” “Initializing scripts”). We update the loadingMessage paragraph.
    • 'progress': This is typically used for the actual loading bar progress. data.loadFraction usually provides a value from 0.0 to 1.0, which we convert to a percentage and feed into our updateProgress function.
    • 'loadingComplete': This isn’t a standard FiveM event, but an example of a custom message you could send from a Lua script (which we’ll discuss later) to signal the end of loading, allowing you to set progress to 100% and show a final message.
  11. Fallback/Simulated Progress:
    • The commented-out section provides a basic simulation of progress. It uses setInterval to increment the progress bar by a small random amount periodically.
    • This is useful for testing your loading screen visually in a browser without running FiveM.
    • You should REMOVE or COMMENT OUT this simulation code when using the actual FiveM NUI events, otherwise, you might see conflicting progress updates.
  12. Fade-in Effect: Adds a subtle fade-in for the entire body when the page loads for a smoother appearance.

Save this file as script.js. Remember to create the audio folder and add your .ogg music file.

Step 4: Integrating with FiveM (Lua – fxmanifest.lua)

Now we need to tell the FiveM server about our new resource and identify it as a loading screen.

Create a file named fxmanifest.lua in the root of your my-loading-screen folder.

-- Resource Manifest
fx_version 'cerulean' -- Use 'cerulean' or a newer version like 'adamant' or 'bodacious'
game 'gta5'

author 'Your Name or Server Name'
description 'Custom Loading Screen for My Awesome Server'
version '1.0.0'

-- Specify this resource as the loading screen
loadscreen 'index.html'

-- List all files needed by the UI (HTML, CSS, JS, images, audio, fonts, etc.)
files {
    'index.html',
    'style.css',
    'script.js',
    'images/logo.png',
    'images/background.jpg', -- Add all your images here
    'audio/background_music.ogg' -- Add all your audio files here
    -- 'fonts/mycustomfont.woff2' -- Add custom fonts if you use any
}

-- Optional: Client script for advanced control (like hiding default elements)
client_script 'client.lua'

-- Optional: If your loading screen needs to fetch data FROM the server (more advanced)
-- server_script 'server.lua'

-- Optional: Define NUI settings if needed (rarely required for basic loading screens)
-- nui_settings {
--     ['scriptFramePolicy'] = "frame-ancestors 'self' https://cfx.re" -- Example security policy
-- }

Explanation:

  • fx_version 'cerulean': Defines the manifest version. ‘cerulean’ is a common baseline, but newer versions like ‘adamant’ or ‘bodacious’ exist. Stick with ‘cerulean’ unless you need features from newer versions.
  • game 'gta5': Specifies the game this resource is for.
  • author, description, version: Metadata about your resource. Fill these in appropriately.
  • loadscreen 'index.html': This is the crucial line. It tells FiveM to use the specified HTML file (index.html in our case) as the game’s loading screen.
  • files { ... }: Very important! You must list every single file that your HTML page needs to load, relative to the resource’s root folder. This includes:
    • The HTML file itself (index.html)
    • The CSS file (style.css)
    • The JavaScript file (script.js)
    • All images (e.g., images/logo.png, images/background.jpg)
    • All audio files (e.g., audio/background_music.ogg)
    • Any custom fonts you might have linked in your CSS.
    • If you forget a file here, it won’t load in-game!
  • client_script 'client.lua': We include this because we’ll create a small client script in the next step to handle hiding the default FiveM loading elements.
  • server_script 'server.lua': Only needed for advanced scenarios where your loading screen needs to communicate back to the server (e.g., fetching dynamic player counts before the main game environment loads, which is complex). We won’t use this for a basic setup.
  • nui_settings: Allows setting specific security policies for the NUI frame. Generally not needed for standard loading screens unless you’re embedding external content or dealing with complex interactions.

Save this file as fxmanifest.lua.

(Note: Older servers might use __resource.lua instead of fxmanifest.lua. The syntax is very similar, but fx_version is usually omitted or different, and directives might vary slightly. fxmanifest.lua is the modern standard).

Step 5: Disabling the Default FiveM Bridge Animation (Lua – client.lua)

By default, FiveM shows its own loading text and sometimes a “bridge” loading animation before your custom screen fully takes over. We can hide these for a cleaner look using a client-side Lua script.

Create a file named client.lua in your my-loading-screen folder.

-- client.lua for the loading screen resource

-- This code runs as soon as the resource starts on the client

-- We wait a brief moment to ensure NUI is likely ready
Citizen.Wait(100)

-- Method 1: Using ShutdownLoadingScreenNui (Recommended for simple hiding)
-- This attempts to immediately hide the default FiveM loading GUI elements.
-- It's often effective, but timing can sometimes be tricky depending on client load speed.
ShutdownLoadingScreenNui()

-- You can also send a message to your NUI page if needed, for example,
-- to signal that Lua is ready or pass initial data.
-- SendNUIMessage({
--     type = "luaReady",
--     message = "Client script has loaded!"
-- })


-- Method 2: More controlled hiding using CreateThread and AddTextEntry
-- This method continuously overrides the default loading text entries.
-- It can be more reliable in ensuring default text doesn't flicker briefly.
-- Uncomment this section and comment out ShutdownLoadingScreenNui() if you prefer this.
--[[
Citizen.CreateThread(function()
    -- Hide the default "Initializing..." text components
    AddTextEntry('FE_THDR_GTAO', ' ') -- Loading Online
    AddTextEntry('PM_NAME_APP', ' ') -- FiveM Application Name (might vary)
    AddTextEntry('PM_INFO_DET', ' ') -- Build Info / Connecting status
    AddTextEntry('LOADING_SPLAYER_L', ' ') -- Loading Story Mode (sometimes appears)
    AddTextEntry('DLC_ITEM_UNLOCK', ' ') -- Unlock messages if any

    -- Keep overriding them periodically while the custom screen is expected to be active
    -- This loop might be excessive; often just setting them once is enough.
    -- Adjust the Wait time or remove the loop if performance is impacted.
    while true do
        Citizen.Wait(500) -- Check/override every 500ms

        -- Check if the loading screen is still active (pseudo-code, needs actual logic)
        -- local isLoading = GetIsLoadingScreenActive() -- This native might not work early enough
        -- if not isLoading then break end -- Exit loop when main game loads (needs better condition)

        -- Re-apply overrides just in case
        AddTextEntry('FE_THDR_GTAO', ' ')
        AddTextEntry('PM_NAME_APP', ' ')
        AddTextEntry('PM_INFO_DET', ' ')
        AddTextEntry('LOADING_SPLAYER_L', ' ')
        AddTextEntry('DLC_ITEM_UNLOCK', ' ')

        -- Optionally, hide the rotating loading circle in the bottom right
        HideHudComponentThisFrame(14) -- HUD_LOADING_SPINNER
    end
end)
--]]

-- You can add more logic here if needed, for example, listening for game events
-- to send specific messages to your NUI loading screen.

-- Example: Send a message when the player spawns (though loading screen is usually gone by then)
-- AddEventHandler('playerSpawned', function()
--    SendNUIMessage({ type = 'playerReady' })
-- end)

print('[MyLoadingScreen] Client script loaded.')

Explanation:

  • Citizen.Wait(100): A small delay. Sometimes trying to interact with NUI or game elements immediately when the script loads can fail. This gives things a moment to initialize.
  • ShutdownLoadingScreenNui(): This is a built-in FiveM native function specifically designed to hide the default loading screen UI elements provided by the game/FiveM itself. It’s usually the simplest and most direct way.
  • SendNUIMessage({ ... }): An example showing how you can send data from Lua to your JavaScript. The table you pass becomes the event.data object in your window.addEventListener('message', ...) listener in script.js. You could use this to trigger specific actions or pass server information.
  • Method 2 (Commented Out):
    • Provides an alternative approach using AddTextEntry. This function allows you to override default game text strings identified by their keys (like FE_THDR_GTAO). By setting them to a space (‘ ‘), you effectively hide them.
    • The Citizen.CreateThread creates a separate thread for this task.
    • The while true loop (with Citizen.Wait) continuously reapplies these overrides. This can be more robust against the game trying to reset the text but might be overkill. It also includes HideHudComponentThisFrame(14) to hide the spinner.
    • Choose one method. Using ShutdownLoadingScreenNui() is generally preferred for simplicity unless you encounter issues where default elements still flash briefly.
  • print(...): Logs a message to the client’s F8 console, useful for confirming the script loaded.

Save this file as client.lua.

Step 6: Installing and Running the Loading Screen

Now that all the pieces are created, let’s put it on the server.

  1. Upload the Resource:
    • Take the entire my-loading-screen folder (which now contains index.html, style.css, script.js, fxmanifest.lua, client.lua, and the images and audio subfolders with their content).
    • Upload this complete folder to your FiveM server’s resources directory. You might use FTP software (like FileZilla) or your server host’s web panel. The structure should look like: [server-data]/resources/my-loading-screen/.
  2. Ensure the Resource in server.cfg:
    • Open your server’s main configuration file, usually named server.cfg.
    • Find the section where resources are started (lines usually beginning with ensure or start).
    • Add a line to start your loading screen resource:
      cfg # Custom Loading Screen ensure my-loading-screen
    • Placement matters slightly: Ensure it’s listed before resources that might take a long time to load if you want the screen to appear as early as possible. However, basic ensure is usually sufficient. Do not place it inside any [category] brackets if you want it to be a default resource.
  3. Restart Your Server: For the changes in server.cfg and the new resource to be recognized, you must fully restart your FiveM server.
  4. Connect and Test: Launch FiveM and connect to your server. You should now see your custom loading screen instead of the default one! Test the progress bar (it should react to the actual FiveM loading), the changing messages, and the music controls. Check the F8 console in-game for any errors from your client.lua or potential NUI issues. Check the browser console (often accessible via F8 -> NUI Tools, or by opening the HTML directly) for JavaScript errors.

Advanced Customization Ideas

Once you have the basics working, you can explore more advanced features:

  • Background Videos: Instead of a static image, use an HTML <video> tag.
    • Add <video autoplay muted loop id="bg-video"><source src="videos/myvideo.mp4" type="video/mp4"></video> to your index.html.
    • Style #bg-video in CSS similar to the .background div (absolute position, 100% width/height, object-fit: cover, z-index: -1).
    • Important: Videos significantly increase loading screen size. Optimize them heavily (resolution, bitrate). Use formats like .mp4 (H.264 codec). Remember to add the video file to fxmanifest.lua. Autoplay might require muted attribute initially due to browser policies; you might need JS to unmute based on user interaction (like clicking the volume control).
  • Fetching Server Rules/Messages Dynamically: Instead of hardcoding messages in JS, use fetch in your script.js to load rules or announcements from a .json file within your resource or even from an external web server/API. This makes updates easier.
  • Using Web Frameworks: Employ CSS frameworks like Tailwind CSS or Bootstrap for faster styling, or JavaScript frameworks like Vue.js or React for more complex UI logic (though this adds significant complexity and build steps).
  • API Integrations: Fetch data from external APIs (e.g., show online player count from your Discord server using a Discord bot and a simple API endpoint). This requires server-side scripting (server.lua in your resource or a separate web service) to handle securely.
  • More Sophisticated Animations: Use CSS animations (@keyframes) or JavaScript animation libraries (like GSAP) for smoother transitions, fading effects, or animated logos.

Troubleshooting Common Issues

  • Loading Screen Doesn’t Appear:
    • Check server.cfg: Is ensure my-loading-screen present and spelled correctly? Are there any errors in the server console on startup related to the resource?
    • Check fxmanifest.lua: Is the loadscreen 'index.html' line correct? Are all necessary files (HTML, CSS, JS, images, audio) listed in the files block? Check filenames and paths carefully (case-sensitive on Linux!).
    • Check Folder Structure: Is the my-loading-screen folder directly inside the resources folder?
  • CSS Styles Not Applied:
    • Check HTML <link> tag: Is the href="style.css" correct?
    • Check fxmanifest.lua: Is style.css listed in the files block?
    • Check CSS Syntax: Are there typos or errors in your style.css file? Use a CSS validator.
    • Browser Cache: Sometimes FiveM’s NUI cache holds onto old versions. Clear your FiveM cache (usually in %localappdata%\FiveM\FiveM.app\cache on Windows, delete folders like browser, db, nui-storage) and restart FiveM.
  • JavaScript Not Working (No progress, no messages changing, no music):
    • Check HTML <script> tag: Is the src="script.js" correct and placed at the end of the <body>?
    • Check fxmanifest.lua: Is script.js listed in the files block?
    • Check Browser Console: Open the F8 console in FiveM, go to NUI Devtools (if available) or open the index.html directly in a browser and check the developer console (usually F12) for JavaScript errors. These errors often pinpoint the exact problem line.
    • Audio Issues: Is the music file in .ogg format? Is the path in new Audio(...) correct? Is audio/your_music.ogg listed in the manifest? Remember browser autoplay restrictions – music might only start after clicking the play button.
  • Progress Bar Not Updating:
    • Are you relying on the FiveM NUI events (window.addEventListener('message', ...)? Ensure this code is active (not commented out).
    • Are the event names (loadstatus, progress, loadFraction) correct? These can sometimes vary slightly between FiveM updates or specific game builds. Add console.log(JSON.stringify(event.data)) inside the message listener to see exactly what data FiveM is sending.
    • Is the element ID (progress-bar-inner) correct in both HTML and JS?
  • Default FiveM Loading Elements Still Visible:
    • Check client.lua: Is the script running (check for the print message in F8)? Is ShutdownLoadingScreenNui() being called? If using AddTextEntry, are the keys correct for your game build? Try increasing the initial Citizen.Wait().

Need a Premium Solution? Check Out QBCore Store LLC!

Building a loading screen from scratch is rewarding, but it can also be time-consuming, especially if you want advanced features and a highly polished design.

If you prefer a ready-to-go, professional solution, we’ve got you covered here at QBCore Store LLC.

We offer a curated selection of premium, feature-rich loading screens designed by experienced developers.

Benefits of QBCore Store LLC Paid Loading Screens:

  • Professional Designs: Visually stunning and modern aesthetics.
  • Advanced Features: Often include background videos, music players, multiple configurable sections, Discord integration previews, server status indicators, and more.
  • Easy Configuration: Typically come with simple configuration files to customize text, logos, links, and features without needing deep code changes.
  • Reliability & Support: Tested for compatibility and often come with developer support if you encounter issues.
  • Save Time & Effort: Get a high-quality result instantly, letting you focus on other aspects of your server.

Explore our range of interfaces and loading screens to find the perfect fit for your server’s identity:

Consider these popular options available on QBCore Store LLC:

  1. Modern Loading Screen V1: A sleek and clean option to get you started.
  2. Advanced Loading Screen V6: Packed with features for ultimate customization.
  3. Unique Loading Screen V13: Stand out with a distinctive design.
  4. Loading Screen V16: Another excellent choice with modern features.

Investing in a premium loading screen can significantly elevate your server’s perceived quality and player experience right from the first click.

Conclusion

Creating a Custom FiveM Loading Screen is a powerful way to enhance your server’s identity and provide a better user experience.

We’ve walked through setting up the HTML structure, styling it with CSS, adding dynamic behavior with JavaScript, and integrating it into FiveM using the fxmanifest.lua and a simple client.lua script to hide default elements.

Remember that the key is careful file management (listing everything in the manifest), understanding how NUI events work for real progress updates, and using web standards (HTML, CSS, JS).

Don’t be afraid to experiment with different styles, messages, and media.

Test thoroughly in your browser and in-game, using the developer consoles to debug issues.

Whether you build your own masterpiece following this guide or choose a polished premium option from QBCore Store LLC.com, investing in your loading screen is investing in your server’s first impression.

Happy coding, and we hope this helps you create an amazing entry point for your players!

Frequently Asked Questions (FAQ)

Q1: Can I use background videos instead of images?

A: Yes! Use the HTML <video> tag (<video autoplay muted loop id="bg-video"><source src="videos/your_video.mp4" type="video/mp4"></video>). Style it with CSS to cover the screen (position: absolute, width: 100%, height: 100%, object-fit: cover, z-index: -1). Remember to mute for autoplay to work reliably, optimize the video file size heavily, use compatible formats like MP4 (H.264), and list the video file in your fxmanifest.lua.

Q2: How do I make the progress bar show the actual FiveM loading progress?

A: The most reliable way is using the JavaScript window.addEventListener('message', ...) to listen for NUI messages sent by FiveM. Specifically, look for an event like progress which often contains a loadFraction property (a value from 0.0 to 1.0). Multiply this by 100 and pass it to your updateProgress JavaScript function. Avoid relying solely on simulated progress (like the setInterval example) for the final version.

Q3: Where exactly do I put the loading screen files on my server?

A: Create a dedicated folder for your resource (e.g., my-loading-screen) inside your server’s main resources directory. All files (index.html, style.css, script.js, fxmanifest.lua, client.lua, and subfolders like images, audio) should go inside this resource folder.

Q4: Can I have background music? How do I add controls?

A: Yes. Use the HTML <audio> tag or create an Audio object in JavaScript (new Audio('audio/music.ogg')). Crucially, use the .ogg audio format for best compatibility in FiveM NUI. Add standard HTML buttons (<button>) and potentially a range input (<input type="range">) for volume in your index.html. Use JavaScript event listeners (addEventListener) on these elements to control the audio object’s .play(), .pause(), and .volume properties. Remember to list the audio file in your manifest.

Q5: Why isn’t my loading screen showing up at all?

A: Double-check these common culprits:
1. Is the resource ensured correctly in server.cfg (ensure resource_folder_name)?
2. Is the fxmanifest.lua present in the resource folder?
3. Does the manifest have the loadscreen 'your_html_file.html' line?
4. Are all required files (HTML, CSS, JS, images, audio, fonts) listed accurately under files { ... } in the manifest? Check paths and filenames (case-sensitive!).
5. Are there any errors in the server console or client F8 console related to the resource failing to load?
6. Did you restart the server after adding the resource and ensuring it?

Q6: How can I make the loading screen fade out smoothly when the game starts?

A: This requires communication between your Lua script and the NUI page. FiveM doesn’t have a perfectly reliable “loading fully complete, about to spawn” event that’s easy to catch before the NUI is destroyed. However, you could:
1. Send a custom NUI message (SendNUIMessage({ type = 'loadingAlmostDone' })) from a client Lua script based on certain game events or timers just before spawn.
2. In your JavaScript, listen for this message (if (event.data.type === 'loadingAlmostDone')).
3. When received, trigger a CSS fade-out animation on your main container (.loading-container.fade-out { opacity: 0; transition: opacity 1s ease-out; } and add the fade-out class using JS). This gives a visual transition, though the NUI might still be abruptly removed by FiveM afterward.

Paid Loading Screens


Free Loading Screens


Done! Any questions? Leave a comment.

Leave a Reply

Your email address will not be published. Required fields are marked *