In my last post about scripting, I ended with an uncomfortable conclusion: once a low-code platform lets people write scripts, the platform is no longer a form builder. It is a programming environment.
That conclusion raised a bigger question, and it took me a while to admit it.
If configuration can express business logic, then configuration is code.
And if configuration is code, why does nobody on a low-code platform get any of the things we take for granted when writing code?
No version control. No diff. No review. No staging. No rollback.
We give them a Save button and call it a day.
That is a strange place for an enterprise platform to be.
The day a customer asks to roll back
Here is the scenario that changed how I think about this.
A customer spends two weeks adjusting a purchase approval workflow. A new node, a new approver rule, a branch for amounts over a certain threshold. Everything looks right in testing. They publish on a Friday afternoon.
On Monday, the routing is wrong for a subset of orders. Not catastrophically wrong. Just wrong enough that a few orders sat untouched over the weekend.
The customer asks the natural question: can we go back to Friday morning's version?
Silence.
There is no Friday morning version. There is a workflow editor showing the current state, and a database holding the current rows. Nobody wrote down what it looked like before.
The fix is to open the workflow editor and rebuild it from memory.
That is not a rollback. That is an incident handled by archaeology.
Why this is genuinely harder in low-code
The temptation is to say "just add version control." Anyone who has used Git assumes this is a weekend project.
It is not, and the reason is structural.
In a normal codebase, the unit of change is a file. Files are self-contained, they have paths, and a diff has an obvious meaning: these lines, in this order.
In a low-code platform, the unit of change is a row in a table.
- A data model is a record in an
entitiestable, and each field is a record in afieldstable. - A form is a layout record that points at field records by ID.
- A workflow is a graph: nodes in one table, edges in another, conditions in a third.
- Permissions are rules that reference roles, departments, and field IDs.
- A script is text that refers to fields by name.
Not one of these is a file. Not one of them can be diffed line by line, because there are no lines. And they all reference each other through generated IDs that mean nothing outside the environment where they were created.
So when someone flips a "publish date" field from optional to required, the real question is not "what is the diff?"
It is: which forms render this field? Which workflow conditions read it? Which report filters count on it? Which saved views sort by it?
In a file-based world, grep answers that in a second. In a row-based world, nothing answers it until you build the dependency graph yourself.
That dependency graph is the actual product. The diff is only its visible surface.
Three layers, and most platforms stop at the first
Once I stopped thinking of this as "add Git" and started thinking of it as "make configuration changeable safely," the problem decomposed into three layers.
Layer one: snapshots. A full, immutable copy of the configuration at a point in time. Cheap to build, crude to use. Nobody wants to restore an entire environment because one field permission changed. But if this layer does not exist, you have nothing to fall back on at all. It is the emergency brake.
Layer two: changesets. Instead of snapshotting everything, record the delta. Every publish produces a changeset with an author, a timestamp, a description, and a list of atomic changes. Now rollback means reverting a changeset, not rebuilding a workflow from memory.
This is also where review becomes possible, and that matters more than it sounds. In most enterprises today, one person with workflow editor access can silently change how money moves through the company. A changeset with a visible diff turns that from an invisible act into a reviewable one.
Layer three: promotion. This is the layer almost everyone skips, and it is the one enterprise buyers actually ask about.
They want three environments. Development for building. UAT for the business to test. Production for real work. And they want a controlled path from one to the next.
That sounds like deployment. In a row-based configuration model, it is a genuinely hard identity problem.
The field ID in development is not the field ID in UAT. The role ID in UAT is not the role ID in production. So a changeset cannot be a list of ID references. It has to be a semantic description — this field, identified by this stable key, on this entity — which the target environment resolves locally.
Which means the platform needs stable, human-meaningful keys for every configurable object from day one. If you start with auto-increment IDs and try to bolt on promotion later, you will spend months mapping things back.
The line nobody draws early enough: configuration versus data
Here is the trap that costs the most, and it is not a technical one.
Every low-code platform eventually has a table that is half configuration and half data, and nobody notices until the first production promotion.
A status list attached to a workflow? Configuration. It should ship.
A customer_level reference table the business maintains by hand? That is data. It must not ship.
A currency exchange rate table? Data. Does not ship.
A list of legal entity names used in a dropdown? Honestly, it depends on the customer.
If you never explicitly decided which is which, the first promotion from UAT to production will overwrite live business data with test data.
It is not a subtle bug. It is the kind of bug that ends a pilot.
The rule I have landed on: if a business user can edit it in the running system and would be angry to lose it, it is data, and promotion must never touch it. Everything else is configuration.
It sounds obvious written down. It took me an embarrassing amount of time to write it down.
Draft is not staging
There is one more distinction I had to learn the hard way.
Some platforms do have a draft mode. You edit the application in a designer, then hit Publish, and the changes go live. It feels like a release process.
It is not one.
A draft is a working copy. It tells you what is about to change, which is genuinely useful. But it does not give you a history, it does not let you compare two states, it does not let you promote one application's changes to a different environment, and it does not let you revert.
Draft mode answers "what am I about to ship." A release process answers "what did we ship, who approved it, what changed, and how do we undo it."
These are different questions. Only one of them survives an audit, and only one of them survives a Monday morning.
The uncomfortable conclusion
I started out thinking of the low-code platform as a builder. Tables, forms, workflows, permissions. A faster way to produce a business system.
I now think that is only half of it.
The other half is that we are asking non-engineers to do engineering work, without handing them any of the safety equipment engineers rely on. And when something goes wrong, we act surprised.
Which means the platform has to supply that safety equipment itself.
Not because customers ask for version control. They will not. They will ask exactly once — on the Monday morning after the Friday afternoon publish. And at that moment, "we don't have that" is not an answer that keeps an enterprise account.
If configuration is code, then it deserves what code deserves: a history, a diff, a review, a rollback, and a path from test to production.
Anything less is a Save button pretending to be a release process.
Top comments (0)