> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/agentgateway/agentgateway/llms.txt
> Use this file to discover all available pages before exploring further.

# CEL expressions

> Use Common Expression Language (CEL) in Agentgateway for authorization policies, header transformations, rate limiting, and observability.

Agentgateway uses [CEL (Common Expression Language)](https://cel.dev/) throughout the request processing pipeline. CEL expressions give you fine-grained, programmable control over authorization, header transformations, rate limiting selectors, and log/trace fields — all evaluated at runtime without restarting the proxy.

## What is CEL?

CEL is a fast, safe expression language designed for evaluating user-defined conditions at runtime. Unlike Lua or WASM, CEL is sandboxed and cannot perform I/O or side effects — this makes it predictable, safe to use in a high-performance proxy, and straightforward to reason about.

A simple MCP authorization expression:

```cel theme={null}
jwt.sub == "test-user" && mcp.tool.name == "add"
```

## Where CEL is used

| Use case                             | Example                                           |
| ------------------------------------ | ------------------------------------------------- |
| Authorization policies               | `jwt.sub == "alice" && mcp.tool.name != "delete"` |
| Log and trace field extraction       | `request.headers["user-agent"]`                   |
| HTTP header and body transformations | `"Bearer " + jwt.sub`                             |
| Rate limit selectors                 | `source.address`                                  |
| Tracing sampling                     | `0.1` (10% random sampling)                       |

## How expressions are evaluated

Expressions are compiled at **configuration load time** (not per request). During compilation, Agentgateway extracts which context variables the expression references. At request time, only the referenced data is collected — you only pay for what you use.

```
Config load → CEL expression parsed → referenced variables extracted
                                              │
                                              ▼
Request arrives → ContextBuilder collects required fields → CEL evaluates expression
```

This is especially important for expensive fields:

* `request.body` — causes the body to be buffered in memory (expensive)
* `request.headers` — header map allocation (moderate cost)
* `llm.prompt` / `llm.completion` — large string copies for LLM traffic

<Warning>
  Accessing `request.body` or `response.body` in a CEL expression causes the full body to be buffered in memory before the expression is evaluated. Avoid this for large payloads unless necessary.
</Warning>

<Note>
  Variable extraction uses static analysis. It handles `request.body` correctly, but not dynamic access patterns like `request["body"]`. Stick to dot notation for reliable optimization.
</Note>

## Context reference

The following variables are available in CEL expressions. Availability depends on where in the pipeline the expression runs — for example, `response` is not available in request-phase transformations.

<AccordionGroup>
  <Accordion title="request — incoming HTTP request" icon="arrow-right-to-bracket" defaultOpen={true}>
    The `request` object contains attributes about the incoming HTTP request.

    | Field                  | Type      | Description                                  |
    | ---------------------- | --------- | -------------------------------------------- |
    | `request.method`       | string    | HTTP method, e.g. `GET`                      |
    | `request.uri`          | string    | Complete URI, e.g. `http://example.com/path` |
    | `request.host`         | string    | Hostname, e.g. `example.com`                 |
    | `request.scheme`       | string    | Scheme, e.g. `https`                         |
    | `request.path`         | string    | Path component, e.g. `/path`                 |
    | `request.pathAndQuery` | string    | Path and query, e.g. `/path?foo=bar`         |
    | `request.version`      | string    | HTTP version, e.g. `HTTP/1.1`                |
    | `request.headers`      | map       | Request headers (access by name)             |
    | `request.body`         | string    | Request body (buffers entire body)           |
    | `request.startTime`    | timestamp | Time the request started                     |
    | `request.endTime`      | timestamp | Time the request completed                   |

    **Examples:**

    ```cel theme={null}
    # Allow only GET and POST
    request.method in ["GET", "POST"]

    # Match a specific path prefix
    request.path.startsWith("/api/v2")

    # Check a custom header
    request.headers["x-tenant-id"] == "acme"

    # Use in a log field
    request.headers["user-agent"]
    ```
  </Accordion>

  <Accordion title="response — HTTP response" icon="arrow-right-from-bracket">
    The `response` object is available in response-phase expressions (e.g., response transformations, access logs).

    | Field              | Type   | Description                         |
    | ------------------ | ------ | ----------------------------------- |
    | `response.code`    | int    | HTTP status code                    |
    | `response.headers` | map    | Response headers                    |
    | `response.body`    | string | Response body (buffers entire body) |

    **Examples:**

    ```cel theme={null}
    # Log only error responses
    response.code >= 400

    # Check a response header
    response.headers["content-type"].contains("application/json")
    ```
  </Accordion>

  <Accordion title="jwt — JWT token claims" icon="key">
    The `jwt` object contains claims from a verified JWT token. It is only present when the JWT authentication policy is enabled and a valid token is provided.

    | Field         | Type   | Description                     |
    | ------------- | ------ | ------------------------------- |
    | `jwt.sub`     | string | Subject claim                   |
    | `jwt.iss`     | string | Issuer claim                    |
    | `jwt.aud`     | list   | Audience claim                  |
    | `jwt.<claim>` | any    | Any custom claim from the token |

    **Examples:**

    ```cel theme={null}
    # Allow a specific user
    jwt.sub == "alice@example.com"

    # Check a custom role claim
    jwt.roles.contains("admin")

    # Require a specific issuer
    jwt.iss == "https://auth.example.com"

    # Combine user and tool checks (MCP)
    jwt.sub == "test-user" && mcp.tool.name == "add"
    ```

    <Note>
      `jwt` is only available after the JWT policy has verified the token. If authentication fails, the request is rejected before authorization expressions run.
    </Note>
  </Accordion>

  <Accordion title="mcp — MCP request context" icon="plug">
    The `mcp` object contains attributes about an MCP request. It is available when the route handles MCP traffic.

    | Field                 | Type   | Description                             |
    | --------------------- | ------ | --------------------------------------- |
    | `mcp.tool.name`       | string | Name of the MCP tool being called       |
    | `mcp.tool.target`     | string | Target server for the tool call         |
    | `mcp.resource.name`   | string | Name of the MCP resource being accessed |
    | `mcp.resource.target` | string | Target server for the resource          |
    | `mcp.prompt.name`     | string | Name of the MCP prompt being requested  |
    | `mcp.prompt.target`   | string | Target server for the prompt            |

    **Examples:**

    ```cel theme={null}
    # Allow access to a specific tool only
    mcp.tool.name == "get_weather"

    # Block a dangerous tool
    mcp.tool.name != "delete_all"

    # Allow only reads (tools starting with "get" or "list")
    mcp.tool.name.startsWith("get") || mcp.tool.name.startsWith("list")

    # Restrict a user to specific tools
    jwt.sub == "readonly-bot" && mcp.tool.name in ["search", "get_item"]

    # Check which backend is serving the request
    mcp.tool.target == "production-server"
    ```
  </Accordion>

  <Accordion title="llm — LLM request and response" icon="brain">
    The `llm` object contains attributes about an LLM request or response. It is only present when using an `ai` backend.

    | Field                    | Type   | Description                              |
    | ------------------------ | ------ | ---------------------------------------- |
    | `llm.streaming`          | bool   | Whether the response is streamed         |
    | `llm.requestModel`       | string | Model requested by the client            |
    | `llm.responseModel`      | string | Model that served the response           |
    | `llm.provider`           | string | LLM provider name                        |
    | `llm.inputTokens`        | int    | Input/prompt token count                 |
    | `llm.outputTokens`       | int    | Output/completion token count            |
    | `llm.totalTokens`        | int    | Total token count                        |
    | `llm.cachedInputTokens`  | int    | Tokens read from cache (savings)         |
    | `llm.reasoningTokens`    | int    | Reasoning tokens in output               |
    | `llm.prompt`             | list   | Prompt messages (has performance impact) |
    | `llm.completion`         | string | Completion text (has performance impact) |
    | `llm.params.temperature` | float  | Temperature parameter                    |
    | `llm.params.max_tokens`  | int    | Max tokens parameter                     |

    **Examples:**

    ```cel theme={null}
    # Log total token usage
    llm.totalTokens

    # Alert on large requests
    llm.inputTokens > 10000

    # Rate limit by model
    llm.requestModel == "gpt-4"

    # Block non-streaming requests to a specific model
    llm.requestModel == "claude-3" && !llm.streaming
    ```
  </Accordion>

  <Accordion title="source — connection source" icon="network-wired">
    The `source` object contains attributes about the downstream connection.

    | Field                            | Type   | Description                             |
    | -------------------------------- | ------ | --------------------------------------- |
    | `source.address`                 | string | IP address of the downstream connection |
    | `source.port`                    | int    | Port of the downstream connection       |
    | `source.identity`                | object | SPIFFE identity (if mTLS is enabled)    |
    | `source.identity.trustDomain`    | string | SPIFFE trust domain                     |
    | `source.identity.namespace`      | string | Kubernetes namespace                    |
    | `source.identity.serviceAccount` | string | Kubernetes service account              |
    | `source.subjectAltNames`         | list   | SANs from the downstream certificate    |
    | `source.issuer`                  | string | Issuer from the downstream certificate  |
    | `source.subject`                 | string | Subject from the downstream certificate |

    **Examples:**

    ```cel theme={null}
    # Allow only from a specific IP range (use for rate limiting key)
    source.address

    # Require a specific Kubernetes service account (mTLS)
    source.identity.serviceAccount == "my-agent"

    # Restrict to a specific namespace
    source.identity.namespace == "production"
    ```
  </Accordion>

  <Accordion title="backend — upstream backend info" icon="server">
    The `backend` object contains information about the backend selected for the request.

    | Field              | Type   | Description                                               |
    | ------------------ | ------ | --------------------------------------------------------- |
    | `backend.name`     | string | Backend name, e.g. `my-service`                           |
    | `backend.type`     | string | Backend type: `ai`, `mcp`, `static`, `dynamic`, `service` |
    | `backend.protocol` | string | Backend protocol: `http`, `tcp`, `a2a`, `mcp`, `llm`      |

    **Examples:**

    ```cel theme={null}
    # Log the backend name (useful in access logs)
    backend.name

    # Different behavior based on backend type
    backend.type == "mcp"
    ```
  </Accordion>

  <Accordion title="apiKey and basicAuth — API key and basic auth" icon="lock">
    These objects are present when the corresponding authentication policy is enabled and credentials have been verified.

    | Field                | Type   | Description                |
    | -------------------- | ------ | -------------------------- |
    | `apiKey.key`         | string | The verified API key value |
    | `basicAuth.username` | string | The verified username      |

    **Examples:**

    ```cel theme={null}
    # Restrict to a specific API key holder
    apiKey.key == "internal-service-key"

    # Use the username in an authorization rule
    basicAuth.username == "admin"
    ```
  </Accordion>

  <Accordion title="env — environment variables" icon="leaf">
    The `env` object exposes a curated subset of well-known environment attributes. It does **not** expose raw process environment variables.

    | Field           | Type   | Description               |
    | --------------- | ------ | ------------------------- |
    | `env.podName`   | string | Kubernetes pod name       |
    | `env.namespace` | string | Kubernetes namespace      |
    | `env.gateway`   | string | Gateway name (Kubernetes) |

    **Examples:**

    ```cel theme={null}
    # Include pod name in logs
    env.podName

    # Namespace-aware routing logic
    env.namespace == "staging"
    ```
  </Accordion>
</AccordionGroup>

## Authorization policy examples

CEL expressions are most commonly used in authorization (`authz`) policies. Here are practical examples:

<CodeGroup>
  ```yaml JWT + MCP tool authorization theme={null}
  routes:
  - policies:
      auth:
        jwt:
          issuer: https://auth.example.com
      authz:
        rules:
        # Only allow verified users to call the "add" tool
        - expression: 'jwt.sub == "test-user" && mcp.tool.name == "add"'
    backends:
    - mcp:
        targets:
        - name: calculator
          stdio:
            cmd: npx
            args: ["mcp-server-calculator"]
  ```

  ```yaml Role-based MCP access theme={null}
  routes:
  - policies:
      auth:
        jwt:
          issuer: https://auth.example.com
      authz:
        rules:
        # Admins can call any tool; regular users get read-only tools
        - expression: |
            jwt.roles.contains("admin") ||
            mcp.tool.name.startsWith("get") ||
            mcp.tool.name.startsWith("list")
    backends:
    - mcp:
        targets:
        - name: my-server
          host: mcp-server:8080
  ```

  ```yaml mTLS service account authorization theme={null}
  routes:
  - policies:
      authz:
        rules:
        # Only allow requests from the "agent" service account in production
        - expression: |
            source.identity.serviceAccount == "agent" &&
            source.identity.namespace == "production"
    backends:
    - host: internal-service:8080
  ```

  ```yaml Token usage rate limiting theme={null}
  routes:
  - policies:
      rateLimit:
        # Rate limit per JWT subject
        key: 'jwt.sub'
        requests: 1000
        window: 60s
    backends:
    - mcp:
        targets:
        - name: ai-tools
          host: ai-tools:8080
  ```
</CodeGroup>

<CardGroup cols={2}>
  <Card title="Authorization guide" icon="shield" href="/guides/authorization">
    Complete guide to setting up CEL-based authorization policies
  </Card>

  <Card title="MCP proxy guide" icon="plug" href="/guides/mcp-proxy">
    Proxy MCP servers with tool-level access control
  </Card>
</CardGroup>
