DEV Community

informat
informat

Posted on

The Expression Engine Is Small. That Is Exactly the Problem.

In June, a distributor customer of ours called about a quote that should never have existed. Their quoting app has a validation rule: the discount field must stay under thirty percent unless the record carries an approval flag. The rule had been there for a year. It was written by their finance lead herself, in the platform's formula editor, in about four minutes.

The quote in question had a discount of eighty percent. It had been created by a bulk import from their old system, during a product-line migration. The validation formula had evaluated exactly zero times on its journey into the database. Not because the rule was broken — because the rule only ran where the form ran, and an import is not a form.

Nobody had ever told the finance lead that. Nobody had ever told me that, in those words, and I build the thing.

That phone call rearranged how I think about a component I had mentally filed under "small": the expression engine.

It is not one feature. It is six wearing a trench coat

When you say "formula" in a low-code platform, most people picture the computed field. The little column that multiplies quantity by price. That is the least interesting instance.

Look at where expressions actually live in a platform like ours. Default values on fields. Validation rules. Visibility conditions on form sections. Branch conditions in workflows. Filters in reports and list views. Threshold checks in automations. Six subsystems, and in the early version of our platform, six different ways of parsing what was supposed to be the same language.

That last sentence should horrify you.

Because here is the thing about an expression engine: it is not a feature. It is a language, and a language that appears in six places is six chances to disagree with itself. The day a user discovers that empty means one thing in a validation rule and something subtly different in a workflow branch is the day they stop trusting every formula they read.

Unifying the parsers was tedious work with no visible payoff. It was also one of the highest-leverage things we have done: a user who learned the language once could finally predict the platform everywhere.

Every formula is a tiny contract with a field that can disappear

The second thing the discount incident taught me is that a formula is not text. It is a dependency.

When the finance lead wrote her validation rule, she referenced a field called Approved. That reference is a promise: as long as this formula exists, the field it points at exists, and the platform knows the formula must re-evaluate when the field changes. Multiply that promise by a few thousand formulas across a customer's workspace and you have a dependency graph that nobody drew and everybody relies on.

Now let a user delete or rename that field. What happens to the formula?

The lazy answer is that the formula keeps the old text and breaks at runtime. A rule that looks fine in the editor and fails silently in production is the worst failure mode there is: no exception, just a quiet wrong answer, invisible in the place people check and wrong in the place they don't.

The expensive answer is a real dependency graph. Deleting a field tells you what references it. Renaming a field updates every formula atomically. Formula edits are validated against the current schema at save time, not at evaluation time. And when a formula cannot be fixed automatically, the platform says so loudly, at the moment of the destructive action, while the person who can fix it is still holding the tool.

None of this shows up on a feature list. All of it shows up in whether customers dare to evolve their apps after six months of use. A platform where users are afraid to rename fields is a platform that has quietly taught its users to stop building.

Null is the real language

Here is the part of expression engine design that consumed the most arguments per line of spec: what happens when a value is missing.

Every business table is full of holes: a quote without a close date, an order where the discount field was never touched, a contact imported without a phone number. Formulas run into those holes all day long.

So the questions arrive immediately. Is an empty text field null, or an empty string? Is an untouched number null, or zero? And crucially: does a validation rule that evaluates to null pass or fail? We decided, after real deliberation, that a rule which cannot produce an answer should fail closed. A rule that says "this must be under thirty percent" cannot vouch for a number it cannot see. Our finance lead, when I explained this a month after the incident, said: "Obviously." It was not obvious. We chose it. She just happened to agree.

Type coercion is the same story. A formula language that silently turns "10" into 10 is friendly for ten minutes and a liability for a decade, because the string that looks numeric is not always numeric. We chose to be strict and provide explicit conversion functions, and yes, users complained about the strictness for a quarter. Then they stopped filing the bugs that come from silent coercion.

Dates deserve their own confession. We still carry scar tissue from formulas that computed "days overdue" differently depending on which server evaluated them, before we decided the language would treat a zoned instant and a plain calendar date as different types, not one type with a surprise.

Where a formula runs matters more than what it says

The discount formula said exactly the right thing. It ran in the wrong place.

The deep design question is not the grammar. It is the execution contract: which expressions run in the browser as the user types, and which run on the server in the write path.

Visibility conditions want to be instant, so they run client-side. Validation, computed fields, and workflow branches must run server-side, because the write path is the only place where every door leads through the same checkpoint. Forms, imports, API calls, automations — if the rule lives there, a discount of eighty percent cannot exist, no matter which door it came through.

This sounds like an implementation detail. It is a philosophical one. A low-code platform makes a promise to every builder: the constraints you express are properties of the data, not of one particular screen. The moment a rule is a property of a screen, you have shipped a suggestion, not a constraint. Suggestions do not survive contact with integrations.

The security line nobody sees

One more uncomfortable thing. An expression engine is one step away from being a scripting engine, and the distance is measured in "one more useful function." Users will ask for string manipulation, then lookups, then loops, then "just a small HTTP call." Each request is reasonable. The accumulation is a scripting engine with no review, no permissions, and no audit, running inside every form on the platform.

We drew the line deliberately: expressions compute values over the record they are attached to. They get a whitelist of pure functions. Data from other tables arrives through explicit, permission-checked references — the same permission system that governs the UI, not a side door. The moment someone needs more, the answer is the scripting layer, with its own governance. Keeping that line bright is a permanent negotiation.

The uncomfortable conclusion

Here is what I resisted for years: a low-code platform's expression engine is its second programming language, and for most users of the platform, it is the first one. More people will write formulas than will ever open the scripting editor. Its semantics — what null means, what a failed validation means, what a rename does — will outlive your API versioning, your theming, and half your features.

We did not design it like a language. We designed it like a convenience, and the discount incident was the language reading its own terms back to us.

The uncomfortable version is this: the smallest component in your platform is the one your users live in daily. They will never see the workflow engine's state machine or the query planner's choices. They will see your null handling, every afternoon, in every table.

Design it like a language. Or one June, a phone call will design it for you.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.