Back Original

Show HN: CEL by Example

CEL (Common Expression Language) evaluates expressions against data—simple values, a Protobuf message, or a JSON object.

CEL's fast, portable, and safe: it's used in Kubernetes admission control, Google Cloud IAM conditions, Firebase security rules, Envoy Proxy routing, and Protovalidate's constraint rules.

Let's explore CEL together, starting with a simple User message:

{
    "name": "Alice",
    "roles": ["admin", "editor", "viewer"],
    "age": 30,
    "email": "alice@example.com",
    "created": timestamp("2025-12-14T00:00:00Z"),
    "email_verified": timestamp("2025-12-14T18:30:00Z")
}

Strings and numbers#

A basic comparison: is the user over 18?

user.age >= 18
// result: true (bool)

Check the user's email domain with a string function.

user.email.endsWith("@example.com")
// result: true (bool)

Collections#

Does the user have a specific role? in checks membership in a list.

"admin" in user.roles
// result: true (bool)

What if the match isn't exact? exists() tests whether any element satisfies a condition.

user.roles.exists(r, r.startsWith("ad"))
// result: true (bool)

The user has three roles—what if we only want the elevated ones? filter() narrows a list to matching elements.

user.roles.filter(r, r != "viewer")
// result: ["admin", "editor"] (list)

Timestamps and durations#

Did the user verify their email within 24 hours of signing up? CEL handles time natively—subtract two timestamps to get a duration, then compare.

user.email_verified - user.created < duration("24h")
// result: true (bool)

Logical operators#

Logical operators combine checks into a single expression.

user.age >= 18 && "admin" in user.roles
// result: true (bool)

The conditional operator allows branching logic.

user.age >= 18 ? "adult" : "minor"
// result: "adult" (string)

Transforming data#

CEL expressions return any type. Build a map that strips PII from the user.

{"roles": user.roles, "is_adult": user.age >= 18}
// result: {"roles": ["admin", "editor", "viewer"], "is_adult": true} (map)

Annotate each role with whether it's elevated. map() transforms a collection into a new one.

user.roles.map(r, {"role": r, "elevated": r != "viewer"})
// result: [{"role": "admin", "elevated": true}, {"role": "editor", "elevated": true}, {"role": "viewer", "elevated": false}] (list)

map() can also filter—select elevated roles and grant write access in one step.

user.roles.map(r, r != "viewer", r + ":write")
// result: ["admin:write", "editor:write"] (list)