Event Gateway expressions language

Related Documentation
Incompatible with
on-prem

In Kong Event Gateway, you can use expressions in a policy’s condition field to determine whether the policy should execute. Some policies also accept expressions in other fields, to compute a dynamic value such as a list of fields to encrypt.

Event Gateway expressions are written in Common Expression Language (CEL), wrapped in double curly braces. For example, this condition selects all topics that end with the suffix my_suffix:

{"condition": "{{context.topic.name.endsWith(\"my_suffix\")}}"}

Conditions must be between 1 and 1000 characters long.

Legacy syntax: Expressions without the double curly brace wrapper are parsed as Event Gateway’s older JavaScript-subset syntax, which is still supported but will be deprecated in a future update. See Legacy JavaScript-style syntax.

Expression syntax

A predicate is the basic unit of an expression and compares a field against a value:

context.topic.name == "my_topic"

This predicate has the following structure:

Object

Description

Example

Field A value extracted from the connection, request, or record being evaluated. context.topic.name
Value The value the field is compared against. Can be a constant (string, int, bool) or another field. "my_topic"
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. context.topic.name == "my_topic"

The context. prefix is optional. topic.name == "my_topic" and context.topic.name == "my_topic" are equivalent.

Combining predicates

Multiple predicates can be combined using logical operators:

Operator

Description

Example

&& Logical AND. True if both sides are true. context.topic.name == "a" && context.auth.type == "anonymous"
|| Logical OR. True if either side is true. context.auth.principal.name == "user1" || context.auth.principal.name == "user2"
! Logical NOT. Inverts the result. !(context.auth.type == "anonymous")
() Parentheses. Control evaluation order. context.topic.name == "a" && (context.auth.type == "b" || context.auth.type == "c")

Available fields

Depending on where an expression is authored, the fields available in the context vary. The source of truth for these is the x-expression vendor extension in the API specification, which marks a field as expecting a boolean expression (like condition) or a string expression (like principal_mapping).

Variable

Type

Description

Availability

Example

context.auth.type string The type of authentication used.
  • condition field in Cluster, Produce, and Consume policies
  • resource_names field in ACL policies
context.auth.type == "anonymous"
context.auth.principal.name string The name of the principal for this connection.
  • condition field in Cluster, Produce, and Consume policies
  • resource_names field in ACL policies
context.auth.principal.name == "user1"
context.auth.principal.id v1.2+ string The UUID of the authenticated principal. Only populated when the principal was resolved through a Kong Identity directory lookup.
  • condition field in Cluster, Produce, and Consume policies
  • resource_names field in ACL policies
context.auth.principal.id == "a1b2c3d4-..."
context.auth.principal.metadata v1.2+ map<string, any> Metadata attached to the authenticated principal in Kong Identity. Inner keys must be checked with has() before access.
  • condition field in Cluster, Produce, and Consume policies
  • resource_names field in ACL policies
has(context.auth.principal.metadata.team) && context.auth.principal.metadata.team == "operators"
context.auth.token.claims v1.1+ map<string, any> Only populated for sasl_oauth_bearer authentication. Contains all claims from the JWT token. Claims can be strings, numbers, booleans, arrays, or nested JSON objects.
  • condition field in Cluster, Produce, and Consume policies
  • resource_names field in ACL policies
"test-claim" in context.auth.token.claims
context.topic.name string The name of the topic of the record.
  • condition field in Produce and Consume policies
context.topic.name == "my-ns.my-topic"
record.headers map<string, string> The headers of the record.
  • condition field in Produce and Consume policies
record.headers["skip-record"] == "true"
record.value.content map<string, any> The value of the record. Deep fields can be accessed using map indexing.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.content["sub.other"] == 3
record.value.validated bool Whether record validation succeeded or not.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.validated == true
record.value.schema.id v1.2+ int Registry-assigned schema ID. Populated when the record value is validated by a Confluent schema registry.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.id == 42
record.value.schema.version v1.2+ int Registry-assigned schema version, when returned by the registry.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.version == 3
record.value.schema.format v1.2+ string "avro" or "json".
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.format == "avro"
record.value.schema.avro.name v1.2+ string Avro record name (Avro only).
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.avro.name == "User"
record.value.schema.avro.namespace v1.2+ string Avro record namespace (Avro only, when declared on the schema).
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.avro.namespace == "com.example"
record.value.schema.json.title v1.2+ string JSON Schema title (JSON only, when declared on the schema).
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.json.title == "Order"
record.value.schema.json.id v1.2+ string JSON Schema $id (JSON only, when declared on the schema).
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.json.id == "https://example.com/schemas/order.json"
record.value.schema.metadata.tags list<string> Tags assigned to the schema in the schema registry.
  • condition field in Produce and Consume policies used as children of Schema Validation
"PII" in record.value.schema.metadata.tags
record.value.schema.metadata.properties map<string, string> Custom properties assigned to the schema in the schema registry.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.metadata.properties["owner"] == "team-orders"
record.value.schema.metadata.properties v1.3+ map<string, string> Confluent schema-registry metadata.properties. Present as an empty map whenever record.value.schema is present, even when the registry omits the properties.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.value.schema.metadata.properties['owner'] == 'payments-team'
record.value.schema.metadata.tags v1.3+ map<string, list<string>> Confluent schema-registry metadata.tags, keyed by tag. Each value is the list of schema paths (for example io.confluent.field.<name>) that carry that tag. Present as an empty map whenever record.value.schema is present, even when the registry omits the tags.
  • condition field in Produce and Consume policies used as children of Schema Validation
'PII' in record.value.schema.metadata.tags && 'io.confluent.field.ssn' in record.value.schema.metadata.tags['PII']
record.key.schema.* v1.2+ Same shape as record.value.schema.* Populated when the record key has schema_validation configured. Sub-fields are absent when not applicable; use has() to test presence.
  • condition field in Produce and Consume policies used as children of Schema Validation
record.key.schema.format == "json" && record.key.schema.json.title == "UserKey"
context.certificate.subject map<string, string> Subject distinguished name of the client certificate, as a map. Access individual attributes such as context.certificate.subject["CN"] (Common Name) or context.certificate.subject["O"] (Organization).
  • principal_mapping field in TLS Server listener policies (mTLS)
context.certificate.subject["CN"]
context.certificate.issuer map<string, string> Issuer distinguished name of the client certificate, same format as subject.
  • principal_mapping field in TLS Server listener policies (mTLS)
context.certificate.issuer["O"]
context.certificate.serialNumber string Serial number of the client certificate.
  • principal_mapping field in TLS Server listener policies (mTLS)
context.certificate.serialNumber
context.certificate.sans.dns list<string> DNS Subject Alternative Names on the client certificate.
  • principal_mapping field in TLS Server listener policies (mTLS)
context.certificate.sans.dns[0]
context.certificate.sans.uri list<string> URI Subject Alternative Names on the client certificate.
  • principal_mapping field in TLS Server listener policies (mTLS)
context.certificate.sans.uri.filter(u, u.startsWith("spiffe://"))[0]

principal_mapping expressions only have access to context.certificate. They can’t reference context.auth, context.topic, or record, since no connection or record exists yet at certificate mapping time.

Operators and functions

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, or a key is present in a map.
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 an RE2 regular expression string. Matches any substring unless anchored with ^ and $.
substring() Returns the part of the string from the start index up to and excluding the end index.
size() Returns the number of elements in a list or map, or the number of characters in a string.
has() Returns true if the key exists in the map. Required before accessing inner keys of context.auth.principal.metadata or record.value.schema.*/record.key.schema.*, since those sub-fields aren’t pre-populated.
all() Returns true if all elements in the list satisfy the predicate.
exists(), map(), filter() Additional CEL comprehension macros for working with lists and maps.
?: (ternary) Conditional expression: condition ? value_if_true : value_if_false.

Regular expression matching follows RE2 syntax. Event Gateway doesn’t support a default() or other fallback function for CEL expressions. An expression that accesses a field that isn’t present, or a record.value.schema.*/record.key.schema.* field gated to a higher min_runtime_version, is rejected at configuration time rather than allowed to fail at runtime. Check for optional fields with has() and version-gate your policies with min_runtime_version instead.

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. See record.value.content or context.auth.token.claims.

Example expressions

Don’t apply a policy if a record has an x-restricted=true header and the user isn’t an admin:

context.topic.name == "filterdemo" && record.headers["x-restricted"] == "true" && context.auth.principal.name != "admin"

Apply a policy only for user1 and user2:

context.auth.principal.name == "user1" || context.auth.principal.name == "user2"

Apply a policy only for topics that start with my-prefix:

context.topic.name.startsWith("my-prefix")

Apply a policy if a header is present, regardless of value:

"x-optional-header" in record.headers

Apply a policy if the topic is filterdemo and the record content has a field foo equal to bar, or a field sub.other equal to 3:

context.topic.name == "filterdemo" && record.value.content["foo"] == "bar" || record.value.content["sub.other"] == 3

Compute a dynamic list of fields to encrypt, based on the principal’s identity:

context.auth.principal.name == "external-partner" ? ["personal.ssn", "personal.name"] : []

Apply a policy only when the record’s schema is tagged as PII in the Confluent schema registry v1.3+:

'PII' in record.value.schema.metadata.tags

Apply a policy only to schemas owned by the payments-team, based on a schema registry property v1.3+:

'owner' in record.value.schema.metadata.properties && record.value.schema.metadata.properties['owner'] == 'payments-team'

Migrating from the legacy JavaScript-style syntax

Event Gateway originally shipped with an expression language based on a subset of JavaScript, and has now moved to CEL. CEL is a widely adopted standard with a stricter, better-specified grammar than a JavaScript subset, and gives Event Gateway room to support additional CEL-native capabilities over time.

Both syntaxes are accepted today. The notation you use decides which one parses your condition (and other expression fields):

  • Wrapped in double curly braces: Parsed as CEL.
  • Not wrapped: Parsed as the legacy JavaScript-style syntax.

CEL uses this same double curly brace wrapper for every field type: a boolean field like condition, a string field like principal_mapping, and a list field like encrypt_fields[].paths are all wrapped the same way. The legacy syntax treats the fields differently: condition is unwrapped, but other string fields use ${...} template literals instead.

Existing expressions written in the legacy syntax continue to work unchanged, but the legacy syntax will be deprecated in a future update. We recommend migrating existing expressions to CEL syntax, and creating all new expressions in CEL.

Syntax comparison

The fields available to an expression are the same in both syntaxes. Only the notation for accessing and comparing them differs.

Concept

Legacy JavaScript-style syntax

CEL

Delimiters None. The raw expression is the entire field value. Double curly braces wrap the entire field value.
Field access context.topic.name context.topic.name, or the shorter topic.name (the context. prefix is optional in CEL)
String contains .includes(...) .contains(...)
String prefix/suffix .startsWith(...), .endsWith(...) .startsWith(...), .endsWith(...) (unchanged)
Substring .substring(start, end) .substring(start, end) (unchanged)
Regex match .match(...) .matches(...)
Length .length .size()
Membership test in in (unchanged)
Key presence test 'key' in someMap has(someMap.key) for the maps that require it (see Operators and functions); 'key' in someMap also works
String concatenation + + (unchanged)
Conditional value condition ? valueIfTrue : valueIfFalse condition ? valueIfTrue : valueIfFalse (unchanged)
Secret and environment references ${env['MY_VAR']}, ${vault.my_vault.my_secret} Not available inside condition or other boolean/comparison expressions; vault and env aren’t declared fields there. Continue using the legacy ${...} syntax for condition and similar expression fields.

Some fields that accept secret and environment references, such as encrypt_fields[].encryption_key, are also sensitive fields.

  • A sensitive field that’s set to a literal value is encrypted at rest and never returned in API responses.
  • A sensitive field that’s set to an expression is stored and returned as the expression itself, not encrypted, since the actual secret isn’t known until the expression resolves at runtime.

Example migrations

Legacy JavaScript-style syntax

CEL

context.topic.name.endsWith('my_suffix') context.topic.name.endsWith("my_suffix")
context.topic.name.includes('demo') context.topic.name.contains("demo")
record.value.content['name'].length > 3 record.value.content["name"].size() > 3
context.auth.token.claims.topic_prefix + "*" context.auth.token.claims.topic_prefix + "*"
context.topic.name.startsWith("public_") ? ["personal.ssn"] : [] context.topic.name.startsWith("public_") ? ["personal.ssn"] : []

Legacy JavaScript-style syntax

The following reference covers the JavaScript-subset syntax used when an expression isn’t wrapped in double curly braces. The available fields are the same as for CEL; only the operators and functions are different.

Supported operators and expressions

Event Gateway supports the following operators and expressions in the legacy syntax:

Operator or expression type

Operator or function

Logical operators &&, ||, !
Comparison operators ==, !=, <, <=, >, >=
Concatenation operator +
Relational operator in
Conditional operator ? :
String functions includes, startsWith, endsWith, substring, match, length

Legacy string functions

  • includes: Performs a case-sensitive search to determine whether a given string may be found within this string, as defined in the JavaScript standard.
  • startsWith: Determines whether the string begins with the characters of a specified string, equivalent to the JavaScript standard function.
  • endsWith: Determines whether the string ends with the characters of a specified string, equivalent to the JavaScript standard function.
  • substring: Returns the part of this string from the start index up to and excluding the end index.
  • match: Retrieves the result of matching this string against an RE2 regular expression string.
  • length: Returns the number of characters in the string.

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!