Recursive AI

Fill an Excel template with data in C#

OneDocument is a free, open-source Word, Excel and PowerPoint document library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Populate tagged spreadsheet cells with JSON data while keeping the template’s layout.

Terminal
dotnet add package \
  RecursiveAI.OneDocument.Spreadsheets

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
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:

Text
A1: Customer          B1: Total
A2: {{ customer }}    B2: {{ total }}
C#
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:

Text
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:

Text
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.

C#
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:

Text
Ada: 42
Lin: 18
Sum: 60

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.

All onedocument recipes