Testiment

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_URL

Rules:

  • STORAGE_BUCKET is always required.
  • STORAGE_ACCESS_KEY_ID and STORAGE_SECRET_ACCESS_KEY are always required.
  • STORAGE_REGION is required for standard AWS S3.
  • STORAGE_ENDPOINT is recommended for R2 and custom S3-compatible providers.
  • STORAGE_ADDRESSING_STYLE is optional and overrides how bucket URLs are generated.
  • STORAGE_PUBLIC_URL is 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-1

Cloudflare 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.com

Other 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.com

Testiment 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=auto

Allowed values:

  • auto: default behavior. AWS S3 endpoints use virtual-hosted-style URLs; R2 and other custom endpoints use path-style URLs.
  • path: always use https://endpoint/bucket/key.
  • virtual: always use https://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-reports

Browser Upload Model

Testiment uses this flow for bug report artifacts:

  1. Testiment API creates an upload session.
  2. The browser uploads capture and debugger artifacts directly to storage using presigned PUT URLs.
  3. 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_STYLE can 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:

  • PUT for direct uploads
  • GET and HEAD for direct object access and previews
  • Origin, 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 key allowedOrigins
  • Expose ETag
  • Bucket CORS controls whether the browser may talk to storage
  • Capture public key allowedOrigins controls 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.dev

Cloudflare R2 CORS

In Cloudflare Dashboard:

  1. Open R2.
  2. Select your bucket.
  3. Open Settings.
  4. 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:

  • PUT is the critical method for bug report uploads.
  • GET and HEAD are recommended for media access and preview behavior.
  • DELETE is 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:

  1. Open S3.
  2. Select your bucket.
  3. Open Permissions.
  4. 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_BUCKET points to the correct bucket
  • STORAGE_REGION or STORAGE_ENDPOINT matches 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_ENDPOINT points to the right provider endpoint
  • browser PUT request response in DevTools

On this page