Cloudflare R2
Cloudflare R2 provides S3-compatible object storage. JG Used Vehicles creates a temporary R2 upload URL, then the in-game browser uploads each photo directly to R2.
You need:
- An R2 bucket.
- An R2 access key ID and secret access key.
- Your Cloudflare account ID.
- A public URL for reading uploaded photos.
- CORS enabled for browser PUT uploads.
Create an R2 bucket
- Open the Cloudflare dashboard.
- Open R2 Object Storage.
- Create a bucket, for example
used-vehicles. - Save the bucket name.
Create an R2 API token
- Open R2 Object Storage in the Cloudflare dashboard.
- Open Manage R2 API Tokens.
- Create an API token with object read and write access for the bucket.
- Copy the access key ID and secret access key.
Cloudflare documents this at R2 API tokens.
Make the bucket public
Vehicle photos must be reachable by players and by the server after upload. Connect either an R2 public development URL or a custom domain to the bucket.
Cloudflare documents both options at Public buckets.
Enable CORS for direct uploads
The in-game browser uploads directly to R2, so the bucket must allow browser PUT requests from jg-usedvehicles.
Use this as a starting point:
[
{
"AllowedOrigins": ["https://cfx-nui-jg-usedvehicles"],
"AllowedMethods": ["PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]If you renamed the resource, replace jg-usedvehicles in AllowedOrigins.
Configure JG Used Vehicles
Set the provider in config/config.lua:
Config.ImageStorageProvider = "r2"Add the R2 settings in config/config.photos.lua:
Config.ImageStorageProviders.r2 = {
accountId = "YOUR_CLOUDFLARE_ACCOUNT_ID",
bucket = "used-vehicles",
accessKeyId = "YOUR_R2_ACCESS_KEY_ID",
secretAccessKey = "YOUR_R2_SECRET_ACCESS_KEY",
endpoint = "",
publicUrl = "https://pub-xxxxxxxxxxxxxxxx.r2.dev",
prefix = "used-vehicles",
region = "auto",
forcePathStyle = true,
}Use your custom domain in publicUrl if you configured one:
publicUrl = "https://images.example.com"Do not include a trailing slash in publicUrl.
Test the final URL
Restart jg-usedvehicles and open the vehicle camera. Take one photo after the provider check passes, then confirm the photo appears in the photo library.
If the photo uploads but does not display, copy its URL and open it in a browser. Check the bucket's public access or custom domain configuration if the URL does not load.
Config fields
| Field | Required | Description |
|---|---|---|
accountId | Yes | Cloudflare account ID. |
bucket | Yes | R2 bucket name. |
accessKeyId | Yes | R2 access key ID. |
secretAccessKey | Yes | R2 secret access key. |
endpoint | No | Optional custom S3 API endpoint. Leave empty for standard R2. |
publicUrl | Yes | Public URL used to load uploaded photos. |
prefix | No | Folder-style prefix. Defaults to "used-vehicles" in the example config. |
region | No | R2 uses "auto". |
forcePathStyle | No | Keep this enabled for R2. |
Troubleshooting
Check the Cloudflare account ID, bucket name, access key, secret access key and system clock on the server.
Check that the bucket CORS policy allows PUT requests from https://cfx-nui-jg-usedvehicles. Change the origin if you renamed the resource.
Check publicUrl. It must load uploaded objects without authentication. Also check the bucket's public access or custom domain configuration.
Check that the R2 API token has object write access for the bucket. JG Used Vehicles uses the same credentials to sign delete requests.
Last updated 26 August 2026