Storage Setup
Configure object storage for uploads, direct browser PUTs, and CORS for Cloudflare R2 or AWS S3.
Why Storage Setup Matters
Testiment uploads bug report artifacts directly from the browser to object storage.
That includes:
- screenshots
- video recordings
- raw debugger payload artifacts
This keeps Testiment API requests small, but it means your storage bucket must be configured correctly for browser uploads.
If storage CORS is missing or wrong, bug report uploads fail before finalize completes.
Required Server Environment Variables
Set these in apps/server/.env:
STORAGE_BUCKET
STORAGE_ACCESS_KEY_ID
STORAGE_SECRET_ACCESS_KEY
STORAGE_REGION
STORAGE_ENDPOINT
STORAGE_ADDRESSING_STYLE
STORAGE_PUBLIC_URLRules:
STORAGE_BUCKETis always required.STORAGE_ACCESS_KEY_IDandSTORAGE_SECRET_ACCESS_KEYare always required.STORAGE_REGIONis required for standard AWS S3.STORAGE_ENDPOINTis recommended for R2 and custom S3-compatible providers.STORAGE_ADDRESSING_STYLEis optional and overrides how bucket URLs are generated.STORAGE_PUBLIC_URLis optional.
Use one of these patterns:
AWS S3
STORAGE_BUCKET=testiment-production
STORAGE_ACCESS_KEY_ID=your-access-key-id
STORAGE_SECRET_ACCESS_KEY=your-secret-access-key
STORAGE_REGION=us-east-1Cloudflare R2
STORAGE_BUCKET=testiment-production
STORAGE_ACCESS_KEY_ID=your-r2-access-key-id
STORAGE_SECRET_ACCESS_KEY=your-r2-secret-access-key
STORAGE_REGION=auto
STORAGE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.comOther S3-Compatible Storage
STORAGE_BUCKET=testiment-production
STORAGE_ACCESS_KEY_ID=your-access-key-id
STORAGE_SECRET_ACCESS_KEY=your-secret-access-key
STORAGE_REGION=us-east-1
STORAGE_ENDPOINT=https://minio.example.comTestiment will generate path-style presigned upload URLs for custom non-AWS endpoints, for example:
https://minio.example.com/testiment-production/organizations/...For AWS S3, prefer STORAGE_REGION without STORAGE_ENDPOINT unless you intentionally need a specific AWS endpoint hostname.
Optional Addressing Override
If your provider needs a specific bucket URL style, set:
STORAGE_ADDRESSING_STYLE=autoAllowed values:
auto: default behavior. AWS S3 endpoints use virtual-hosted-style URLs; R2 and other custom endpoints use path-style URLs.path: always usehttps://endpoint/bucket/key.virtual: always usehttps://bucket.endpoint/key.
Use path for providers that require bucket names in the URL path. Use virtual for providers that require bucket subdomains even on a custom endpoint.
Optional Public URL
If you serve files from a CDN or public bucket hostname and want stable public URLs instead of signed URLs:
STORAGE_PUBLIC_URL=https://cdn.example.com/bug-reportsBrowser Upload Model
Testiment uses this flow for bug report artifacts:
- Testiment API creates an upload session.
- The browser uploads capture and debugger artifacts directly to storage using presigned
PUTURLs. - Testiment API finalizes the report only after storage uploads succeed.
Because the browser talks directly to storage, CORS is mandatory.
Presigned URL addressing follows the storage endpoint:
- By default, AWS S3 uses virtual-hosted-style URLs.
- By default, Cloudflare R2 keeps the existing R2-compatible path-style behavior.
- By default, custom non-AWS S3-compatible endpoints use path-style URLs, which is the expected setup.
STORAGE_ADDRESSING_STYLEcan override this for any provider when needed.
CORS Requirements
Your storage bucket must allow browser requests from every origin that can submit bug reports.
At minimum, allow:
PUTfor direct uploadsGETandHEADfor direct object access and previewsOrigin,Content-Type, and other request headers used by the browser
Recommended self-host policy:
- Recommended default:
if you know exactly which sites may upload, keep the bucket CORS origins aligned with the capture public key
allowedOrigins - Optional broad SDK policy:
if you want the SDK to work on arbitrary customer websites, use
AllowedOrigins: ["*"]for the bucket and manage site-level restriction with capture public keyallowedOrigins - Expose
ETag - Bucket CORS controls whether the browser may talk to storage
- Capture public key
allowedOriginscontrols whether Testiment will authorize uploads for that site
If you are testing locally, include your local frontend origin too, for example:
http://localhost:3001
https://local.testiment.devCloudflare R2 CORS
In Cloudflare Dashboard:
- Open
R2. - Select your bucket.
- Open
Settings. - Add a
CORS policy.
Recommended policy for a known site:
[
{
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Keep AllowedOrigins aligned with the capture public key allowedOrigins for that same site.
Optional wildcard policy for arbitrary SDK install sites:
[
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Notes:
PUTis the critical method for bug report uploads.GETandHEADare recommended for media access and preview behavior.DELETEis not required for browser uploads.- The known-site policy above is the recommended default.
- Use the wildcard policy only when the SDK may run on arbitrary customer domains.
AWS S3 CORS
In AWS Console:
- Open
S3. - Select your bucket.
- Open
Permissions. - Edit
Cross-origin resource sharing (CORS).
Recommended policy for a known site:
[
{
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Keep AllowedOrigins aligned with the capture public key allowedOrigins for that same site.
Optional wildcard policy for arbitrary SDK install sites:
[
{
"AllowedOrigins": ["*"],
"AllowedMethods": ["GET", "HEAD", "PUT"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Verify Storage Before Go-Live
Before shipping, verify all of these:
STORAGE_BUCKETpoints to the correct bucketSTORAGE_REGIONorSTORAGE_ENDPOINTmatches the provider- access key and secret key belong to that bucket/account
- bucket CORS matches your deployment model
- if uploads are restricted to known sites, bucket CORS origins match the capture public key
allowedOrigins - if uploads are allowed from arbitrary sites, bucket CORS uses
AllowedOrigins: ["*"] - screenshot upload works
- video upload works
- uploaded report media is readable after finalize
Common Failure Mode
If bug report upload fails with a message similar to:
- direct upload to storage failed
- upload failed before the server responded
the most common cause is missing or incorrect bucket CORS.
Check:
- bucket CORS policy
- bucket CORS origins match the capture public key
allowedOrigins, or the bucket intentionally uses wildcard origin STORAGE_ENDPOINTpoints to the right provider endpoint- browser
PUTrequest response in DevTools