Back to Blog

Repeating Rows in Salesforce Documents Without Start and End Tags

ZeroExport infers which table rows repeat, and where each group ends, from the merge fields in the row. See how that replaces start/end loop tags in Salesforce document templates.

ZeroExport Team

In most Salesforce document generation tools, a repeating table row needs a pair of markers: one tag to start the loop, one to end it. The tags sit in the template next to your content, and they have to be placed correctly for the output to be right.

ZeroExport has no loop tags. You put a child-record merge field in a row, and the row repeats. The start and end of the repeat are worked out from the template itself.

What start and end tags ask of you

Loop syntax varies by tool, but the pattern is the same. A generic version looks like this:

{#OpportunityLineItems}
| {{Product2.Name}} | {{Quantity}} | {{TotalPrice}} |
{/OpportunityLineItems}

That is manageable for one flat list. The cost shows up as the document gets more realistic:

  • Placement is part of the layout. The opening tag goes in the first cell of the row, the closing tag in the last cell, and a tag in the wrong cell repeats the wrong thing or nothing.
  • Nesting multiplies the tags. An opportunity with line items inside an account needs two pairs, opened and closed in the right order.
  • Subtotals depend on where the tags end. A subtotal row has to sit outside the inner loop but inside the outer one. Move it by one row and it prints once per line instead of once per deal.
  • Tags are visible clutter. They show up in the template, and a reviewer has to know which tags are logic and which are content.

None of this is hard in isolation. It is the part of template building where small mistakes produce output that looks almost right.

In ZeroExport, the token is the signal

A body row that contains a token pointing at a collection of child records repeats once per record. There is nothing else to configure.

| Product                               | Qty                              | Amount                             |
|---------------------------------------|----------------------------------|------------------------------------|
| {{OpportunityLineItem.Product2.Name}} | {{OpportunityLineItem.Quantity}} | {{OpportunityLineItem.TotalPrice}} |

With three line items on the record, this renders three body rows. The header row above it prints once, because rows above the first row that holds a child token are treated as headers.

The same rule works for a list item that holds a child token (one bullet per record), and it nests. A table can list each opportunity followed by its own line items, and each level stays scoped to its parent, up to four levels below the primary object.

How the boundaries are worked out

Three things tell ZeroExport where a repeat starts and stops.

1. The tokens in the row. A row repeats over the list its tokens refer to. The row is the unit. You do not mark where it begins or ends because it is already a row.

2. The shape of your data sources. Which rows repeat, and which list each one repeats over, is decided at save time from how the template's data sources are nested. This happens when the template is compiled, so generation does not re-derive it for every document, and a token that points at a field the data sources do not provide is rejected at save instead of printing blank.

3. The row's role, for rows that close a group. A row set to Subtotal or Total closes the group above it. In a table grouped by a list, a Subtotal row prints once after each outer row's nested rows, and a Total row prints once after all the groups.

That last point is what makes closing rows work without a closing tag. A row such as an account total, which holds aggregates and fields of its parent rather than fields of the rows it would close, ends the group. A row that holds a field of the rows above it, such as a deal's own total, stays with them and prints for each one.

Rows that share a list print together

A common layout has a line row and a total row that both repeat over the same list, for example an item's line followed by its own total row. In 1.11.0 ZeroExport prints these as a block per item: line, total, line, total. The block runs from the first to the last row that uses the list. Fixed rows in between print with each item, and a fixed row after the last use prints once below the items.

Without that inference you would be wrapping the two rows in a loop yourself. Here you describe the rows, and the engine works out that they belong together.

Edge cases are rules, not surprises

Because the boundaries come from the template's structure, the unusual cases have defined behavior:

  • A parent with no child records produces no repeating rows, with no empty row and no error.
  • A Subtotal or Total row can be hidden when there are no rows above it, with the Hide when there are no rows above it option, so an account with no deals does not print an empty subtotal.
  • A row with a visibility rule stops a block, so conditional rows do not get pulled into a repeat they do not belong to.

Where explicit tags still win

Inference is a good default, not a universal answer. Tags give you full control over an arbitrary region, such as repeating a block of mixed content that has no natural row or list item. ZeroExport repeats rows and list items. If you need a free-form region to loop, check that your layout can be expressed as one of those.

Inference also leans on your data sources being set up correctly. A nested source has to name its parent by alias, and the aliases decide which list a token belongs to. That is a small amount of configuration, but it is where to look first when a row does not repeat the way you expect.

FAQ

How do I repeat a table row in a Salesforce document without loop tags?

Put a merge field that points at a collection of child records, such as {{OpportunityLineItem.Product2.Name}}, in a body row. That row repeats once per record. Rows above the first repeating row print once as headers.

How does ZeroExport know where a group ends?

A row's role and its tokens decide it. A Subtotal row closes each outer row's nested rows, and a Total row closes the whole table. A row that holds only fields of the rows it follows stays with those rows.

Can I nest repeating rows?

Yes. A table can repeat over a list inside another list, for example opportunities with their line items, up to four levels below the primary object. Each level is scoped to its parent.

Do I need to resave templates after upgrading?

Boundaries are planned when a template is saved. Save a template once after upgrading to ZeroExport 1.11.0 to get rows that share a list printing together for each item.

Related reading

Ready to try ZeroExport?

Start generating documents directly in your Salesforce org. No integrations, no setup overhead, no complexity.