Zoho Creator · Deluge

Subforms give you three tasks.
Everything else is design work.

Subforms are how you model line items in Creator — an order and its products, an inspection and its checkpoints, an invoice and its charges. The simplest field to drag onto the canvas, and the one that generates more support threads than anything else in the platform.

By Value Score Team · 27 August 2026 · 10 min read

0
Deluge subform tasks, total
0
Deluge ways to update or delete one row
0
quick-view values before a report won’t open
0/min
action calls to design your throttle against

Here’s why. Deluge gives you exactly three subform tasks: insert rows, clear rows, and read a row’s fields. Not update a row. Not delete a row. Everything you build has to come out of those three. And half the syntax floating around online was never valid to begin with. So this is what’s actually documented, and how to design around the gaps.

The decision you can’t reverse: blank vs existing form

When you add a subform field, Creator asks whether you want a Blank Form or an Existing Form. That choice decides everything you can build afterwards. There’s no documented way to switch later.

 Blank FormExisting Form
Rows exist as standalone recordsNoYes
Can build a report over the line itemsNoYes
Counted against record usageNot statedYes, each row is billed
Formula field allowed insideNoYes
Other blocked field typesSignature, Auto number, Section, Notes, UsersAuto number, Section, Notes

Zoho is direct about the consequence: if you want users to view the subform records separately in a report, build that form first and then add it as a subform.

So settle one question before you drag the field onto the canvas. Will anyone ever need a report of the line items themselves — all products sold this quarter, every failed checkpoint across inspections? If yes, build the child form first and add it as an existing form. Pick blank, watch that requirement turn up later, and you’re rebuilding.

Note

Under Field Properties → Display Type, Limit maximum entries takes a value up to 500. That 500 is the ceiling of the setting, not a row cap — Zoho doesn’t document a hard row limit for a subform with no limit configured.

Reading rows: the row keyword and where it doesn’t exist

Inside client-side form events, row is the handle to the subform row you’re acting on:

// read
emailId = row.mail;

// write
row.concession = "yes";

Now the most important table in this article. It maps where row is actually available:

Eventrow accessinsert()clear()
On LoadYesYesYes
On User InputYesYesYes
On ValidateNoYesNo
On SuccessNoYesNo
Subform on add rowYesYesYes
Subform on delete rowYesYesYes
Custom functionNoYesNo
Scheduled workflowNoYesNo
Report action itemNoYesNo
Nearly every “why is row null” question traces back to this table. row is a client-event handle, nothing more.

It doesn’t exist in On Validate, On Success, standalone functions, schedules, or report actions.

Watch out — Zoho contradicts itself

The table says clear() isn’t supported in custom functions or schedules — it’s describing the input.<subform>.clear() form, which makes sense, because input only exists in a form context. But the same official page then publishes a server-side example that calls .clear() on a fetched record:

fetchedRecords = Employees [ Date == today ];
for each rec in fetchedRecords
{
    rec.Job_Experience.clear();
}

Those two things sit on one page and seem to contradict each other. If your design depends on clearing rows from a schedule, prove it in your own app before you commit.

To read rows server-side, iterate the subform off a fetched parent record:

fetch_records = form1[ID != 0];
for each entry in fetch_records
{
    fetch_subform1 = entry.subform1;
    for each data in fetch_subform1
    {
        // data.Product, data.Quantity ...
    }
}
Test this first

Iterating input.<subform> inside an On Validate workflow is not documented anywhere. The snippet you’ll find on forums for that comes from an unresolved bug report, and it has an assignment sitting where a comparison should be. If you need to validate across rows, test it in your own app instead of trusting the copy-paste.

Writing rows: the insert pattern

Rows are constructed against the parent form, not the subform:

row1 = Orders.Items();
row1.Item_Name = "Laptop";
row1.Quantity = 1;

rows = Collection();
rows.insert(row1);

input.Items.insert(rows);

<mainForm_linkName>.<subForm_linkName>() is a constructor call. That’s the bit people get wrong. Zoho’s reference page also shows passing a bare row directly (input.Items.insert(row1);) — that works — but the fuller examples use the collection form, and it’s the one worth standardising on.

To create a parent record along with its lines in a single statement, pass the collection as the subform field’s value. The rows have to be constructed against the same form you’re inserting into:

row1 = Orders.Items();
row1.Item_Name = "Laptop";
row1.Quantity = 1;

rows = Collection();
rows.insert(row1);

response = insert into Orders
[
    Items = rows
];
Note

Two documented caveats on insert into: the target form’s On Validate and On Success workflows don’t run, and Auto Number, Formula, and system fields must not be specified.

To empty a subform:

input.Items.clear();

What you cannot do, and the way around it

There’s no task to delete one row. No task to update one row. Zoho says so plainly: you can’t delete selected rows in a subform, and the clear task wipes every row. The record-update task is just as explicit — Signature, Subform, and Add Notes fields can’t be specified.

So in Deluge, changing one line item means clear and rebuild. Read the rows, filter in memory, clear, re-insert the survivors.

And here’s the asymmetry that changes how you architect integrations: the v2.1 API can write subforms. Both Add Records and Update Records accept a subform as an array of objects, and Update Records’ exclusion list doesn’t mention subform at all:

"SubForm": [
  { "Date_Time": "10-Jan-2020 22:12:10", "Email": "barry@zylker.com" },
  { "Date_Time": "11-Jan-2020 22:12:10", "Email": "harry@zylker.com" }
]

Get Records returns each row with its own ID, so rows are individually addressable through the API — even though Deluge gives you no way to target one.

Test this first

Zoho doesn’t document whether passing a subform array to Update Records replaces, appends, or merges by row ID. That’s the single most commercially important undocumented behaviour in this topic. Try it against a throwaway record first.

Parent totals: formula fields won’t do it

The instinct is to drop a formula field on the parent that sums the subform. It doesn’t hold up. Zoho documents the limitation directly: formula expressions involving input.subform.<field> don’t update when the underlying values change, and that limitation applies only to fields living in different forms connected through existing relationships. And a formula field can’t exist at all in a blank subform.

The documented pattern is Deluge, on the on-user-input event of a subform field:

if(row.Quantity != null && row.Rate != null)
{
    row.Sub_Total = row.Quantity * row.Rate;
}
else
{
    row.Sub_Total = 0;
}
input.Number_of_Items = Order_Details.count();
input.Total_Amount = Order_Details.sum(Sub_Total);

Order_Details.sum(Sub_Total): subform link name, .sum(), unquoted field link name. Zoho notes that average, minimum, maximum, and median work the same way. Worth knowing that this form shows up in Zoho’s Academy but not in the Deluge sum() reference page, which only documents the form-plus-criteria version. You won’t find it by looking it up.

Pair it with disable on load so users can’t type into the computed fields, including the ones inside the subform:

disable Total_Amount;
disable Number_of_Items;
disable Order_Details.Rate;
disable Order_Details.Sub_Total;
Note

A caveat on count() that mirrors the sum() one: Zoho’s Academy uses bare Order_Details.count() on one page and input.Dependents.count() on another, in the same class of workflow event, and the Deluge reference documents neither. If one form returns nothing, try the other before you decide your script is broken.

Watch out — scripted inserts

Both row events are documented purely as button-click triggers, firing when Add new is clicked. Whether a script-driven insert() fires an add-row workflow isn’t documented either way, and developers report that it doesn’t. If you put a rollup on add-row and also populate rows by script, test that path on purpose — or your totals will be silently wrong for the scripted inserts.

The honest trade-off on the documented pattern: it recomputes live. Good UX, expensive on your throttle. Subform add-row scripts, delete-row scripts, on-user-input scripts, formula fields, and lookup filters all pull from one budget — documented as 120 workflow action calls per minute per IP on the limitations page and 100 on the best-practices page, so design to 100. A user rattling in twenty line items fast is the most likely place in a Creator app to hit that wall.

Subforms in reports: manage expectations

  • Keep them out of quick view. A report won’t load when a subform in quick view has more than 2,500 values mapped for a record. That’s not slow — that’s a report that won’t open. Use detail view and Add Related Block.
  • 20 display fields maximum for subform data in a report.
  • No sorting, grouping, or pivots on subform fields.
  • Search is almost absent: only the first field of the subform, and not at all if that subform holds a lookup with advanced search or a dropdown.
  • Criteria restrictions: subform fields can’t be compared to single-select fields with ==, and list expressions can’t hold more than six elements.
  • XLSX export offers three subform layouts: single column, separate columns, or a separate sheet. XLSX only — CSV and PDF have no equivalent.

The orphan trap

This one quietly corrupts data for years.

Deleting a parent record de-links its subform rows instead of deleting them. Zoho’s wording: deleting a parent record only delinks the subform fields tied to it.

With an existing-form subform, those child records survive — still stored, still billed, still counted against your record allowance, and attached to nothing. .clear() does the same on an existing-form subform. It de-links rather than deletes.

Watch out — the mandatory flag gives no cover

It bites the other way too: deleting a child form’s record that was added as a subform entry empties that entry in the parent as well — even when the subform field is marked mandatory.

To cascade properly, write the delete yourself in the parent form’s Delete → on validation workflow. Zoho’s documentation says exactly this. Almost nobody does it.

Two more to file away. Audit trail doesn’t cover subforms — not on updated records and not on deleted ones — which matters if you have a compliance story to tell. And the recommended rollup pattern runs on on-user-input workflows, which sit among the things that disqualify a form from offline access. Zoho doesn’t draw that connection itself, but if offline capture is on your roadmap, check it early.

Takeaways

  • Choose blank vs existing form deliberately — reports over line items need existing form, and the choice can’t be undone.
  • row is a client-event handle only, absent from On Validate, On Success, functions, and schedules.
  • Deluge can’t update or delete a single row, but the v2.1 API can. Clear-and-rebuild in Deluge, or drive it through the API.
  • Write your own cascading delete in the parent’s Delete → on validation workflow, or you’ll accumulate orphans indefinitely.
  • Got an app in production with existing-form subforms? Run one query today: count the child records with no parent. That number is usually a surprise — and the fastest evidence that the cascade rule is worth implementing.
Modelling line items on Zoho Creator?

Start your Creator account and build the parent–subform structure right the first time — existing forms where you’ll need reports, cascading deletes where you can’t afford orphans.

Get started with Zoho Creator