Skip to content

jx:each

jx:each is the workhorse command. It loops over a collection and repeats its template area for each item — like a for loop, but in your spreadsheet.

jx:each(items="employees" var="e" lastCell="C1")
AttributeDescriptionDefaultRequired
itemsExpression for the collection to iterateYes
varLoop variable nameYes
lastCellBottom-right cell of the repeating areaYes
varIndexVariable name for the 0-based indexNo
directionDOWN or RIGHTDOWNNo
selectFilter expression (must return bool)No
orderBySort spec: "e.Name ASC, e.Age DESC"No
groupByProperty to group byNo
groupOrderGroup sort: ASC or DESCASCNo
multisheetVariable with sheet names (one sheet per item)No

The simplest and most common use case — repeat a row for each item:

Template:

Template with a header row and a data row containing ${e.name}, ${e.age}, ${e.payment}

Output:

Output with 5 employee rows, formatting preserved from template

The comment on the top-left cell:

jx:area(lastCell="C2")
jx:each(items="employees" var="e" lastCell="C2")

Each employee produces one row. The header stays, the data row repeats, all formatting carries over.

Need a row number? Use varIndex:

jx:each(items="employees" var="e" varIndex="i" lastCell="D1")

Template:

Template with ${i} column for the iteration index

Output:

Output showing 0-based index in the first column

The index i is 0-based (0, 1, 2, …).

By default, rows expand downward. Set direction="RIGHT" to expand across columns instead:

jx:each(items="months" var="m" direction="RIGHT" lastCell="A2")

Template:

Template configured for RIGHT direction expansion

Output:

Output showing data expanded horizontally across columns

Great for time series, calendar layouts, or cross-tab reports.

Only include items that match a condition:

jx:each(items="employees" var="e" select="e.payment > 2000" lastCell="C1")

Template:

Template with a select filter expression

Output:

Output showing only filtered employees

The select expression must return a boolean. Only items where it evaluates to true appear in the output.

Sort items before looping:

jx:each(items="employees" var="e" orderBy="e.name ASC" lastCell="C1")

Template:

Template with orderBy attribute for sorting

Output:

Output with employees sorted alphabetically by name

Multiple sort keys:

jx:each(items="employees" var="e" orderBy="e.department ASC, e.name DESC" lastCell="C1")

Group items by a property. Each group becomes a GroupData object with Item (the key) and Items (the group members):

jx:each(items="employees" var="g" groupBy="department" groupOrder="ASC" lastCell="C5")

Template:

Template configured for groupBy with nested employee iteration

Output:

Output showing employees grouped by department with headers

Inside the loop, g.Item is the group key (e.g., "Engineering") and g.Items is the slice of items in that group. Nest another jx:each inside to iterate over g.Items.

Generate one worksheet per item using multisheet:

jx:each(items="departments" var="dept" multisheet="sheetNames" lastCell="C5")

Template (single sheet serves as the template for all):

Template sheet that will be copied for each item in multisheet mode

Output (one sheet per department):

First output sheet for one department Second output sheet for another department

The sheetNames variable must be a []string in your data:

data := map[string]any{
"departments": departments,
"sheetNames": []string{"Engineering", "Marketing", "Sales"},
}

The template sheet is copied for each item, then removed.

Place an inner jx:each inside an outer one. The inner command’s area must be strictly within the outer:

Cell A1 comment:
jx:area(lastCell="C5")
jx:each(items="departments" var="dept" lastCell="C5")
Cell A2 comment:
jx:each(items="dept.Employees" var="e" lastCell="C2")

This creates a department header followed by employee rows for each department. XLFill detects the nesting automatically from the cell positions.

Download the runnable examples from examples/xlfill-test:

FeatureTemplateOutput
Basic loopt01.xlsx01_basic_each.xlsx
Loop index (varIndex)t02.xlsx02_varindex.xlsx
Expand RIGHTt03.xlsx03_direction_right.xlsx
Filter (select)t04.xlsx04_select.xlsx
Sort (orderBy)t05.xlsx05_orderby.xlsx
Group (groupBy)t06.xlsx06_groupby.xlsx
Nested loopst13.xlsx13_nested_each.xlsx
Multi-sheett14.xlsx14_multisheet.xlsx

See the full code snippets for each example.

Sometimes you need to conditionally show or hide part of a template:

jx:if →