SERVER HUB · DOCUMENTATIONFrom zero to searchable server logs.
Step-by-step guides for FiveM logs, screenshots and phone media. Every example says which file it belongs in and what you need to replace.
Introduction
Server Hub keeps structured FiveM logs and private media in one searchable place. Each account starts with 10 GB of media storage.
What you can do
How it is organized
An account contains servers. Every server has its own token, datasets, logs and media. A token can write to one server and cannot read your dashboard.
Quickstart
Connect one server and send your first log in about five minutes.
- Open Server Hub, create a server, then click Connect your server and create a token.
- Download
ffhub.zip, extract it and put the ffhub folder inside your server's resources folder. - Paste the configuration below into
server.cfg and replace the example token. - Restart the server, then open the Logs tab. Connection and resource events should appear automatically.
set ffhub_token "ffh_REPLACE_THIS_WITH_YOUR_TOKEN"
set ffhub_endpoint "https://five-forge.com/hub-ingest"
ensure screencapture
ensure ffhub
Using screenshot-basic? Replace ensure screencapture with ensure screenshot-basic. You only need one capture resource.
Installation
The resource has no framework dependency and works with ESX, QBCore, Qbox or standalone servers.
Correct folder
resources/
└── ffhub/
├── fxmanifest.lua
├── client/main.lua
└── server/main.lua
Start order
The capture resource must start before ffhub. Resources that call ffhub exports should start after it.
ensure screencapture
ensure ffhub
ensure your_inventory
ensure your_phone
Update
Stop the server, replace the ffhub files, keep your server.cfg token, then restart. The retry queue survives a normal resource restart.
Configuration
Two server convars are required. Keep both in server.cfg.
set ffhub_token "ffh_YOUR_SERVER_TOKEN"
set ffhub_endpoint "https://five-forge.com/hub-ingest"
Token safety
Never put the token in client Lua, shared scripts, NUI JavaScript or GitHub. Create a separate token per server. Revoke and replace it immediately if it becomes public.
Logging: getting started
Call an ffhub export from any server-side Lua resource.
-- server.lua
exports.ffhub:Info('GENERAL', 'My first FiveForge log', {
playerSource = source,
action = 'test'
})
Restart your resource, trigger the code and open Server Hub → Logs → GENERAL.
Lua log exports
Use a short stable message and place values that change in metadata.
exports.ffhub:Log('BANK_HEIST', 'warn', 'Vault drill started', {
playerSource = source,
bank = 'Pacific',
alarm = true
})
exports.ffhub:Info('CHARACTER', 'Character selected', data)
exports.ffhub:Warn('ADMIN', 'Action rejected', data)
exports.ffhub:Error('DATABASE', 'Save failed', data)
Levels
debug, info, warn, error and fatal. Use warn for suspicious or rejected actions and error when an operation failed.
Player identifiers
Add playerSource or targetSource. ffhub attaches available non-IP identifiers before queueing the event.
Custom log datasets
The first argument becomes the tab name. You do not need to configure it in advance.
exports.ffhub:Info('INVENTORY', 'Item moved', metadata)
exports.ffhub:Info('CHARACTER', 'Character loaded', metadata)
exports.ffhub:Warn('BANK_HEIST', 'Alarm triggered', metadata)
The names INVENTORY, CHARACTER and BANK_HEIST appear as separate filters. Names accept letters, numbers, dots, underscores and hyphens, up to 48 characters.
Logging best practices
- Log server-side after the action is accepted.
- Keep the message stable: use “Item moved” and put item/count in metadata.
- Do not log passwords, API tokens, player IP addresses or full payment details.
- Use one dataset per system, not per player.
- Add a transaction or trace ID when several logs belong to one action.
exports.ffhub:Info('INVENTORY', 'Item moved', {
playerSource = source,
transactionId = transactionId,
item = itemName,
count = count,
from = fromSlot,
to = toSlot
})
Search and filters
Choose a server, time range, level, dataset or resource. Free-text search checks the message and serialized metadata.
Start broad with a dataset and time range. Add a player license, transaction ID or exact message when the result set is large.
Player screenshots
Supported providers: screencapture and screenshot-basic.
-- server.lua
exports.ffhub:TakePlayerScreenshot(source, {
reason = 'Admin review'
}, function(err, url, mediaId)
if err then
print('[reports] screenshot failed: ' .. err)
return
end
print('Screenshot ready: ' .. url)
end)
The callback fires after FiveForge verifies the uploaded file. The permanent record appears in Screenshots.
Phone photos
Use this server export when your phone or custom app can request a server-side photo.
exports.ffhub:TakePlayerPhoto(source, {
encoding = 'jpg', quality = 0.80,
maxWidth = 1280, maxHeight = 720
}, { app = 'phone-camera' }, function(err, url, mediaId)
if err then print(err) return end
savePhoto(url) -- replace with the phone's save function
end)
Phone URLs are stable and unguessable. The R2 bucket itself stays private.
Secure upload flow
- Your server asks FiveForge for a one-time media reservation.
- FiveForge returns an upload URL that expires in five minutes.
- The capture resource uploads directly with no account token exposed to the player.
- FiveForge checks the real file signature, size and media type before accepting it.
- Your callback receives the media ID and URL.
Why 415 happens: the declared image type did not match the file bytes. Update ffhub and use jpg, png or webp.
Media library
Screenshots and phone photos are grouped per server.
Open a file for a temporary private view link, download it, or delete it. Deleting removes both the database record and the R2 object. Storage usage includes ready files and active upload reservations.
LB Phone
LB Phone 1.5+ exposes upload providers, but its built-in “Fivemanage” choice calls Fivemanage's own API. A FiveForge token cannot be pasted into that slot.
Custom LB Phone app
Create a small server bridge and call it from your custom camera app:
-- server.lua in your bridge resource
RegisterNetEvent('my-lb-app:takePhoto', function()
local src = source
exports.ffhub:TakePlayerPhoto(src, {
encoding='jpg', quality=0.80,
maxWidth=1280, maxHeight=720
}, {app='lb-phone'}, function(err, url)
TriggerClientEvent('my-lb-app:photoReady', src, err, url)
end)
end)
Stock camera status: it needs a provider adapter for the exact installed LB Phone version because LB Phone owns the captured file pipeline. Do not select Fivemanage and insert a FiveForge token; that configuration will fail.
Quasar Smartphone
Use a server bridge wherever your Quasar version accepts a custom uploaded image URL.
exports.ffhub:TakePlayerPhoto(source, {encoding='jpg', quality=.8},
{app='quasar-smartphone'}, function(err, url)
if err then return print(err) end
-- Call the photo/message save function from your Quasar version with url.
end)
Function names differ between Smartphone and Smartphone Pro releases. Keep the FiveForge call server-side.
YSeries / YFlip / YPhone
Connect FiveForge in a bridge resource and pass the returned URL to the YSeries app callback that requested the photo.
exports.ffhub:TakePlayerPhoto(source, {encoding='jpg'},
{app='yseries'}, function(err, url)
if not err then saveYSeriesPhoto(url) end
end)
CodeM Phone
Use the generic photo export from server Lua, then store the URL through your CodeM version's media callback.
exports.ffhub:TakePlayerPhoto(source, {encoding='jpg'},
{app='codem-phone'}, function(err, url)
if err then return print(err) end
-- Send url to the CodeM callback here.
end)
nPhone
Call FiveForge from the server handler used by your custom nPhone app and return the URL to the requesting client.
exports.ffhub:TakePlayerPhoto(source, {encoding='jpg'},
{app='nphone'}, function(err, url)
TriggerClientEvent('my-nphone:uploadResult', source, err, url)
end)
JPR Phone
Use the FiveForge phone export in the server callback where JPR expects the final hosted image URL.
exports.ffhub:TakePlayerPhoto(source, {encoding='jpg'},
{app='jpr-phone'}, function(err, url)
if not err then saveJprPhoto(url) end
end)
Report scripts
0Bug Report, RX ReportsPRO and custom report resources can request evidence through the screenshot export.
RegisterNetEvent('my-reports:create', function(reportData)
local src = source
exports.ffhub:TakePlayerScreenshot(src, {
reportId = reportData.id,
reason = 'Player report'
}, function(err, url, mediaId)
if err then return print(err) end
saveReportEvidence(reportData.id, url, mediaId)
end)
end)
ShareX
Direct desktop ShareX upload is not enabled yet because project tokens never belong on a desktop client. Upload through the Server Hub dashboard or a trusted server-side adapter.
Planned: scoped desktop tokens and a ready-to-import .sxcu file.
Any custom phone
If the phone lets server Lua return an image URL, it can use FiveForge.
- Find the server callback that currently receives or saves the hosted URL.
- Call
exports.ffhub:TakePlayerPhoto with the player source. - Handle
err; never save an empty URL. - Pass
url into the phone's existing database or callback function.
If the phone only offers a named provider such as Discord or Fivemanage, a version-specific adapter is required.
API authentication
Use the server token as a Bearer token from trusted server code.
Authorization: Bearer ffh_YOUR_SERVER_TOKEN
Content-Type: application/json
Never call authenticated endpoints directly from NUI or client Lua.
Ingest logs
POST https://five-forge.com/hub-ingest/v1/logs
Authorization: Bearer ffh_YOUR_SERVER_TOKEN
Content-Type: application/json
X-FiveForge-Dataset: INVENTORY
[{"level":"info","message":"Item moved","resource":"inventory",
"metadata":{"item":"lockpick","count":2}}]
Send 1–100 events per request. The ffhub resource handles batching, retries and idempotency automatically.
Media reservations
ffhub wraps this flow for screencapture and screenshot-basic. Custom trusted servers can reserve an upload before sending file bytes.
POST https://five-forge.com/hub-ingest/v1/media/reservations
Authorization: Bearer ffh_YOUR_SERVER_TOKEN
Content-Type: application/json
{"filename":"evidence.jpg","contentType":"image/jpeg",
"maxBytes":8388608,"purpose":"log"}
The response contains id and a five-minute uploadUrl. Upload the file once using the exact declared media type.
Errors and troubleshooting
401 Unauthorized
The token is missing, revoked or belongs to another endpoint. Create a fresh server token.
402 or quota response
The account reached its current ingestion or storage allowance. Existing files and logs remain accessible.
415 file signature
The actual bytes are not the JPEG, PNG or WebP type declared during reservation. Update ffhub and use a supported encoding.
Screenshot timed out
Start screencapture or screenshot-basic before ffhub and confirm the player is connected.
No such export
Call exports.ffhub from server Lua and ensure ffhub starts before the calling resource.
Limits and security
- Log batches: 1–100 events, maximum 1 MiB.
- Dataset names: 1–48 letters, numbers, dots, underscores or hyphens.
- Media: validated JPEG, PNG and WebP, up to 12 MiB.
- Upload reservations expire after five minutes and can be used once.
- Tokens are displayed once and stored hashed.
- Player IP identifiers are intentionally excluded by ffhub.