CEL expressions

Agentgateway uses the CEL CEL (Common Expression Language) A simple expression language used throughout agentgateway to enable flexible configuration. CEL expressions can access request context, JWT claims, and other variables to make dynamic decisions. expression language throughout the project to enable flexibility. CEL allows writing simple expressions based on the request context that evaluate to some result.

CEL playground

You can try out CEL expressions directly in the built-in CEL playground in the agentgateway admin UI. The playground uses agentgateway’s actual CEL runtime, so custom functions and variables specific to agentgateway are available for testing.

To open the playground:

  1. Run agentgateway.

    agentgateway -f config.yaml
  2. Open the CEL playground.

  3. In the Expression box, enter the CEL expression that you want to test.

  4. In the Input Data (YAML) box, paste the YAML file structure that the CEL expression is running against.

  5. To test your CEL expression, click Evaluate.

Example expressions

Review the following examples of expressions for different uses cases.

Default function

You can use the default function to provide a fallback value if the expression cannot be resolved.

Request header fallback with example of an anonymous user:

default(request.headers["x-user-id"], "anonymous")

Nested object fallback with example of light theme:

default(json(request.body).user.preferences.theme, "light")

JWT JWT (JSON Web Token) A compact, URL-safe token format used for securely transmitting information between parties. JWTs are commonly used for authentication and authorization in agentgateway. claim fallback with example of default “user” role:

default(jwt.role, "user")

Logs, traces, and observability

# Include logs where there was no response or there was an error
filter: |
   !has(response) || response.code > 299
fields:
  add:
    user.agent: 'request.headers["user-agent"]'
    # A static string. Note the expression is a string, and it returns a string, hence the double quotes.
    span.name: '"openai.chat"'
    gen_ai.usage.prompt_tokens: 'llm.input_tokens'
    # Parse the JSON request body, and conditionally log...
    # * If `type` is sum, val1+val2
    # * Else, val3
    # Example JSON request: `{"type":"sum","val1":2,"val2":3,"val4":"hello"}`
    json.field: |
      json(request.body).with(b,
         b.type == "sum" ?
           b.val1 + b.val2 :
           b.val3
      )

Authorization

mcpAuthorization:
  rules:
  # Allow anyone to call 'echo'
  - 'mcp.tool.name == "echo"'
  # Only the test-user can call 'add'
  - 'jwt.sub == "test-user" && mcp.tool.name == "add"'
  # Any authenticated user with the claim `nested.key == value` can access 'printEnv'
  - 'mcp.tool.name == "printEnv" && jwt.nested.key == "value"'

Rate limiting

remoteRateLimit:
   descriptors:
   # Rate limit requests based on a header, whether the user is authenticated, and a static value (used to match a specific rate limit rule on the rate limit server)
   - entries:
      - key: some-static-value
        value: '"something"'
      - key: organization
        value: 'request.headers["x-organization"]'
      - key: authenticated
        value: 'has(jwt.sub)'

CEL in YAML

When writing CEL expressions in agentgateway, typically they are expressed as YAML string values, which can cause confusion. Any string literals within the expression need to be escaped with additional quotes.

These examples all represent the same CEL expression, a string literal "hello".

doubleQuote: "'hello'"
doubleQuoteEscaped: "\"hello\""
singleQuote: '"hello"'
blockSingle: |
  'hello'
blockDouble: |
  "hello"

These examples all represent the same CEL expression, a variable request:

doubleQuote: "request"
singleQuote: 'request'
block: |
  request

The block style is often the most readable for complex expressions, and also allows for multi-line expressions without needing to escape quotes.

Context reference

When using CEL expressions, a variety of variables and functions are made available.

Variables

Variables are only available when they exist in the current context. Previously in version 0.11 or earlier, variables like jwt were always present but could be null. Now, to check if a JWT claim exists, use the expression has(jwt.sub). This expression returns false if there is no JWT, rather than always returning true.

Additionally, fields are populated only if they are referenced in a CEL expression. This way, agentgateway avoids expensive buffering of request bodies if no CEL expression depends on the body.

Each policy execution consistently gets the current view of the request and response. For example, during logging, any manipulations from earlier policies (such as transformations or external processing) are observable in the CEL context.

Table of variables

FieldTypeDescription
requestobjectrequest contains attributes about the incoming HTTP request
request.methodstringThe HTTP method of the request. For example, GET
request.uristringThe complete URI of the request. For example, http://example.com/path.
request.hoststringThe hostname of the request. For example, example.com.
request.schemestringThe scheme of the request. For example, https.
request.pathstringThe path of the request URI. For example, /path.
request.pathAndQuerystringThe path and query of the request URI. For example, /path?foo=bar.
request.versionstringThe version of the request. For example, HTTP/1.1.
request.headersobjectThe headers of the request.
request.bodystringThe body of the request. Warning: accessing the body will cause the body to be buffered.
request.startTimestringThe time the request started
request.endTimestringThe time the request completed
responseobjectresponse contains attributes about the HTTP response
response.codeintegerThe HTTP status code of the response.
response.headersobjectThe headers of the response.
response.bodystringThe body of the response. Warning: accessing the body will cause the body to be buffered.
envobjectenv contains selected process environment attributes exposed to CEL.
This does NOT expose raw environment variables, but rather a subset of well-known variables.
env.podNamestringThe name of the pod (when running on Kubernetes)
env.namespacestringThe namespace of the pod (when running on Kubernetes)
env.gatewaystringThe Gateway we are running as (when running on Kubernetes)
jwtobjectjwt contains the claims from a verified JWT token. This is only present if the JWT policy is enabled.
apiKeyobjectapiKey contains the claims from a verified API Key. This is only present if the API Key policy is enabled.
apiKey.keystring
basicAuthobjectbasicAuth contains the claims from a verified basic authentication Key. This is only present if the Basic authentication policy is enabled.
basicAuth.usernamestring
llmobjectllm contains attributes about an LLM request or response. This is only present when using an ai backend.
llm.streamingbooleanWhether the LLM response is streamed.
llm.requestModelstringThe model requested for the LLM request. This may differ from the actual model used.
llm.responseModelstringThe model that actually served the LLM response.
llm.providerstringThe provider of the LLM.
llm.inputTokensintegerThe number of tokens in the input/prompt.
llm.cachedInputTokensintegerThe number of tokens in the input/prompt read from cache (savings)
llm.cacheCreationInputTokensintegerTokens written to cache (costs)
Not present with OpenAI
llm.outputTokensintegerThe number of tokens in the output/completion.
llm.reasoningTokensintegerThe number of reasoning tokens in the output/completion.
llm.totalTokensintegerThe total number of tokens for the request.
llm.countTokensintegerThe number of tokens in the request, when using the token counting endpoint
These are not counted as ‘input tokens’ since they do not consume input tokens.
llm.prompt[]objectThe prompt sent to the LLM. Warning: accessing this has some performance impacts for large prompts.
llm.prompt[].rolestring
llm.prompt[].contentstring
llm.completion[]stringThe completion from the LLM. Warning: accessing this has some performance impacts for large responses.
llm.paramsobjectThe parameters for the LLM request.
llm.params.temperaturenumber
llm.params.top_pnumber
llm.params.frequency_penaltynumber
llm.params.presence_penaltynumber
llm.params.seedinteger
llm.params.max_tokensinteger
llm.params.encoding_formatstring
llm.params.dimensionsinteger
llmRequestanyllmRequest contains the raw LLM request before processing. This is only present during LLM policies;
policies occurring after the LLM policy, such as logs, will not have this field present even for LLM requests.
sourceobjectsource contains attributes about the source of the request.
source.addressstringThe IP address of the downstream connection.
source.portintegerThe port of the downstream connection.
source.identityobjectThe (Istio SPIFFE) identity of the downstream connection, if available.
source.identity.trustDomainstringThe trust domain of the identity.
source.identity.namespacestringThe namespace of the identity.
source.identity.serviceAccountstringThe service account of the identity.
source.subjectAltNames[]stringThe subject alt names from the downstream certificate, if available.
source.issuerstringThe issuer from the downstream certificate, if available.
source.subjectstringThe subject from the downstream certificate, if available.
source.subjectCnstringThe CN of the subject from the downstream certificate, if available.
mcpobjectmcp contains attributes about the MCP request.
Request-time CEL only includes identity fields such as tool, prompt, or resource.
Post-request CEL may also include fields like methodName, sessionId, and tool payloads.
mcp.methodNamestring
mcp.sessionIdstring
mcp.toolobject
mcp.tool.targetstringThe target handling the tool call after multiplexing resolution.
mcp.tool.namestringThe resolved tool name sent to the upstream target.
mcp.tool.argumentsobjectThe JSON arguments passed to the tool call.
mcp.tool.resultanyThe terminal tool result payload, if available.
mcp.tool.erroranyThe terminal JSON-RPC error payload, if available.
mcp.promptobject
mcp.prompt.targetstringThe target of the resource
mcp.prompt.namestringThe name of the resource
mcp.resourceobject
mcp.resource.targetstringThe target of the resource
mcp.resource.namestringThe name of the resource
backendobjectbackend contains information about the backend being used.
backend.namestringThe name of the backend being used. For example, my-service or service/my-namespace/my-service:8080.
backend.typestringThe type of backend. For example, ai, mcp, static, dynamic, or service.
backend.protocolstringThe protocol of backend. For example, http, tcp, a2a, mcp, or llm.
extauthzobjectextauthz contains dynamic metadata from ext_authz filters
extprocobjectextproc contains dynamic metadata from ext_proc filters
metadataobjectmetadata contains values set by transformation metadata expressions.

Variables by policy type

Depending on the policy, different fields are accessible based on when in the request processing they are applied.

PolicyAvailable Variables
Transformationsource, request, jwt, extauthz
Remote Rate Limitsource, request, jwt
HTTP Authorizationsource, request, jwt
External Authorizationsource, request, jwt
MCP Authorizationsource, request, jwt, mcp
Loggingsource, request, jwt, mcp, extauthz, response, llm
Tracingsource, request, jwt, mcp, extauthz, response, llm
Metricssource, request, jwt, mcp, extauthz, response, llm

Functions

The following functions can be used in all policy types.

FunctionPurpose
jsonParse a string or bytes as JSON. Example: json(request.body).some_field.
toJsonConvert a CEL value into a JSON string. Example: toJson({"hello": "world"}).
unvalidatedJwtPayloadParse the payload section of a JWT without verifying the signature. This splits the token, base64url-decodes the middle segment, and parses it as JSON. Example: unvalidatedJwtPayload(request.headers.authorization.split(" ")[1]).sub
withCEL does not allow variable bindings. with allows doing this. Example: json(request.body).with(b, b.field_a + b.field_b)
variablesvariables exposes all of the variables available as a value. CEL otherwise does not allow accessing all variables without knowing them ahead of time. Warning: this automatically enables all fields to be captured.
mapValuesmapValues applies a function to all values in a map. map in CEL only applies to map keys.
filterKeysReturns a new map keeping only entries where the key matches the predicate (must evaluate to bool). Example: {"a":1,"b":2}.filterKeys(k, k == "a") results in {"a":1}. To remove keys, invert the predicate: m.filterKeys(k, !k.startsWith("x_")).
mergemerge joins two maps. Example: {"a":2,"k":"v"}.merge({"a":3}) results in {"a":3,"k":"v"}.
flattenUsable only for logging and tracing. flatten will flatten a list or struct into many fields. For example, defining headers: 'flatten(request.headers)' would log many keys like headers.user-agent: "curl", etc.
flattenRecursiveUsable only for logging and tracing. Like flatten but recursively flattens multiple levels.
base64.encodeEncodes a string to a base64 string. Example: base64.encode("hello").
base64.decodeDecodes a string in base64 format. Example: string(base64.decode("aGVsbG8K")). Warning: this returns bytes, not a String. Various parts of agentgateway will display bytes in base64 format, which may appear like the function does nothing if not converted to a string.
randomGenerates a number float from 0.0-1.0
defaultResolves to a default value if the expression cannot be resolved. For example default(request.headers["missing-header"], "fallback")
coalesceEvaluates expressions from left to right and returns the first one that resolves successfully to a non-null value. null values are skipped while searching, but if every expression is either null or an error and at least one expression resolved to null, the result is null. Unlike default, it swallows any error from earlier expressions, not just missing keys or undeclared references. Example: coalesce(request.headers["x-id"], json(request.body).id, "fallback")
regexReplaceReplace the string matching the regular expression. Example: "/id/1234/data".regexReplace("/id/[0-9]*/", "/id/{id}/") would result in the string /id/{id}/data.
failUnconditionally fail an expression.
uuidRandomly generate a UUIDv4

The following standard functions are available:

  • contains, size, has, map, filter, all, max, startsWith, endsWith, string, bytes, double, exists, exists_one, int, uint, matches.
  • Duration/time functions: duration, timestamp, getFullYear, getMonth, getDayOfYear, getDayOfMonth, getDate, getDayOfWeek, getHours, getMinutes, getSeconds, getMilliseconds.
  • From the strings extension: charAt, indexOf, join, lastIndexOf, lowerAscii, upperAscii, trim, replace, split, substring, stripPrefix, stripSuffix.
  • From the Kubernetes IP extension: isIP("..."), ip("..."), ip("...").family(), ip("...").isUnspecified(), ip("...").isLoopback(), ip("...").isLinkLocalMulticast(), ip("...").isLinkLocalUnicast(), ip("...").isGlobalUnicast().
  • From the Kubernetes CIDR extension: cidr("...").containsIP("..."), cidr("...").containsIP(ip("...")), cidr("...").containsCIDR(cidr("...")), cidr("...").ip(), cidr("...").masked(), cidr("...").prefixLength().
Agentgateway assistant

Ask me anything about agentgateway configuration, features, or usage.

Note: AI-generated content might contain errors; please verify and test all returned information.

Tip: one topic per conversation gives the best results. Use the + button in the chat header to start a new conversation.

Switching topics? Starting a new conversation improves accuracy.
↑↓ navigate select esc dismiss

What could be improved?

Your feedback helps us improve assistant answers and identify docs gaps we should fix.

Need more help? Join us on Discord: https://discord.gg/y9efgEmppm

Want to use your own agent? Add the Solo MCP server to query our docs directly. Get started here: https://search.solo.io/.