This reference describes the CEL expression syntax and available fields for plugin conditions.
A condition expression is a string value assigned to the condition field of a plugin object.
A predicate is the basic unit of an expression and compares a field against a value:
This predicate has the following structure:
|
Object
|
Description
|
Example
|
|
Field
|
A value extracted from the incoming request or Kong Gateway context.
An absent field may return null or cause a runtime error depending on the field type. See Null handling.
|
http.method
|
|
Value
|
The value the field is compared against. Can be a constant (string, int, bool, null) or another field.
The value can appear on either side of the predicate.
|
"GET"
|
|
Operator
|
Defines the comparison to perform between the field and the value.
|
==
|
|
Predicate
|
Compares a field against a value using the given operator. Returns true if the comparison passes, false if it does not.
|
http.method == "GET"
|
Multiple predicates can be combined using logical operators:
|
Operator
|
Description
|
Example
|
&&
|
Logical AND — true if both sides are true.
|
http.method == "GET" && net.dst.port == 443
|
||
|
Logical OR — true if either side is true.
|
http.method == "GET" || http.method == "POST"
|
!
|
Logical NOT — inverts the result.
|
!(http.method == "DELETE")
|
()
|
Parentheses — control evaluation order.
|
(http.method == "GET" || http.method == "POST") && net.dst.port == 443
|
Plugin conditions support HTTP request fields, Kong Gateway context fields, and plugin state fields.
These fields reflect the state of the incoming HTTP request at the time the condition is evaluated.
Higher-priority plugins may have already modified these values (for example, by rewriting a header or query parameter) before the condition is evaluated.
|
Field
|
Type
|
Description
|
Example
|
http.method
|
string
|
The HTTP method of the incoming request, for example "GET" or "POST".
|
http.method == "POST"
|
http.host
|
string
|
The Host header of the incoming request.
|
http.host == "internal.example.com"
|
http.path
|
string
|
The normalized request path, without query parameters.
|
http.path.startsWith("/api/v2")
|
http.path_segments
|
list<string>
|
The path split on /, with empty segments excluded.
For example, /a/b/c yields ["a", "b", "c"].
Individual segments can be accessed by index: http.path_segments[0] returns "a".
Membership can be tested with in.
|
"admin" in http.path_segments
|
http.headers.<header_name>
|
string
|
The value of the specified request header.
Header names are always normalized to lowercase with underscores, so X-My-Header becomes http.headers.x_my_header.
Returns the first value if the header has multiple values.
Returns null if the header is absent.
|
http.headers.x_version == "2"
|
http.queries.<param_name>
|
string
|
The value of the specified query parameter.
Returns the first value if the parameter appears multiple times.
Returns null if the parameter is absent.
|
http.queries.auth == "required"
|
http.headers_list.<header_name>
|
list<string>
|
All values of the specified request header as a list. Returns null if the header is absent.
|
http.headers_list.x_roles != null && "editor" in http.headers_list.x_roles
|
http.queries_list.<param_name>
|
list<string>
|
All values of the specified query parameter as a list. Returns null if the parameter is absent.
|
http.queries_list.tag != null && "featured" in http.queries_list.tag
|
net.protocol
|
string
|
The protocol of the Route, for example "http" or "https".
|
net.protocol == "https"
|
net.tls.sni
|
string
|
The server name from the TLS ClientHello packet, if the connection is over TLS. Returns null for non-TLS connections.
|
net.tls.sni == "api.example.com"
|
net.src.ip
|
string
|
The IP address of the client.
|
net.src.ip == "10.0.0.1"
|
net.src.port
|
int
|
The port used by the client to connect.
|
net.src.port > 1024
|
net.dst.ip
|
string
|
The listening IP address where Kong Gateway accepted the connection.
|
net.dst.ip == "192.168.1.1"
|
net.dst.port
|
int
|
The listening port where Kong Gateway accepted the connection.
|
net.dst.port == 443
|
The following fields are populated during plugin execution and reflect the Gateway context at the time the condition is evaluated.
|
Field
|
Type
|
Description
|
Example
|
route.id
|
string
|
The UUID of the matched Route. null for global plugins or Service-scoped plugins when no Route is matched.
|
route.id == "a1b2c3d4-..."
|
route.name
|
string
|
The name of the matched Route. null when no Route is matched.
|
route.name == "payments-route"
|
route.tags
|
list<string>
|
Tags assigned to the matched Route. null if no Route is matched or the Route has no tags.
|
route.tags != null && "internal" in route.tags
|
service.id
|
string
|
The UUID of the matched Service. null for global plugins when no Service is matched.
|
service.id == "a1b2c3d4-..."
|
service.name
|
string
|
The name of the matched Service. null when no Service is matched.
|
service.name == "payments-service"
|
service.tags
|
list<string>
|
Tags assigned to the matched Service. null if no Service is matched or the Service has no tags.
|
service.tags != null && "internal" in service.tags
|
consumer.id
|
string
|
The UUID of the authenticated Consumer, if one has been identified by a higher-priority plugin. null if no Consumer is matched.
|
consumer.id == "a1b2c3d4-..."
|
consumer.username
|
string
|
The username of the authenticated Consumer. null if no Consumer is matched or the Consumer has no username.
|
consumer.username == "alice"
|
consumer.custom_id
|
string
|
The custom ID of the authenticated Consumer. null if no Consumer is matched or the Consumer has no custom ID.
|
consumer.custom_id == "user-123"
|
consumer.tags
|
list<string>
|
Tags assigned to the authenticated Consumer. null if no Consumer is matched or the Consumer has no tags.
|
consumer.tags != null && "vip" in consumer.tags
|
consumer_group.names
|
list<string>
|
Names of Consumer Groups matched for this request. null if no Consumer Groups are matched.
|
consumer_group.names != null && "premium" in consumer_group.names
|
consumer_group.ids
|
list<string>
|
UUIDs of Consumer Groups matched for this request. null if no Consumer Groups are matched.
|
consumer_group.ids != null && "a1b2c3d4-..." in consumer_group.ids
|
kong.ctx.shared
|
Map
|
Values from the kong.ctx.shared table, set by higher-priority plugins during the access phase.
Inner keys must be checked with has() before access, as they are not pre-populated.
See Null handling.
|
has(kong.ctx.shared.my_flag) && kong.ctx.shared.my_flag == "enabled"
|
principal.id
|
string
|
The UUID of the authenticated Principal. null if no Principal is authenticated.
|
principal.id == "a1b2c3d4-..."
|
principal.display_name
|
string
|
The display name of the authenticated Principal. null if no Principal is authenticated.
|
principal.display_name == "alice"
|
principal.metadata
|
Map
|
Attributes of the authenticated Principal’s metadata. null if no Principal is authenticated.
Inner keys must be checked with has() before access.
See Null handling.
|
has(principal.metadata.department) && principal.metadata.department == "finance"
|
consumer.* and consumer_group.* fields are only populated after an authentication plugin (such as Key Auth or Basic Auth) has run.
Conditions referencing these fields must be on a plugin with a lower priority than the authentication plugin.
The following fields reflect the configured state of other plugins in the same Gateway workspace or control plane at the time the condition is evaluated.
|
Field
|
Type
|
Description
|
Example
|
plugins.<plugin_name>.is_matched
|
boolean
|
-
true if the plugin is configured and its Route or Service scope matches this request (Consumer scope excluded).
-
false if the plugin is configured but its scope does not match.
-
null if the plugin isn’t configured.
|
plugins.key_auth.is_matched == true
|
plugins.<plugin_name>.priority
|
int
|
The priority of the matched plugin. null if the plugin isn’t matched.
|
plugins.key_auth.priority > 1000
|
plugins.<plugin_name>.access_has_executed
|
boolean
|
-
true if the plugin’s access phase has already executed at the time this condition is evaluated.
-
false if the plugin is matched but its access phase has not run yet.
-
null if the plugin isn’t configured.
|
plugins.key_auth.access_has_executed == true
|
How null values behave depends on the field type.
The following fields return null when a value isn’t set:
http.headers.*
http.queries.*
http.headers_list.*
http.queries_list.*
net.tls.sni
- All context fields (
consumer.*, route.*, service.*, principal.*, consumer_group.*, plugins.*)
You can compare these directly using null:
consumer.id != null
route.tags != null && "internal" in route.tags
The kong.ctx.shared and principal.metadata fields are maps whose inner keys are not pre-populated by Kong Gateway.
Accessing a missing key in these maps causes a runtime error (500).
Before accessing a key, use has() to check for its existence:
has(kong.ctx.shared.my_flag) && kong.ctx.shared.my_flag == "enabled"
Alternatively, wrap the entire expression in default() to return a safe fallback if the key is missing:
default(kong.ctx.shared.my_flag == "enabled", false)
The following operators and functions are supported in plugin condition expressions:
|
Operator or function
|
Description
|
&&, ||, !
|
Logical AND, OR, NOT.
|
()
|
Group expressions to control evaluation order.
|
==, !=, <, <=, >, >=
|
Standard value comparison.
|
+
|
String concatenation.
|
in
|
Tests whether a value is a member of a list.
|
contains()
|
Returns true if the string contains the given substring.
|
startsWith()
|
Returns true if the string starts with the given prefix.
|
endsWith()
|
Returns true if the string ends with the given suffix.
|
matches()
|
Tests the string against a RE2 regular expression.
Matches any substring unless anchored with ^ and $.
|
size()
|
Returns the number of elements in a list, or the number of characters in a string.
|
has()
|
Returns true if the key exists in the map. Required before accessing inner keys of kong.ctx.shared or principal.metadata.
|
all()
|
Returns true if all elements in the list satisfy the predicate.
|
exists(), map(), filter()
|
Additional CEL comprehension macros for working with lists.
|
default()
|
Wraps an expression to return a fallback boolean value if the expression produces a runtime error.
Wraps an expression to return a fallback boolean value if the expression produces a runtime error.
Must wrap the entire expression, and cannot be used inline within a larger expression.
The second argument for this function accepts only boolean values (true or false).
|
Regular expressions follow the rules of Rust Crate regex.
Regular expression matches succeed if they match a substring of the argument.
Use explicit anchors (^ and $) in the pattern to force full-string matching, if desired.
For example, http.path.matches("^/api/v[0-9]+$") matches /api/v1 but not /other/api/v1.
Plugins support the following CEL types:
|
Type
|
Description
|
Example literal
|
bool
|
Boolean value.
|
true, false
|
int
|
64-bit signed integer.
|
42, -1
|
string
|
UTF-8 string.
|
"hello"
|
list<string>
|
Ordered list of string values.
|
["foo", "bar"]
|
map<string, _type>
|
Map with string keys and values of any type. Used for kong.ctx.shared and principal.metadata.
|
See kong.ctx.shared field.
|
null
|
Null value, returned when an optional field has no value.
|
null
|
default() makes condition expressions safe at runtime.
If the expression raises an evaluation error (for example, accessing a missing key in a map), default() returns the fallback value instead of causing a 500 error.
default() must wrap the entire expression. It can’t appear inline within a larger expression:
# Valid — wraps the entire expression
default(kong.ctx.shared.my_flag == "enabled", false)
# Not valid — default() can't be used inline
kong.ctx.shared.my_flag == "enabled" && default(principal.id == "abc", false)
Use default() when your expression references fields that might not be set for every request, such as kong.ctx.shared.* or principal.metadata.*:
default(principal.metadata["Department"] == "finance", false)