CORS

Configure Cross-Origin Resource Sharing for browser-based clients. Handles preflight OPTIONS requests automatically.

Configuration

{
  "name": "cors",
  "type": "cors",
  "cors": {
    "allowOrigins": [
      {"value": "https://app.example.com"},
      {"value": "https://.*.staging.example.com", "regex": true}
    ],
    "allowMethods": ["GET", "POST", "PUT", "DELETE"],
    "allowHeaders": ["Authorization", "Content-Type"],
    "exposeHeaders": ["X-Request-ID"],
    "maxAge": 3600,
    "allowCredentials": true
  }
}

All fields

FieldTypeDefaultDescription
allowOriginsarrayOrigins to allow (exact string or regex)
allowOrigins[].valuestringrequiredOrigin value or regex pattern
allowOrigins[].regexboolfalseWhether value is a regex
allowMethodsstring[]HTTP methods allowed in CORS requests
allowHeadersstring[]Request headers the browser can send
exposeHeadersstring[]Response headers the browser can access
maxAgenumber0Preflight cache duration in seconds
allowCredentialsboolfalseAllow cookies and auth headers

Examples

Single origin

{
  "name": "cors",
  "type": "cors",
  "cors": {
    "allowOrigins": [{"value": "https://app.example.com"}],
    "allowMethods": ["GET", "POST"],
    "allowHeaders": ["Content-Type"]
  }
}

Only https://app.example.com is allowed. Other origins get no CORS headers.

Multiple exact origins

{
  "cors": {
    "allowOrigins": [
      {"value": "https://app.example.com"},
      {"value": "https://admin.example.com"},
      {"value": "http://localhost:3000"}
    ]
  }
}

Regex origins (staging, preview deploys)

{
  "cors": {
    "allowOrigins": [
      {"value": "https://app.example.com"},
      {"value": "https://.*\\.staging\\.example\\.com", "regex": true},
      {"value": "https://deploy-preview-[0-9]+\\.netlify\\.app", "regex": true}
    ]
  }
}

Matches https://app.staging.example.com, https://api.staging.example.com, https://deploy-preview-42.netlify.app, etc.

Wildcard (allow any origin)

{
  "cors": {
    "allowOrigins": [{"value": "*"}],
    "allowMethods": ["GET"],
    "allowHeaders": ["Content-Type"]
  }
}

Allows any origin. Be careful: browsers reject * when allowCredentials: true.

Full API CORS (with credentials)

{
  "name": "api-cors",
  "type": "cors",
  "cors": {
    "allowOrigins": [
      {"value": "https://app.example.com"},
      {"value": "https://admin.example.com"}
    ],
    "allowMethods": ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
    "allowHeaders": ["Authorization", "Content-Type", "X-Request-ID", "X-Tenant"],
    "exposeHeaders": ["X-Request-ID", "X-RateLimit-Remaining"],
    "maxAge": 86400,
    "allowCredentials": true
  }
}

Public read-only API (no credentials)

{
  "name": "public-cors",
  "type": "cors",
  "cors": {
    "allowOrigins": [{"value": "*"}],
    "allowMethods": ["GET", "HEAD"],
    "allowHeaders": ["Content-Type", "Accept"],
    "maxAge": 3600
  }
}

Preflight handling

Browsers send an OPTIONS preflight request before making cross-origin requests with custom headers or methods. Vrata detects preflight requests (OPTIONS with Origin and Access-Control-Request-Method headers) and returns 204 No Content with the appropriate CORS headers. The request never reaches your upstream.

Response headers

Vrata adds these response headers based on your configuration:

HeaderSet when
Access-Control-Allow-OriginAlways (matching origin or *)
Access-Control-Allow-MethodsPreflight response
Access-Control-Allow-HeadersPreflight response
Access-Control-Expose-HeadersNormal response, if exposeHeaders is set
Access-Control-Max-AgePreflight response, if maxAge > 0
Access-Control-Allow-CredentialsIf allowCredentials: true
VaryOrigin (always added)