SERVER HUB · DOCUMENTATION

From 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.

  1. Open Server Hub, create a server, then click Connect your server and create a token.
  2. Download ffhub.zip, extract it and put the ffhub folder inside your server's resources folder.
  3. Paste the configuration below into server.cfg and replace the example token.
  4. 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

  1. Your server asks FiveForge for a one-time media reservation.
  2. FiveForge returns an upload URL that expires in five minutes.
  3. The capture resource uploads directly with no account token exposed to the player.
  4. FiveForge checks the real file signature, size and media type before accepting it.
  5. 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.

  1. Find the server callback that currently receives or saves the hosted URL.
  2. Call exports.ffhub:TakePlayerPhoto with the player source.
  3. Handle err; never save an empty URL.
  4. 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.
FiveForge Server Hub · Need a version-specific integration? Send the script name and exact version in Discord.