Dynamic plugin config with CEL

Uses: Kong Gateway
Related Documentation
Minimum Version
Kong Gateway - 3.16

A plugin’s config can be computed per request from a CEL expression, for example, reading an attribute of the authenticated Consumer or Principal, instead of always using a fixed value. This is useful when you want to change plugin behavior depending on a specific situation, such as for authenticated users. For example, you can set different limits for Consumers tagged with vip versus Consumers that don’t have that tag on the Rate Limiting Advanced plugin.

Kong Gateway supports this through the following mechanisms:

  • Expressible config fields: A config field has a paired CEL expression field. If the expression evaluates successfully, Kong Gateway uses its result; otherwise, it falls back to the field’s static value.
  • Direct CEL fields: A config field’s value is a CEL expression. There’s no separate static value to fall back to.

Both are different from a plugin’s condition field, which decides whether the whole plugin runs for a request. Expressible config fields and direct CEL fields don’t skip the plugin; they only change the value of one specific field.

The following plugins support expressions in fields:

Plugin

Mechanism

Fields

ACL Direct CEL fields allow_when, deny_when
Rate Limiting Expressible config fields custom_key, second, minute, hour, day, month, year
Rate Limiting Advanced Expressible config fields limit, custom_key

See each plugin’s own documentation for how its fields are used.

Expressible config fields

A plugin schema field marked as expressible has a matching field of the same name under a top-level expressions block, alongside config:

plugins:
  - name: rate-limiting-advanced
    config:
      limit:
        - 10          # static fallback
      window_size:
        - 60
    expressions:
      limit:
        - consumer.tags.exists(t, t == "vip") ? 1000 : 10

For each request, Kong Gateway evaluates the field’s expression:

  • If the expression evaluates successfully and produces a value of the correct type for that field, Kong Gateway uses that value for the request.
  • If the expression is unset, invalid, or fails to evaluate (for example, because a referenced field is missing), Kong Gateway falls back to the field’s static value in config.

This fallback means an expressible field can never cause a request to fail. If you want to use an expression for a field, make sure its sibling static config.FIELD value is also set. For example, to use expressions.limit, also set config.limit.

Expressible fields use the same CEL field vocabulary, types, and null-handling rules as plugin conditions. See the CEL expressions reference:

Array fields

If the underlying config field is an array, its expression is also an array, with one expression per element, in the same order. For example, the Rate Limiting Advanced plugin’s limit field holds one value per configured window:

config:
  limit: [10, 100]
  window_size: [60, 3600]
expressions:
  limit:
    - consumer.tags.exists(t, t == "vip") ? 1000 : 10
    - consumer.tags.exists(t, t == "vip") ? 10000 : 100

Leave an element as an empty string ("") to keep that position’s static config value with no expression override:

expressions:
  limit:
    - consumer.tags.exists(t, t == "vip") ? 1000 : 10
    - ""

Handling default values

Unlike condition, an expressible field’s expression can’t be wrapped in default(). The sibling config value is already the automatic fallback, so default() would be redundant. Accessing a missing key in a map field like principal.metadata also doesn’t need a has() guard: if the key is absent, the expression fails to evaluate, and Kong Gateway falls back to the static config value, the same as for any other evaluation failure.

expressions:
  custom_key: principal.metadata.partner_id

Use has() only if you want the expression itself to compute a different fallback than the static config value when a key is missing:

expressions:
  custom_key: "has(principal.metadata.partner_id) ? principal.metadata.partner_id : \"unknown\""

Direct CEL fields

Some plugin config fields don’t have a static equivalent to fall back to. Instead, the field itself holds one or more CEL expressions, and there is no parallel static value.

The ACL plugin’s allow_when and deny_when fields work this way: each field is an array of CEL boolean expressions, evaluated against the same request context as plugin conditions. allow_when and deny_when are mutually exclusive with allow and deny. Exactly one of allow, deny, allow_when, or deny_when must be set on a given ACL plugin instance.

plugins:
  - name: acl
    config:
      allow_when:
        - "has(principal.metadata.tier) && principal.metadata.tier == \"partner\""

Because there’s no static value to fall back to, a direct CEL field that fails to evaluate at runtime behaves according to that plugin’s own documented error handling, rather than falling back to a static config value.

For example, in ACL, allow_when and deny_when are treated differently:

  • If an allow_when expression fails, the request is denied with a 403, the same safe default as when no expression matches at all.
  • If a deny_when expression fails, the plugin fails closed with a 500, because silently treating that error as “no match” could let through a request that should have been denied.

See Dynamic allow and deny rules in the ACL plugin documentation for details.

Limitations

  • Expressible fields and direct CEL fields are only supported in the HTTP subsystem. They can’t be used with stream (TCP, TLS, UDP) Routes.
  • In hybrid mode, if the control plane is running Kong Gateway 3.16 or later and a data plane is running an earlier version, that data plane won’t understand expressible fields or direct CEL fields.
    • For an expressible field, Kong Gateway strips the plugin’s expressions for that data plane, and the plugin falls back to its static config value.
    • For a direct CEL field, there’s no static value to fall back to, so Kong Gateway doesn’t sync the plugin’s config to that data plane at all. For example, an older data plane won’t receive an ACL plugin instance configured with allow_when or deny_when until it’s upgraded.
  • Only fields the plugin’s schema marks as expressible can have an expressions entry. The plugin’s schema lists these fields under its expressions key. Setting expressions for any other field is rejected at configuration time.
  • An expressible field’s expression must resolve to a type compatible with that field’s own Kong type (for example, a number field requires a CEL double, int, uint, or dyn result).

FAQs

No. A CEL expression can reference request data like http.headers.* and http.queries.*. That data is controlled by the client, so a client can set it to any value, including one chosen to collide with, spoof, or evade another client’s.

This matters most for a field that keys, identifies, or scopes something for a specific client, such as a counter key, a quota bucket, or an identity binding. For a sensitive field like this, we recommend authenticated identity-derived values, such as principal.id or principal.metadata.*. A client can’t forge those values without first compromising the authentication step itself.

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!