Skip to content
agentgateway has joined the Agentic AI Foundation — Learn more

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Variables and functions

Page as Markdown

How CEL variables are populated per policy phase, and where to find the full context reference and function list.

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.

For the full list of fields and types on every top-level object, see the CEL reference page. It is generated from the agentgateway CEL schema and is the source of truth for nested fields (for example, source.address or llm.inputTokens).

Note

The llm object carries both normalized and provider-reported token counts. llm.inputTokens and llm.totalTokens include the tokens read from or written to the prompt cache, so they mean the same thing for every provider. llm.providerInputTokens and llm.providerTotalTokens report what the provider sent. For guidance on which one to read, see Token usage fields.

Variables by policy type

Depending on the policy, different top-level variables are bound when CEL runs. A variable is only non-null when it is populated for the current request (for example, has(jwt.sub) or has(apiKey.key)). The same name can refer to different snapshots depending on pipeline stage: early policies evaluate against the live HTTP request, while logging, tracing, and metrics run after the exchange and can include response, mcp, and full telemetry fields. Note that when using streaming responses, the evaluation of response body attributes or LLM response information can be inconsistent.

PolicyAvailable top-level variables
Transformation (request)request, env, jwt, apiKey, basicAuth, llm, source, mcp, backend, extauthz, extproc, metadata — not response or llmRequest. 1
Transformation (response)Same as request-path, plus response for response-side rules. 2
Remote rate limitrequest, env, jwt, apiKey, basicAuth, llm, source, mcp, backend, extauthz, extproc, metadata
Local rate limit key (requests rule)request, env, jwt, apiKey, basicAuth, source, mcp, backend, extauthz, extproc, metadata — not llm, because the rule is checked before the LLM request is parsed. 3
Local rate limit key (tokens rule)Same as a requests rule, plus llm for fields such as llm.requestModel, because the rule is charged after the LLM request is parsed. 3
HTTP Authorizationrequest, env, jwt, apiKey, basicAuth, llm, source, mcp, backend, extauthz, extproc, metadata
Network authorizationenv, source 4
External Authorizationrequest, response, env, jwt, apiKey, basicAuth, llm, source, mcp, backend, extauthz, extproc, metadata — some expressions run after the authorization service returns and can read response. 5
MCP Authorizationrequest, env, jwt, apiKey, basicAuth, llm, source, mcp, backend, extauthz, extproc, metadata — mcp.methodName distinguishes methods such as tools/list and tools/call. For list methods, rules run once per listed item. mcp.sessionId and mcp.tool.arguments aren’t set.
External processing (ExtProc)Request-phase rules: same as Transformation (request). Response-phase rules: same as Transformation (response).
LLM policyrequest, env, jwt, apiKey, basicAuth, llm, llmRequest, source, backend, extauthz, extproc, metadata — llmRequest is the raw JSON body during LLM request handling (not mcp). 6
Loggingrequest, response, env, jwt, apiKey, basicAuth, llm, source, mcp, backend, extauthz, extproc, metadata 7
TracingSame as Logging.
MetricsSame as Logging.

When mcp is available

Policies can read request-time mcp fields only when all of the following are true:

  • The policy runs in the route phase, after route selection. Policies with phase: gateway run before route selection and never see mcp.
  • The selected backend is an MCP backend.
  • The request is an MCP JSON-RPC POST request. The mcp variable isn’t set for /sse, well-known OAuth metadata, or client registration requests.

At request time, mcp.methodName is always set, and mcp.sessionId is set when the client sends a session ID. The field for the method’s target depends on the method.

MethodTarget field
tools/callmcp.tool, including mcp.tool.arguments
prompts/getmcp.prompt
Resource reads and subscriptionsmcp.resource
Task methodsmcp.task
List methods, such as tools/listNone. List methods have no target, so mcp.tool isn’t set.

MCP authorization rules differ in two ways. mcp.sessionId and mcp.tool.arguments aren’t set. For list methods, the rules also run once for each listed item, and in each run the target field contains that item, such as mcp.tool for each tool in a tools/list response.

Response payload fields, such as mcp.tool.result and mcp.tool.error, are available only in logging, tracing, and metrics.

Functions

The following functions can be used in all policy types.

To define reusable functions from CEL expressions, see Custom functions.

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.
url.encodePercent-encodes a string for use as a URL component. Example: url.encode("hello world/?x=1") returns hello%20world%2F%3Fx%3D1.
url.decodePercent-decodes a URL-encoded string. Example: url.decode("hello%20world") returns hello world. This does not decode + as a space; use form.decode for application/x-www-form-urlencoded values.
form.decodeParses an application/x-www-form-urlencoded string or bytes value into a map. + is decoded as a space; repeated keys become lists. Example: form.decode("a=1&a=2") returns {"a":["1","2"]}.
form.encodeEncodes a map as an application/x-www-form-urlencoded string. Keys are encoded in sorted order, list values emit repeated fields, and null values are skipped. Example: form.encode({"scope":"openid profile"}) returns scope=openid+profile.
sha1.encodeComputes the SHA-1 digest of a string or bytes value and returns the lowercase hex string. Example: sha1.encode("hello").
sha256.encodeComputes the SHA-256 digest of a string or bytes value and returns the lowercase hex string. Example: sha256.encode("hello").
md5.encodeComputes the MD5 digest of a string or bytes value and returns the lowercase hex string. Example: md5.encode("hello").
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 math extension: math.least, math.greatest, math.ceil, math.floor, math.round, math.trunc, math.isInf, math.isNaN, math.isFinite, math.abs, math.sign, math.sqrt, math.bitAnd, math.bitOr, math.bitXor, math.bitNot, math.bitShiftLeft, math.bitShiftRight.
  • 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().

  1. Request-time transformation evaluation binds jwt, apiKey, basicAuth, llm, source, backend, extauthz, extproc, and metadata when earlier filters have populated them. mcp is populated only for MCP JSON-RPC requests to an MCP backend. ↩︎

  2. Response-side transformation sees the HTTP response object as well as the request snapshot fields. ↩︎

  3. A key that reads a variable that is not bound where its rule runs cannot be evaluated, so the request counts against the rule’s shared bucket instead. For more information, see Per-key limits. ↩︎ ↩︎

  4. Network (L4) authorization uses new_source only: no HTTP request object. ↩︎

  5. Some external authorization expressions run with only the client request; others run after the authorization service responds and can read the authorization HTTP response. ↩︎

  6. LLM route transforms bind llmRequest to the parsed JSON body and restore the other fields from the stored request snapshot when available. ↩︎

  7. For TCP logging, the executor is narrowed to env, source, and request timing fields (no full HTTP request/response objects). ↩︎

Was this page helpful?
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/.