case study / Zoho Creator & Deluge / Cyprus

Three hundred near-identical Deluge scripts. No way to write once. A proper MCP use case.

The same access and audit logic had to run on every form in the application. The obvious answer is to write it once as a function and call it from everywhere. Zoho Creator does not make it easy, for two separate reasons, and neither is a matter of preference.

That leaves you with dozens of separate copies of the same five scripts, and the problem becomes keeping them identical — a task made considerably easier with AI.

Standardizing and generating Deluge scripts across a large Zoho Creator application using MCP

At a glance

Industry
Healthcare
Location
Cyprus
Year
2026
Engagement
Standards, tooling, and AI-assisted generation for the access and audit layer of a large Zoho Creator application — roughly 300 Deluge scripts, most of which were copies or variants of a few versioned scripts.
Products
Zoho Creator, Deluge, MCP, Claude
Key takeaway
Where a platform won't let you share code, the alternative is a standard every copy is held to. That only works while checking it stays cheap.

01 / The constraintThe obvious solution doesn't work.

Two reasons Creator won't let the logic be a shared function — and how little of it could have moved even if it did.

Every form in the application enforces the same two controls: that the person opening a record is entitled to it, and that whatever they do is written to an audit log. Five scripts, dozens of forms. The normal answer is to write each script once and call from everywhere.

Zoho Creator prevents that in two ways.

A called function cannot stop the workflow that called it.

A return inside a called function does not end the calling workflow. So a security check that returns when it fails has not actually prevented anything: the form carries on as though the check had passed. Errors thrown inside a called function do not propagate the way they do in the parent either, so a try/catch around delegated logic can fail without anyone noticing. And the function has no reliable access to the form's context — record state, lifecycle stage, the input object — unless every value is passed in by hand, which puts back the coupling the function was meant to remove.

The obvious counter is to have the function return a result and let the form treat anything but an explicit pass as a failure. That works but changes almost nothing: the form still has to hold the warning, the redirect, and the stop, and those are the parts that have to be identical everywhere. Furthermore, the audit log has to record which check failed and why — and a version that failed quietly would deny somebody access and keep no record of having done it. More on what could actually move.

Deluge cannot discover its own fields.

The second constraint does more damage. Deluge will not accept a form or field name as a variable. Every reference has to be written out as a literal, which means a Deluge script in Creator cannot meaningfully discover its own fields while running.

The imprecise version of this is wrong, so it is worth stating carefully. The field names are perfectly obtainable: the Creator API returns full form metadata whenever you ask for it. What is unavailable is reflection while the script is running. The names have to be known before the script exists rather than worked out as it executes. So a general routine that audits whatever fields a form happens to have is not difficult to write. There is no way to use that discovery live.

So what was left to move?

Duplicating code is normally poor practice, so the question was asked properly rather than assumed away: how much of this block could live in one shared place, with each form making a short call out to it? Working through it part by part, the answer was "Almost none."

How much of the on-load block could be shared The block splits into three parts. The checks that stop the user must stay in the form because of a platform limitation. The audit record and alert could have moved but were kept deliberately. Only the email wording, about a tenth, moved out. Checks that stop the user must stay, platform limitation Audit record and alert could move, kept deliberately Email wording about a tenth
Only the smallest and least consequential part of the block could reasonably be shared.

A shared function can look things up and work out an answer. It cannot reach back into the form the user is sitting in front of and act on it. Closing the form, showing the warning, and returning the user to the home page can only be done by code living in the form itself, so every check that stops somebody has to stay put.

The audit record and the alert email were a different matter. Those could technically have moved, but they were also the parts we least wanted to gamble with because they are the evidence to the regulator that the controls worked. If a script ever failed without saying so, it would refuse someone access but keep no record of having done it. A refusal with no audit trail is its own compliance problem, and a silent one — from the outside, everything looks fine.

So the only part that could reasonably move was the wording of the alert email: roughly a tenth of the block, and the tenth that matters least. Not worth adding a new moving part to a control protecting confidential healthcare records.

02 / The standardThe answer is a standard.

Standardized field names, one master copy, seventeen revisions, and the cost of choosing duplication on purpose.

If the logic cannot live in one place, the only protection against the copies drifting apart is being able to show that they are identical.

Field names were standardized across the application so that one script is valid everywhere without per-form adjustment. A single master copy sits on an unused base form with a version number and a dated record of what changed and why. No form's script differs from that master unless the difference was decided on and written down.

The version history is there for a practical reason. Seventeen revisions across five major versions, each with its reasoning: an assignment bug where = had been typed instead of ==; a revision that accidentally locked administrators out of the support access they needed; a change to the access model after a strict ownership check turned out to stop managers reading their own staff's records. When the same script exists in hundreds of copies, someone has to be able to answer which version a given form is running.

This approach has a real cost. Copying code into dozens of otherwise identical workflows invites version drift. A shared function would have removed that risk entirely. The trade was made deliberately: a called function's failures would be harder to notice, harder to trace, and more likely to leave the check silently not working.

More importantly, a single failure would be globally catastrophic. We opted for a slightly higher chance of a small escape than a smaller chance of a catastrophic one.

03 / ExceptionsWhat can be centralized, and what can't.

Application variables, module styling, scheduled jobs. Where the line falls between computing something and deciding something.

Functions are not useless in Creator. Three kinds did live centrally in this app:

  • Application variables, holding shared values such as notice text and configuration constants, which scripts read rather than carry their own copies of.
  • Module styling, which is a called function, so each module takes its colour scheme from a single definition.
  • Scheduled jobs, which never had the problem at all since they run on their own rather than inside a form — the one that watches record counts against the platform's limits, and the one that copies expiring audit rows into permanent storage before they are deleted.

What has to stay inline is anything that must stop execution, cancel a submission, redirect a user, or refer to a field by name.

Record locking also exists in two versions depending on whether a form has a status field. Portal invitations and user deactivation run from buttons on reports. All of these write to the audit trail, so all of them have to match the pattern as well.

04 / Generating the scriptsA standard is not the same as three hundred correct copies.

Live field metadata, the master pattern — and the specific way this goes wrong.

Having a standard is necessary and not enough. A standard describes what the scripts should look like. It does not write them.

Doing that by hand would have meant, for each form: fetching the current field list, working out the comparison logic for every field, handling each subform's nesting individually, and typing several hundred lines with nothing to catch a mistyped field name. Deluge does not complain about a wrong field literal. It logs nothing for that field and the script otherwise looks fine — the kind of mistake nobody finds for months.

What made this decision affordable was AI. Give a language model the master pattern, let it pull a form's current field metadata through the MCP, and it generates the script for that form's actual structure. A second AI can validate in bulk.

The reasonable objection is that a template engine could do the same thing. It could not, because the differences between forms are structural rather than a matter of substitution. One form has four nested subforms that need iteration to compare properly; another needs a single value captured; another has no subform at all. A generator flexible enough to handle that range becomes a small compiler for the pattern. The pattern also kept changing — seventeen revisions — and each change would have meant rewriting the generator, now its own failure surface.

What goes wrong when a model writes three hundred files.

The risk is not that a language model writes bad code. Mostly it doesn't. The risk is that it writes different code: an alternate variable name, new edge case handling, a specific regression to an earlier version it had in project memory.

So an app-specific skill was built. The rule became that no change is made beyond what was asked for. Bugs and improvements get raised as questions and wait for a decision. Scripts that are shared are applied to the base form only with the new version number noted at the top for easy comparison. Scripts that need form-specific variation are validated by a second, independent model with the check noted in the comments.

Human review and validation persists at every level, including a mandatory pre-deployment checklist and a post-deployment version audit.

Where that leaves it.

The result is a body of code that is repetitive on purpose and checkable by machine. The master copy carries its version and its history. The next time the pattern changes, the forms are regenerated from current metadata instead of edited one at a time, which is what makes a seventeenth revision affordable in a way an eighteenth hand-edit would not be.

The MCP connection was build-time tooling, not part of the running system. It was switched off at go-live. Nothing in the deployed application depends on it, and a production app holding clinical records does not need a standing programmatic door into its own configuration.

The general version of this argument — where AI earns a place in a business and what has to be true before the tool matters at all — is set out in our ten lessons from the AI boom.

Running into the edges of what Creator will let you build? Let's talk.

Start a Conversation