AWS S3
Amazon S3 gives you full control over vehicle photo storage, public access and CDN delivery. JG Used Vehicles creates a temporary S3 upload URL, then the in-game browser uploads each photo directly to S3.
You need four parts working together:
| Part | Why it matters |
|---|---|
| S3 bucket | Stores the vehicle photos |
| IAM access key | Signs upload and delete requests |
| CORS policy | Lets the in-game browser upload to the bucket |
| Public read access or CDN | Lets players load the saved photo URL |
The two URLs
The upload URL is temporary. JG Used Vehicles creates it on the server, then the in-game browser uses it once to upload a photo.
The public URL is saved with the photo. Players and the server use it to load the image later. It normally looks like one of these:
https://my-vehicle-images.s3.eu-west-2.amazonaws.com
https://cdn.example.comIf you use CloudFront or another CDN, put its domain in publicUrl.
Create an S3 bucket
- Open the AWS S3 console.
- Create a bucket, for example
my-vehicle-images. - Pick the AWS region closest to your server, for example
eu-west-2. - Save the bucket name and region.
Bucket names are globally unique, so choose a name that is available.
Create an IAM user and access key
Create an IAM access key that can upload, read and delete objects inside the used-vehicles/ prefix.
AWS documentation:
Use this policy as a starting point:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-vehicle-images/used-vehicles/*"
}
]
}Replace my-vehicle-images with your bucket name. Change used-vehicles/ if you use a different prefix.
Enable CORS for direct uploads
The in-game browser uploads directly to S3, so the bucket must allow browser PUT requests from jg-usedvehicles.
Open Permissions > Cross-origin resource sharing (CORS) on the bucket and use this as a starting point:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT"],
"AllowedOrigins": ["https://cfx-nui-jg-usedvehicles"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]If you renamed the resource, replace jg-usedvehicles in AllowedOrigins.
Make uploaded photos public
The final photo URL must load without AWS credentials.
For a public bucket, add a bucket policy like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadUsedVehiclePhotos",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-vehicle-images/used-vehicles/*"
}
]
}AWS may block this policy through the bucket's Block Public Access settings.
If you do not want a public bucket, put CloudFront or another CDN in front of it and use that domain for publicUrl.
AWS documentation:
Configure JG Used Vehicles
Set the provider in config/config.lua:
Config.ImageStorageProvider = "s3"Add the S3 settings in config/config.photos.lua:
Config.ImageStorageProviders.s3 = {
bucket = "my-vehicle-images",
region = "eu-west-2",
accessKeyId = "YOUR_AWS_ACCESS_KEY_ID",
secretAccessKey = "YOUR_AWS_SECRET_ACCESS_KEY",
sessionToken = "",
endpoint = "",
publicUrl = "https://my-vehicle-images.s3.eu-west-2.amazonaws.com",
prefix = "used-vehicles",
acl = nil,
forcePathStyle = false,
}If you use CloudFront, set:
publicUrl = "https://cdn.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 policy, Block Public Access settings, CDN or publicUrl if it does not load.
Config fields
| Field | Required | Description |
|---|---|---|
bucket | Yes | S3 bucket name. |
region | Yes | AWS region, for example "eu-west-2". |
accessKeyId | Yes | AWS access key ID. |
secretAccessKey | Yes | AWS secret access key. |
sessionToken | No | Temporary AWS session token when using temporary credentials. |
endpoint | No | Custom S3-compatible endpoint. Leave empty for AWS S3. |
publicUrl | No | Public URL or CDN domain. Standard S3 uses the bucket URL when this is empty. |
prefix | No | Folder-style prefix. Defaults to "used-vehicles" in the example config. |
acl | No | Canned ACL. Leave nil unless your S3 setup requires one. |
forcePathStyle | No | Enable only when a custom S3-compatible provider requires path-style URLs. |
Troubleshooting
Check the bucket region, endpoint, 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 that publicUrl can load uploaded objects without authentication. Also check the bucket policy, Block Public Access settings and CDN origin configuration.
Add s3:DeleteObject to the IAM policy for the configured bucket and prefix.
Last updated 26 August 2026