JG Docs
Vehicle Studio

Cloudflare R2

Cloudflare R2 is a good hosted option if you want S3-compatible storage without setting up AWS (as well as a pretty awesome free tier).

Vehicle Studio signs a short-lived R2 PUT URL, then the NUI uploads the image directly to R2.

You'll need:

  • An R2 bucket.
  • An R2 access key ID.
  • An R2 secret access key.
  • Your Cloudflare account ID.
  • A public URL for reading uploaded files.
  • CORS enabled for browser PUT uploads.

Create An R2 Bucket

  1. Open the Cloudflare dashboard.
  2. Go to R2 Object Storage.
  3. Create a bucket, for example vehicle-studio.
  4. Save the bucket name.

Create R2 API Tokens

  1. In the Cloudflare dashboard, open R2 Object Storage.
  2. Go to Manage R2 API Tokens.
  3. Create an API token with object read and write access for the bucket.
  4. Copy the access key ID and secret access key.

Cloudflare documents this flow here: R2 API tokens.

Make The Bucket Public

Uploaded images must be reachable by the browser and by other scripts that use the saved image URL.

You can use either:

  • An R2 public bucket URL.
  • A custom domain connected to the R2 bucket.

Cloudflare's public bucket and custom domain options are documented here: Public buckets.

Enable CORS For Direct Uploads

Because the NUI uploads directly to R2, the bucket must allow browser PUT requests from your resource.

Use this as a starting point:

[
  {
    "AllowedOrigins": ["https://cfx-nui-jg-vehiclestudio"],
    "AllowedMethods": ["PUT"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

For quick testing, you can temporarily use "*" as the allowed origin, then tighten it afterwards.

Configure Vehicle Studio

In config/config.lua:

Config.ImageStorageProvider = "r2"

In config/config.upload.lua:

Config.ImageStorageProviders = Config.ImageStorageProviders or {}

Config.ImageStorageProviders.r2 = {
  accountId = "YOUR_CLOUDFLARE_ACCOUNT_ID",
  bucket = "vehicle-studio",
  accessKeyId = "YOUR_R2_ACCESS_KEY_ID",
  secretAccessKey = "YOUR_R2_SECRET_ACCESS_KEY",
  publicUrl = "https://pub-xxxxxxxxxxxxxxxx.r2.dev",
  prefix = "vehicle-studio",
  region = "auto",
  forcePathStyle = true,
  acl = nil,
  presignExpires = 900
}

Use your custom domain in publicUrl if you configured one (Cloudflare recommends this):

publicUrl = "https://images.example.com"

Cloudflare notes that presigned uploads use the R2 S3 API domain, not a custom domain. publicUrl is still the final public URL saved after the upload.

Config Fields

FieldRequiredDescription
accountIdYesCloudflare account ID.
bucketYesR2 bucket name.
accessKeyIdYesR2 access key ID.
secretAccessKeyYesR2 secret access key.
publicUrlYesPublic read URL used to build the final saved image URL.
prefixNoFolder-style prefix for uploaded images, for example "vehicle-studio".
regionNoR2 uses "auto".
forcePathStyleNoRequired for R2-compatible signing. Defaults should normally stay enabled.
aclNoOptional canned ACL. Leave nil unless you know the provider requires it.
presignExpiresNoPresigned upload URL lifetime in seconds. Defaults to 900.

Troubleshooting

On this page