Block Types Reference covers the
shape a grid's data needs to end up in - a flat array of cells, two per row for a
2-column grid, three for a 3-column grid, and so on. Typing that array out by hand
works for a short, fixed table. This page is about building that array from real
submitted data - most commonly a repeating group (see Form
Editor), where the number of
rows isn't known ahead of time.
The simplest option: bind directly, no JS at all
If a grid just needs one column per sub-field of a repeating group - no custom
formatting, no computed values - skip data entirely and set source_field /
column_keys on the grid block instead:
{
"id": "options_grid", "type": "grid", "columns": 2,
"source_field": "options",
"column_keys": ["description", "price"]
}
This flattens inputs.options (one object per submitted row) into cells automatically
- one
{text: ...}cell per key incolumn_keys, in order, one row per submitted item. It's the same output as the loop/.flatMap()versions below, minus the ceremony, for the common case where every cell is just a sub-field's raw value with no formatting. The Layout Editor's grid properties panel has a "Bind to Repeating Group" section for this that writes the same two properties.
The moment you need formatting (currency signs, right-aligned prices, a header row,
computed text) - reach for data instead; a grid with data set ignores
source_field/column_keys entirely. The rest of this page covers that path.
The straightforward way: loop and push
Say a repeating group field called options has two sub-fields, description and
price. Each submitted row shows up in inputs.options as an object with those two
keys. To turn that into grid cells - a numbered description on the left, a
right-aligned price on the right - loop over it and push two cells per row:
inputs.gridData = [];
for (var i = 0; i < inputs.options.length; i++) {
var option = inputs.options[i];
inputs.gridData.push(
{ text: (i + 1) + '. ' + option.description },
{ text: '$' + option.price, 'text-align': 'right' }
);
}
Read top to bottom: start with an empty array, walk each submitted option, and for
each one add its two cells in order. Array.push happily takes more than one value at
a time, so both of a row's cells go in with one call.
Put this in process.js (see below), then reference it from
the grid block:
{ "id": "options_grid", "type": "grid", "columns": 2, "data": "inputs.gridData" }
Adding a header row
Push the header cells once, before the loop, using the same object shape:
inputs.gridData = [
{ text: 'Description', 'font-weight': 'bold' },
{ text: 'Price', 'font-weight': 'bold', 'text-align': 'right' },
];
for (var i = 0; i < inputs.options.length; i++) {
var option = inputs.options[i];
inputs.gridData.push(
{ text: option.description },
{ text: '$' + option.price, 'text-align': 'right' }
);
}
A more compact alternative: .flatMap()
Once the loop-and-push version feels familiar, the same logic can be written as one
expression with .flatMap() - each option maps to its two cells, and flatMap
flattens all those pairs into one array automatically:
inputs.gridData = inputs.options.flatMap(function (option, i) {
return [
{ text: (i + 1) + '. ' + option.description },
{ text: '$' + option.price, 'text-align': 'right' },
];
});
This produces exactly the same array as the loop above - it's a style preference, not a different feature. Reach for it once you're comfortable with it; the loop version is just as correct and often easier to read back later, especially once a header row or some conditional formatting gets added in.
Where this code lives
Both versions above can go straight in the grid block's own data field instead of
process.js, wrapped as a single expression (an immediately-invoked function, since a
for loop needs statements, not just an expression):
"data": "(function(){ var cells = []; for (var i = 0; i < inputs.options.length; i++) { var o = inputs.options[i]; cells.push({text: (i+1)+'. '+o.description}, {text: '$'+o.price, 'text-align':'right'}); } return cells; })()"
...but that's awkward to read and edit as a single JSON string. Once the logic is more
than a one-liner, it's worth moving to process.js instead - assign it to a new
property on inputs (inputs.gridData above), then reference that property from the
block. See process.js -> Handing computed data to
blocks for exactly how
that mechanism works and the couple of things to know about it (new property names
only, separate scripting context from the block itself).