Conditional expressions for plugins

Uses: Kong Gateway

Plugin conditions let you attach an optional condition expression to any plugin. When a request comes in, Kong Gateway evaluates the expression immediately before the plugin’s access phase. If the expression evaluates to true, the plugin runs normally. If it evaluates to false, the plugin is skipped for that request.

Conditions use Common Expression Language (CEL), a lightweight expression language.

Here are some common use cases for setting a condition on a plugin:

  • Skip a global plugin for specific Routes, hosts, or request paths without removing the plugin or duplicating it across individual Routes.
  • Enforce a plugin only for specific HTTP methods, headers, or query parameters.
  • Make one plugin’s execution depend on context set by a higher-priority plugin.
  • Condition plugin behavior on the authenticated Consumer, matched Route, or target Gateway Service.

How it works

When Kong Gateway receives a request, it matches the request to a Route and determines which plugins are in scope according to the plugin scoping rules. For each in-scope plugin that has a condition set, Kong Gateway evaluates the expression before that plugin’s access phase runs.

The following plugin contexts always execute, regardless of the condition: init_worker, configure, certificate, and rewrite. If the condition evaluates to false, the plugin’s access phase and all later phases are skipped. Because of this, values set during or after the response phase (for example, kong.ctx.shared values written in header_filter or body_filter) aren’t available to condition expressions.

Unlike plugin scopes, which are evaluated once at router time before any plugins run, conditions are evaluated per request, per plugin, immediately before each plugin executes. This means a higher-priority plugin can set values in kong.ctx.shared that a lower-priority plugin’s condition can then read.

If no condition is set, the plugin always executes.

Performance considerations

Plugin scopes are evaluated once at router time and are more efficient than conditions, which are evaluated per request for each conditioned plugin. Where possible, use plugin scopes to control plugin execution rather than conditions.

When conditions are necessary, keep the following in mind:

  • A plugin’s configuration is always loaded into memory, even if its condition evaluates to false.
  • Complex compound expressions with many fields are more expensive to evaluate than simple single-field expressions.
  • Conditions that reference kong.ctx.shared fields require a higher-priority plugin to set those values on every request, which adds its own overhead.

Limitations

Plugin conditions are only supported in the HTTP subsystem. They can’t be used with stream (TCP, TLS, UDP) Routes.

The following plugins do not support conditions:

  • Pre-Function
  • Post-Function
  • WebSocket Size Limit
  • WebSocket Validator

All other Kong Gateway plugins support conditions.

Condition expressions have a maximum length of 1024 characters.

Plugin conditions reference

This reference describes the CEL expression syntax and available fields for plugin conditions.

Expression syntax

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:

http.method == "GET"

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"

Combining predicates

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

Available fields

Plugin conditions support HTTP request fields, Kong Gateway context fields, and plugin state fields.

HTTP request 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

Gateway context fields

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.

Plugin state fields

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

Null handling

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)

Operators and functions

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.

Types

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

Handling default values

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)

Debugging

When Kong Gateway is running with debug logging enabled, a log line is emitted for each condition evaluation.

When a condition is not matched and the plugin is skipped:

plugin condition not matched for plugin 'request-termination' (ID: 66a1adbb-0179-49af-a065-4d0bc6c28cd6): skipped

When a condition is matched and the plugin executes:

plugin condition matched for plugin 'request-termination' (ID: 66a1adbb-0179-49af-a065-4d0bc6c28cd6)

If a condition expression fails at runtime, the error is logged at the ERROR level and Kong Gateway returns a 500 to the client:

error evaluating plugin condition for plugin 'request-termination' (ID: 66a1adbb-0179-49af-a065-4d0bc6c28cd6): No such key: foo

To prevent runtime errors, wrap your expression in default():

"condition": "default(kong.ctx.shared.my_flag == \"enabled\", false)"

Migration from 3.14 to 3.15

The plugin expression language changed between 3.14 and 3.15. In Kong Gateway 3.14, the feature was in beta and used ATC (Abstract Tree Classifier) syntax for plugin conditions. Kong Gateway 3.15 uses CEL (Common Expression Language), which isn’t backwards-compatible.

Any conditional expression that worked in 3.14 will need to be rewritten for 3.15. The main syntax changes are:

  • Prefix matching: ^=startsWith(). For example, http.path ^= "/api" becomes http.path.startsWith("/api").
  • Suffix matching: =^endsWith(). For example, http.path =^ ".json" becomes http.path.endsWith(".json").
  • Regex matching: ~matches(). For example, http.path ~ r#"^/api/v[0-9]+"# becomes http.path.matches("^/api/v[0-9]+").
  • The http.path.segments.<index> fields are replaced by http.path_segments (a list).
  • Header and query fields now return null when absent (instead of an empty string), so null checks may be needed.

For the 3.14 beta reference, see Conditional expressions for plugins in 3.14.

FAQs

Yes, conditions can be used on global plugins that are not scoped to a Route, Service, Consumer, or Consumer Group.

Routes with expression router conditions should be used instead of per-plugin conditions, since Route expressions are more performant than plugin conditions. This is because:

  • The init phase of plugins on excluded Routes won’t execute.
  • The plugin condition won’t need to be evaluated.

No, the condition expression language doesn’t support this explicitly. As an alternative, you can use a plugin such as Datakit or Pre-Function to parse the body, extract the required value, and store it in kong.ctx.shared. The plugin condition can then reference that kong.ctx.shared key.

If a condition expression fails at runtime, Kong Gateway logs the error at the ERROR level and returns a 500 status code to the client. To prevent this, wrap your expression in a default() call: default(<expression>, false). This returns false (and skips the plugin) instead of a 500 if the expression errors.

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!