Populate tagged spreadsheet cells with JSON data while keeping the template’s layout.
dotnet add package \ RecursiveAI.OneDocument.Spreadsheets
Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using RecursiveAI.OneDocument.Spreadsheets; using RecursiveAI.OneDocument.Spreadsheets .Templating; using System.Text.Json; using var book = Workbook.Open("template.xlsx"); using var data = JsonDocument.Parse( """{"customer":"Ada","total":42}"""); XlsxTemplateBinder.Render(book, data); book.SaveAs("filled.xlsx");
Writes filled.xlsx with A2 = “Ada” and B2 = 42; the snippet prints nothing.
Unzip the example, then dotnet run.
Full program
template.xlsx has these cells on its Data sheet:
A1: Customer B1: Total A2: {{ customer }} B2: {{ total }}
using RecursiveAI.OneDocument.Spreadsheets; using RecursiveAI.OneDocument.Spreadsheets .Templating; using System.Text.Json; using var book = Workbook.Open("template.xlsx"); using var data = JsonDocument.Parse( """{"customer":"Ada","total":42}"""); XlsxTemplateBinder.Render(book, data); book.SaveAs("filled.xlsx"); Console.WriteLine(book.Sheets[0]["A2"].FormattedText); Console.WriteLine(book.Sheets[0]["B2"].FormattedText);
dotnet run prints:
Ada 42
By default, missing data renders empty with a warning; strict template mode throws TemplateException.
Variations
Repeat rows and calculate the total
rows-template.xlsx is also in the zip. Its populated cells are:
B1: Customer C1: Total A2: {% for order in orders %} B2: {{ order.customer }} C2: {{ order.total }} D2: {% endfor %} B3: Sum C3: =SUM(C2:C2)
The tags in A2 and D2 repeat row 2 for each JSON item. The summary moves to row 4, and its formula expands to SUM(C2:C3). Strict rejects missing data; RecalculateNow calculates formula results before saving.
using RecursiveAI.OneDocument.Spreadsheets; using RecursiveAI.OneDocument.Spreadsheets .Templating; using RecursiveAI.OneDocument.Templating; using System.Text.Json; using var book = Workbook.Open("rows-template.xlsx"); using var data = JsonDocument.Parse(""" {"orders":[ {"customer":"Ada","total":42}, {"customer":"Lin","total":18} ]} """); XlsxTemplateBinder.Render(book, data, new TemplateOptions { Strict = true }, new XlsxTemplateOptions { RecalculateNow = true }); book.SaveAs("orders.xlsx"); var sheet = book.Sheets["Data"]; for (int row = 2; row <= 4; row++) Console.WriteLine( $"{sheet[$"B{row}"].FormattedText}: " + sheet[$"C{row}"].FormattedText);
Writes orders.xlsx and prints:
Ada: 42 Lin: 18 Sum: 60
Related recipes
The short answer
Put tags such as {{ customer }} in an XLSX template, load it with Workbook.Open, and call XlsxTemplateBinder.Render with JSON data. Save the edited workbook to a separate output path. The binder changes the workbook in place; it does not return a second independent workbook for you to save.
This approach is useful when the workbook already contains the layout and number formats you want. The template decides where values go, and the data supplies those values. The example keeps the JSON document alive until rendering finishes and writes filled.xlsx, leaving the original template available for another customer.
How do I fill an Excel template with data in C#?
Use a complete tag within one cell. In the sample, A2 contains {{ customer }} and B2 contains {{ total }}. The JSON object has matching customer and total properties. A tag cannot span two cells, even if their displayed text appears next to each other.
A cell containing only one expression becomes a typed value: numbers remain numbers, booleans remain booleans, and text remains text. Thus B2 becomes the number 42 rather than a string containing those digits. Text around a tag changes that rule: a cell such as Total: {{ total }} renders as text. Use a single-expression cell when formulas need to consume the result as a number.
Formulas themselves are not templates. The binder moves formulas and rewrites references when repeated rows change their positions. Do not put a template tag inside formula text and expect the expression engine to replace it.
A loop with its opening and closing tags in different cells repeats whole rows by default. A loop with both tags in one cell is inline text within that cell. That difference is important for invoice lines: repeating the text of one cell is not the same as making another spreadsheet row. Row properties travel with each copy, and tag-only cells keep their positions and styles even when their visible tag disappears.
How do I preserve formatting in Excel?
Keep formatting in the template cells. A single-expression cell keeps its style when it becomes a typed value. Apply currency, date or decimal formatting to the template cell rather than concatenating a currency symbol into the data. Cells without tags retain their values, formulas and styles; sheets without tags in either cell text or their names are not touched by the binder.
Repeated bands carry their cell styles and row properties. Some accompanying objects also follow the changed ranges, including merged cells, defined names, tables and standard conditional-formatting ranges. This does not mean every extension feature is rewritten: the documented x14 rules inside extLst are carried only on the template’s own sheet and are not rewritten.
After rendering, the workbook is marked for full calculation on open. That is not the same as having fresh cached formula results immediately. An automated downstream reader that only uses cached results may need the binder’s RecalculateNow option. Charts that depend on formula cells without cached values have no point for those cells until calculation supplies a value.