---
title: "Fill an Excel template with data in C#"
url: https://recursiveai.org/onedocument/cookbook/fill-excel-template/
description: "Populate tagged spreadsheet cells with JSON data while keeping the template’s layout."
version: "1.0.0"
---

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

```shell
dotnet add package \
  RecursiveAI.OneDocument.Spreadsheets
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
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](https://recursiveai.org/onedocument/cookbook/fill-excel-template/fill-excel-template.zip), then `dotnet run`.

## Full program

`template.xlsx` has these cells on its `Data` sheet:

```text
A1: Customer          B1: Total
A2: {{ customer }}    B2: {{ total }}
```

```csharp
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.

```csharp
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
```

## Related recipes

- [Create an Excel file](https://recursiveai.org/onedocument/cookbook/create-excel-file/)
- [Read an Excel file](https://recursiveai.org/onedocument/cookbook/read-excel-file/)
- [Fill a Word template](https://recursiveai.org/onedocument/cookbook/fill-word-template/)

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