Conditions (skipWhen / onlyWhen)

Control when a middleware runs using CEL expressions. This avoids duplicating routes just to skip a middleware on certain paths, methods, or headers.

Three controls

ControlMeaningUse case
skipWhenSkip if any expression matchesSkip JWT on /health
onlyWhenRun only if at least one matchesRate limit only on /api
disabledCompletely disableTurn off middleware for one route

skipWhen and onlyWhen are mutually exclusive on the same override.

Where to use them

Conditions are set in middlewareOverrides on a route or group:

{
  "middlewareOverrides": {
    "<middleware-name>": {
      "skipWhen": ["<CEL expression>"],
      "onlyWhen": ["<CEL expression>"],
      "disabled": false
    }
  }
}

CEL variables

VariableTypeExample
request.methodstring"GET"
request.pathstring"/api/v1/users"
request.hoststring"api.example.com"
request.schemestring"https"
request.headersmap[string]stringrequest.headers["authorization"]
request.queryParamsmap[string]stringrequest.queryParams["token"]
request.clientIpstring"10.0.0.1"

CEL expressions are compiled once at routing table build time — no per-request compile cost.

Examples

skipWhen — skip JWT on health/ready

{
  "middlewareOverrides": {
    "jwt-auth": {
      "skipWhen": [
        "request.path == '/health'",
        "request.path == '/ready'"
      ]
    }
  }
}

JWT runs on all requests except /health and /ready. If any expression matches, the middleware is skipped.

skipWhen — skip rate limit on GET

{
  "middlewareOverrides": {
    "rate-limit": {
      "skipWhen": ["request.method == 'GET'"]
    }
  }
}

Rate limiting only applies to POST, PUT, DELETE, etc. GET requests are unlimited.

onlyWhen — rate limit only on /api

{
  "middlewareOverrides": {
    "rate-limit": {
      "onlyWhen": ["request.path.startsWith('/api')"]
    }
  }
}

Rate limiting only runs on /api/* paths. Static assets, health checks, etc. are not rate limited.

onlyWhen — CORS only for browser origins

{
  "middlewareOverrides": {
    "cors": {
      "onlyWhen": ["'origin' in request.headers"]
    }
  }
}

CORS middleware only runs when the request has an Origin header (browser requests). Server-to-server requests skip it.

disabled — turn off for one route

{
  "middlewareOverrides": {
    "cors": {"disabled": true}
  }
}

Completely disables CORS for this specific route, even if the group has it enabled.

Complex conditions

Skip auth for internal IPs:

{
  "middlewareOverrides": {
    "jwt-auth": {
      "skipWhen": ["request.clientIp.startsWith('10.0.')"]
    }
  }
}

Only run access logging for non-GET requests:

{
  "middlewareOverrides": {
    "access-log": {
      "onlyWhen": ["request.method != 'GET'"]
    }
  }
}

Skip external auth for a specific tenant:

{
  "middlewareOverrides": {
    "ext-auth": {
      "skipWhen": ["request.headers['x-tenant'] == 'internal'"]
    }
  }
}

Multiple expressions (OR logic)

{
  "skipWhen": [
    "request.path == '/health'",
    "request.path == '/ready'",
    "request.path == '/metrics'",
    "request.method == 'OPTIONS'"
  ]
}

Skip if the path is /health OR /ready OR /metrics OR the method is OPTIONS. Any match skips.

Precedence

Override fields beyond conditions

The middlewareOverrides map also supports middleware-specific overrides:

FieldMiddlewareDescription
headersheadersOverride the header manipulation config
extProcextProcOverride phases and error handling