# Showing a live player count on your loading screen

> Display how many players are online on a FiveM loading screen: the server’s /dynamic.json and /players.json endpoints, why browsers block direct requests (mixed content and CORS), an HTTPS endpoint with SetHttpHandler or a proxy, and the handover alternative.

Source: https://fam-tools.com/guides/fivem-loading-screen-live-player-count · FAM Tools · published 2026-09-11 · updated 2026-09-11 · about 12 min

**Short answer.** The FiveM server reports its player count at `/dynamic.json` (`clients` and `sv_maxclients`). A loading screen usually cannot fetch it directly: the page is served securely and the server endpoint is plain HTTP on port 30120, so the request is blocked as mixed content (and may lack CORS headers). Serve the count from an HTTPS URL you control — a reverse proxy, or a small resource using `SetHttpHandler` behind HTTPS — or send it once with `deferrals.handover`.

## Key points

- `/dynamic.json` → `clients`, `sv_maxclients`, `hostname`.
- Public `/players.json` is anonymised; real names need `sv_playersToken`.
- Browsers block `http://` requests from secure pages — use HTTPS.
- A tiny server resource with `SetHttpHandler` can answer with CORS headers.
- Handover is the zero-infrastructure fallback (a number at connect time).

“84 / 128 players online” on the loading screen is powerful social proof: it tells a new player the city is alive before they have seen it. Getting that number is easy on paper — the server publishes it — but browsers have rules about where a page may fetch data from, and a loading screen is a page. Here is how to do it so it actually works in game.

## What the server publishes

| Endpoint | Contains |
| --- | --- |
| `/dynamic.json` | `hostname`, `clients` (online now), `sv_maxclients`, game type and map |
| `/info.json` | Server variables, resources and version information |
| `/players.json` | One entry per player — anonymised unless the request carries `sv_playersToken` |

Current FiveM servers no longer expose players’ addresses on any of these endpoints (the old `sv_endpointPrivacy` setting was removed for that reason), and player names and identifiers appear on `/players.json` only when the request is authenticated with the server’s `sv_playersToken`. For a count, `/dynamic.json` is all you need.

## Why a direct fetch fails in game

1. **Mixed content**: modern (cerulean) loading screens and hosted ones run on secure origins. A request from a secure page to `http://1.2.3.4:30120/dynamic.json` is blocked by the browser engine.
2. **CORS**: even over HTTPS, the response must allow your page’s origin to read it.
3. **Exposure**: publishing your server’s raw IP inside a loading screen hands it to everyone who opens the file.

## Option 1: a small HTTP resource

Resources can answer HTTP requests at `http://your-server:30120/<resourceName>/…` with `SetHttpHandler`. Return just the numbers, with a CORS header, and put HTTPS in front of the server with a reverse proxy and a domain.

*server.lua (resource named "online")*

```lua
SetHttpHandler(function(req, res)
    if req.path == '/count' then
        res.writeHead(200, {
            ['Content-Type'] = 'application/json',
            ['Access-Control-Allow-Origin'] = '*',
            ['Cache-Control'] = 'max-age=10',
        })
        res.send(json.encode({
            online = #GetPlayers(),
            max = GetConvarInt('sv_maxclients', 48),
        }))
        return
    end
    res.writeHead(404)
    res.send('')
end)
```

*script.js (loading screen)*

```javascript
async function refreshCount() {
  try {
    const r = await fetch('https://play.mycity.gg/online/count', { cache: 'no-store' });
    const { online, max } = await r.json();
    document.getElementById('online').textContent = `${online} / ${max} online`;
  } catch { /* keep the last value */ }
}
refreshCount();
setInterval(refreshCount, 15000);
```

The HTTPS address in the example is your reverse proxy forwarding to the server’s port 30120. A short `Cache-Control` keeps a hundred loading screens from hammering the server.

## Option 2: handover (no infrastructure)

Send the count once while the player connects with `deferrals.handover({ online = #GetPlayers() })` and read `window.nuiHandoverData.online`. It is not live, but it is accurate at the moment they joined and needs nothing extra — see [handover data](https://fam-tools.com/guides/fivem-loading-screen-handover-data).

## Option 3: a hosted widget

Hosted loading screen builders can fetch the count on their own servers and show it as a widget, which avoids the mixed-content and CORS work entirely. Our builder’s player-count widget works this way.

## Questions

### How do I get the player count of a FiveM server?

The server’s `/dynamic.json` endpoint returns `clients` (players online) and `sv_maxclients`.

### Why does fetching dynamic.json fail from my loading screen?

The loading screen runs on a secure origin and the server endpoint is plain HTTP, so the browser blocks it as mixed content. Serve the count over HTTPS with CORS allowed.

### Can I show player names on the loading screen?

Public `/players.json` is anonymised. Names require the server’s `sv_playersToken`, which must never be put in client-side code — so do it through your own backend if at all.

### Is sv_endpointPrivacy still needed?

No. It has been removed; current servers no longer expose player addresses on HTTP endpoints.

## Related

- https://fam-tools.com/guides/fivem-loading-screen-handover-data
- https://fam-tools.com/guides/fivem-loading-screen-performance
- https://fam-tools.com/guides/fivem-server-connect-link-cfx-re
- https://fam-tools.com/guides/fivem-loading-screen-design-tips
