Agency Docs
QBESX

Agency-Pad Media Storage

Now with AgencyOS27: a free update for everyone who already owns the pad.

v27.1.1Paid7 Pages

Where photos, videos and voice messages are stored

Every photo, video clip, screen recording and voice message from Agency Pad is saved by the script itself, from version 27.1.1 on. You choose where:

StorageSetupGood for
Local (default)None. Works right after installing.Every server. Files stay on your own server, in your database.
Cloudflare R2About 10 minutes, once. Free up to 10 GB.Servers with a lot of video, or very many players.

Fivemanage and Discord are no longer used as storage. Discord webhooks are still available for logs.

Local storage (default)

There is nothing to set up. The script creates two tables in your database on its first start (agency_media and agency_media_parts) and stores the files there. You only need oxmysql, which the script needs anyway.

What this means for you:

  • No account, no API key, no extra resource. Nothing runs outside your server.
  • Updates are safe. The files are in the database, not in the resource folder, so replacing the folder on an update keeps every photo.
  • Your database backups include the media. The database grows by about a third more than the files themselves. A photo takes about 150 KB.
  • Agency Phone and Agency Pad share the storage. A photo taken with one of them shows up on the other, including photos synced with Agency Cloud.
  • The Agency Companion app shows them too, on iOS, Android and the web app. Nothing to set up for that either.

Speed

Files travel through the FiveM connection itself, without blocking the game. FiveM limits how fast a player can send data to the server (about 165 KB per second), so:

WhatSaving takes about
Photo1 second
Voice messageunder 1 second
15 second video clip20 seconds (the camera shows the progress in percent)

Showing a file to other players is fast, a whole clip arrives in a few seconds. If your server records a lot of video, Cloudflare R2 saves clips almost instantly.

Settings in config.lua

AgencyPadConfig.MediaStorage = {
    Provider = 'local',                -- 'local' or 'cloudflare'

    MaxFileMegabytes = 10,             -- largest single file (clips, recordings)
    TransferKilobytesPerSecond = 1000, -- 'local': how fast files are sent to players

    Local = {
        RetentionDays = 30,            -- delete files older than this (0 = keep forever)
        MaxStorageGigabytes = 10,      -- delete the oldest files above this (0 = no limit)
    },
}
SettingWhat it does
RetentionDaysFiles older than this are deleted automatically once an hour. A deleted photo shows a small placeholder in the gallery. Set 0 to keep everything.
MaxStorageGigabytesWhen all files together get bigger than this, the oldest are deleted first. Set 0 for no limit.
MaxFileMegabytesBigger files are refused. Also limits video clips and screen recordings.
TransferKilobytesPerSecondSpeed per player when files are sent to players. The game always has priority.

Cloudflare R2, step by step

Cloudflare R2 is free up to 10 GB of storage, 1 million uploads and 10 million downloads per month, and downloads never cost anything. Cloudflare asks for a payment method when you turn R2 on, even for the free plan. You only pay if you go above the free limits.

1. Turn on R2

  1. Log in at dash.cloudflare.com (a free account is enough).
  2. In the menu on the left, open Storage & databases and then R2 Object Storage.
  3. Add the R2 subscription. The overview shows $0.00 due now. Accept the terms and click Activate.

2. Create a bucket

  1. Click Create bucket.
  2. Name it, for example myserver-media. The name cannot be changed later.
  3. Leave location on Automatic and storage class on Standard.
  4. Click Create bucket.

3. Make the files viewable

  1. Open your bucket and go to Settings.
  2. Under Public Development URL, click Enable, type allow and confirm.
  3. Copy the address that appears, it looks like https://pub-1a2b3c….r2.dev. This is your PublicUrl.

Cloudflare limits the r2.dev address for very busy servers. If you have your own domain on Cloudflare, you can add it under Custom Domains in the same place and use that address as PublicUrl instead.

4. Allow uploads from the game

  1. Still in Settings, find CORS Policy and click Add.
  2. Delete everything in the text field, paste this and click Save:
[
  {
    "AllowedOrigins": ["https://cfx-nui-agency-phone", "https://cfx-nui-agency-pad"],
    "AllowedMethods": ["PUT", "GET"],
    "AllowedHeaders": ["*"],
    "MaxAgeSeconds": 3600
  }
]

Without this step the game cannot upload, and photos fail to save. If you renamed the resource folder, put your folder name after cfx-nui-.

5. Create a key

  1. Go back to R2 Object Storage and click Manage API tokens.
  2. Click Create API token (an account token is fine).
  3. Permissions: Object Read & Write.
  4. Under the bucket choice, pick only your bucket.
  5. Click Create. Cloudflare shows the Access Key ID and the Secret Access Key only once, copy both now.
  6. Your Account ID is shown on the R2 overview, and it is also the first part of the S3 address (https://<Account ID>.r2.cloudflarestorage.com).

6. Enter it in the script

The keys go into config_server.lua. This file is only read by the server and never sent to players. Do not put them into config.lua: that file is sent to every player.

-- config_server.lua
AgencyPadServerConfig.Cloudflare = {
    AccountId       = 'your account id',
    Bucket          = 'myserver-media',
    AccessKeyId     = 'your access key id',
    SecretAccessKey = 'your secret access key',
    PublicUrl       = 'https://pub-1a2b3c....r2.dev',
}

Then set the provider in config.lua and restart the resource:

AgencyPadConfig.MediaStorage = {
    Provider = 'cloudflare',
    ...
}

The server console now shows Media: storage: Cloudflare R2, bucket myserver-media. If something is missing, the console says what, and the script keeps saving locally until it is fixed, so the camera never stops working.

Using Agency Phone and Agency Pad together? Both can use the same bucket and the same key, each keeps its files in its own folder.

Check that it works

Run this in the server console (or as an admin in game), with the ID of a player who is online:

padmediatest 1

The player's Agency Pad saves a small test picture, loads it back the way any other player would, shows it, and plays a short voice message. The console answers:

Media test passed for player 1 in 1361 ms: upload ok, download ok, shown as picture ok, app markup and backgrounds ok, voice message plays ok

Add big to also test a 6 MB video clip: padmediatest 1 big.

Problems and fixes

You seeFix
could not create the media tables in the consoleoxmysql is not running or cannot reach the database. Start oxmysql before agency-pad.
Provider is 'cloudflare', but config_server.lua is missing ...One of the five values is empty, or PublicUrl does not start with https://. Check step 6.
Photos fail with Cloudflare HTTP 403The key has no write permission for this bucket, or the bucket name is wrong. Check step 5.
Photos fail with Failed to fetch on CloudflareThe CORS policy from step 4 is missing or has a different resource name.
Saved photos do not show on CloudflareThe Public Development URL from step 3 is off, or PublicUrl points to another bucket.
A video takes long to saveNormal with local storage, see Speed above. Use Cloudflare R2 for instant clips.
Old photos are goneRetentionDays deleted them. Raise it or set 0.

Coming from Fivemanage or Discord

Nothing to migrate. Photos saved before keep their old links and stay visible as long as that service still delivers them. Everything new goes to the storage you chose. The old settings MediaStorageProvider, FiveManageApiUrl, FiveManageApiKey and ScreenshotWebhook are ignored and can be deleted from your config.

Security

  • Only players connected to your server can upload. Every upload is announced first and checked for type and size.
  • The content is checked by its first bytes. A web page or a script can never be stored disguised as a photo.
  • File names are 128-bit random, they cannot be guessed or listed.
  • Uploads and downloads are limited per player.
  • With Cloudflare, every upload gets its own link that is valid for 5 minutes, for exactly one file of one type and one size. The secret key never leaves your server.

Still stuck after this page? Our support team takes it from here.