CEL Expression Language
The Computed Column transform evaluates a CEL expression against every row of your dataset. This page documents the expression syntax and the FileBender-specific extensions layered on top of it.
What is CEL?
Section titled “What is CEL?”CEL (Common Expression Language) is a non-Turing-complete expression language designed for speed and safety. FileBender uses the @marcbachmann/cel-js implementation, which covers most of the CEL specification.
Expressions are evaluated per-row. Each row is exposed as a flat object whose keys are the column names from your dataset.
Column references
Section titled “Column references”There are two ways to reference a column value in an expression:
Dot syntax
Section titled “Dot syntax”When a column name is a valid identifier (letters, digits, underscores, starts with a letter), use dot notation directly:
revenue - costBracket syntax
Section titled “Bracket syntax”When a column name contains spaces, punctuation, or starts with a digit, wrap it in square brackets:
[Total Revenue] - [Operating Cost]FileBender’s preprocessor rewrites bracket references into safe identifiers before the expression reaches the CEL engine. You can mix both styles in the same expression:
[Total Revenue] * tax_rateOperators
Section titled “Operators”Arithmetic
Section titled “Arithmetic”| Operator | Description | Example |
|---|---|---|
+ | Addition | price + tax |
- | Subtraction | total - discount |
* | Multiplication | quantity * price |
/ | Division | total / count |
% | Modulo | index % 2 |
FileBender registers cross-type arithmetic overloads so that numeric CSV columns (parsed as JavaScript floating-point numbers) work naturally with integer literals. An expression like Amount * -1 works even though Amount is a double and -1 is an int.
Comparison
Section titled “Comparison”| Operator | Description | Example |
|---|---|---|
== | Equal | status == "active" |
!= | Not equal | status != "draft" |
< | Less than | age < 18 |
<= | Less or equal | score <= 100 |
> | Greater than | amount > 0 |
>= | Greater or equal | count >= 10 |
Logical
Section titled “Logical”| Operator | Description | Example |
|---|---|---|
&& | AND | age >= 18 && status == "active" |
|| | OR | role == "admin" || role == "owner" |
! | NOT | !is_deleted |
| Operator | Description | Example |
|---|---|---|
? : | Ternary | score > 50 ? "pass" : "fail" |
in | Membership | status in ["active", "pending"] |
Built-in functions
Section titled “Built-in functions”Type conversion
Section titled “Type conversion”| Function | Description | Example |
|---|---|---|
string(x) | Convert to string | string(123) → "123" |
int(x) | Convert to integer | int("42") → 42 |
double(x) | Convert to floating-point | double("3.14") → 3.14 |
type(x) | Get the type name | type(42) → int |
dyn(x) | Cast to dynamic type | dyn(42) |
Collections
Section titled “Collections”| Function | Description | Example |
|---|---|---|
size(x) | Length of list, string, or map | size("hello") → 5 |
String methods
Section titled “String methods”These are called as methods on a string value:
| Method | Description | Example |
|---|---|---|
.contains(s) | Substring check | name.contains("Inc") |
.startsWith(s) | Prefix check | code.startsWith("US") |
.endsWith(s) | Suffix check | email.endsWith(".com") |
.matches(regex) | Regex match | phone.matches("[0-9]+") |
.size() | String length | name.size() |
.indexOf(s) | First occurrence index | text.indexOf("@") |
.lastIndexOf(s) | Last occurrence index | path.lastIndexOf("/") |
.substring(start) | Slice from index | code.substring(2) |
.substring(start, end) | Slice range | code.substring(0, 2) |
Macros
Section titled “Macros”| Macro | Description | Example |
|---|---|---|
has(field) | Check if a field exists | has(row.email) |
list.all(x, cond) | All elements satisfy condition | scores.all(s, s > 0) |
list.exists(x, cond) | Any element satisfies condition | tags.exists(t, t == "urgent") |
list.exists_one(x, cond) | Exactly one satisfies | flags.exists_one(f, f == "primary") |
list.map(x, expr) | Transform each element | values.map(v, v * 2) |
list.filter(x, cond) | Keep matching elements | items.filter(i, i > 0) |
Common patterns
Section titled “Common patterns”String concatenation
Section titled “String concatenation”first_name + " " + last_nameConditional values
Section titled “Conditional values”amount > 1000 ? "high" : "low"Null-safe access
Section titled “Null-safe access”If a column might be missing or null, use has() to guard the access:
has(row.discount) ? price - row.discount : priceNumeric coercion
Section titled “Numeric coercion”CSV values are parsed as JavaScript numbers (floating-point). Use int() if you need integer arithmetic:
int(quantity) * int(unit_price)Combining string and number
Section titled “Combining string and number”name + " (qty: " + string(quantity) + ")"Error handling
Section titled “Error handling”When a CEL expression fails for a given row (type mismatch, division by zero, missing variable), FileBender does not stop the flow. Instead:
- The failing row’s computed column value is set to
null. - The row is marked as an error in the execution results.
- Processing continues with the next row.
This means a single bad row won’t block the rest of your dataset. Check the execution results panel for error counts after running the flow.
Limitations
Section titled “Limitations”- Expressions are evaluated per-row. You cannot reference other rows or aggregate across the dataset within a single expression. Use Group By for aggregation.
- The CEL engine runs in the browser. Extremely complex expressions on large datasets may slow down processing.
- Only the CEL standard library subset shipped by
@marcbachmann/cel-jsis available. FileBender does not add custom functions beyond the arithmetic overloads described above.