# Merging Templates

> Merge DOCX templates with JSON data into invoices and letters with the merge endpoint: merge fields, merge blocks, one document per record and MergeSettings.

- **Product:** DS Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** DS Server > Document Processing > Merging Templates
- **3444 words**
- **Web URL:** https://docs.textcontrol.com/ds-server/document-processing/mail-merge/
- **LLMs URL:** https://docs.textcontrol.com/ds-server/document-processing/mail-merge/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/ds-server/document-processing/mail-merge/llms-full.txt

---

Invoices, letters, contracts and reports usually share a layout and differ in their data. With the merge endpoint of the DocumentProcessing Web API, you design the layout once as a template with merge fields and merge blocks, send it to DS Server with JSON data and get finished documents back, as PDF or in another format. The sections below build a template request with curl and with .NET, create one document per record and set the merge options.

### How Merging Works

The endpoint is [POST /documentprocessing/document/merge](https://docs.textcontrol.com/ds-server/reference/web-api/documentprocessing/document-merge/post/llms-full.txt). Its JSON body, the `MergeBody`, has these properties:

| Property | Content |
|---|---|
| `mergeData` | The data as a JSON array. Each element is one record |
| `mergeDataJson` | Instead of `mergeData`, the same array as a string that contains JSON. The .NET package sends its `MergeDataJson` property here |
| `template` | The template as a Base64 string, in the format RTF, DOC, DOCX or TX. On Linux, use DOCX, as [Limits on Linux](https://docs.textcontrol.com/ds-server/overview/core-classic/#limits-on-linux) explains |
| `mergeSettings` | Optional. The merge options and the document properties of the result, listed in [Set Merge Options](#set-merge-options) |

Two query parameters control the result:

| Parameter | Content |
|---|---|
| `returnFormat` | The format of the created documents: `PDF`, `PDFA`, `DOCX`, `DOC`, `RTF`, `HTML`, `TX` or `TXT`. The default is `PDF` |
| `append` | With more than one record, `true` appends all merged records to one document, and `false` creates one document per record. The default is `true` |

The response is a JSON array of Base64 strings, one per created document. A request without data or without a template is refused with `400`.

Every request needs an access token, as [Authentication](https://docs.textcontrol.com/ds-server/document-processing/authentication/llms-full.txt) describes. The samples use the service URL `http://localhost:5000` and the token in the variable `TOKEN`.

### Prepare the Template and the Data

The data of the samples is a list of customers, each with the items of an order. Save it as *data.json*:

```json
[
  {
    "Name": "Peter",
    "LastName": "Jackson",
    "EMail": "peter@jackson.com",
    "OrderItems": [
      { "Qty": 5, "Product": "TX Spell .NET" },
      { "Qty": 1, "Product": "TX Text Control .NET Server" }
    ]
  },
  {
    "Name": "Jack",
    "LastName": "Peterson",
    "EMail": "jack@peterson.com",
    "OrderItems": [
      { "Qty": 2, "Product": "TX Spell .NET" }
    ]
  }
]
```

The template, *template.docx*, connects the layout with the data:

- **Merge fields** are placeholders named like the properties of a record, such as `Name`, `LastName` and `EMail`. Each field receives the value of the current record.
- **Merge blocks** are sections of the template, often a table row, that are repeated for each element of a nested array. A block named `OrderItems` with the fields `Qty` and `Product` becomes one row per order item.

DS Server merges with TX Text Control, so the template follows the mail merge rules of TX Text Control. You create templates in the Document Editor, whose **Reporting** tab offers the fields and blocks of the JSON data you pass to it, or in Microsoft Word. The pages [Merge Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/fields/llms-full.txt) and [Merge Blocks](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/blocks/llms-full.txt) of TX Text Control .NET Server explain field names, nested data and blocks in depth. To list the fields and blocks of an existing template, send it to the info endpoint, as [Document Information](https://docs.textcontrol.com/ds-server/document-processing/document-info/llms-full.txt) shows.

### Merge a Template With curl

The following commands merge *template.docx* with *data.json*. With the default `append=true`, the two customers become one PDF document:

```bash
DS=http://localhost:5000
TOKEN="<access_token>"

# Build the MergeBody from the template and the data
jq -n \
  --rawfile template <(base64 -w 0 template.docx) \
  --slurpfile data data.json \
  '{template: $template, mergeData: $data[0]}' > merge.json

curl --request POST "$DS/documentprocessing/document/merge?returnFormat=PDF" \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --data-binary @merge.json \
  --output merged.json

# The response is an array; decode its first document
jq -r '.[0]' merged.json | base64 -d > invoice.pdf
```

The commands work as follows:

- `--rawfile template` reads the Base64 string of the template into the variable `$template`, and `--slurpfile data` reads *data.json* into `$data`, an array around the file's content. `$data[0]` is therefore the array of customers.
- The filter `{template: $template, mergeData: $data[0]}` writes the `MergeBody` to *merge.json*, which `--data-binary @merge.json` sends as the body.
- `jq -r '.[0]'` takes the first Base64 string of the response array, and `base64 -d` decodes it.

### Merge a Template With .NET

The package `TXTextControl.DocumentServices.DocumentProcessing` has a `MergeBody` class for the body. The following console application merges the same files:

```csharp
using TXTextControl.DocumentServices.DocumentProcessing;
using TXTextControl.DocumentServices.DocumentProcessing.OAuth;

var oauthSettings = new OAuthSettings("<client ID>", "<client secret>");
var documentProcessing = new DocumentProcessing("http://localhost:5000", oauthSettings);

var mergeBody = new MergeBody
{
    Template = await File.ReadAllBytesAsync("template.docx"),
    MergeDataJson = await File.ReadAllTextAsync("data.json")
};

// true appends all records to one document; the result holds one byte array per created document
byte[][] results = await documentProcessing.Merge(mergeBody, ReturnFormat.PDF, true);
await File.WriteAllBytesAsync("invoice.pdf", results[0]);
```

The code works as follows:

- `Template` takes the template as a byte array; the package encodes it.
- `MergeDataJson` takes the data as a JSON string. Instead of JSON, `MergeData` takes the records as a list of objects of one type, such as a `List<Customer>`.
- `Merge` returns an array of byte arrays, one for each created document. Its overloads take the `MergeBody` alone, which creates PDF, a `ReturnFormat` as well, or a `ReturnFormat` and the `append` value.
- The overloads without the `append` value send `append=false`, unlike the Web API's default. To get one document for all records, pass `true` as the third argument, as the sample does.

### Create One Document per Record

To get a separate document for each customer, for example to send each letter by email, set `append=false`. The response array then holds one Base64 string per record:

```bash
curl --request POST "$DS/documentprocessing/document/merge?returnFormat=PDF&append=false" \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --data-binary @merge.json \
  --output merged.json

# Decode each document of the array into its own file
n=0
for document in $(jq -r '.[]' merged.json); do
  n=$((n + 1))
  echo "$document" | base64 -d > "invoice-$n.pdf"
done
```

In C#, pass `false` as the third argument of `Merge`, or use an overload without it, and save each element of the result:

```csharp
byte[][] results = await documentProcessing.Merge(mergeBody, ReturnFormat.PDF, false);

for (int i = 0; i < results.Length; i++)
{
    await File.WriteAllBytesAsync($"invoice-{i + 1}.pdf", results[i]);
}
```

### Set Merge Options

The optional `mergeSettings` object controls how empty data is handled, how values are formatted and which properties the created documents get. The following body sets a title and an author, keeps empty fields and formats date and currency values for Germany:

```bash
jq -n \
  --rawfile template <(base64 -w 0 template.docx) \
  --slurpfile data data.json \
  '{
    template: $template,
    mergeData: $data[0],
    mergeSettings: {
      documentTitle: "Invoice",
      author: "Sales Department",
      removeEmptyFields: false,
      culture: "de-DE"
    }
  }' > merge.json
```

The settings, with their defaults:

| Setting | Effect | Default |
|---|---|---|
| `removeEmptyFields` | Removes fields that receive no data | `true` |
| `removeEmptyBlocks` | Removes the content of merge blocks that receive no data | `true` |
| `removeEmptyImages` | Removes images that receive no data | `false` |
| `removeEmptyLines` | Removes lines that contain only empty fields and no other content | `false` |
| `removeTrailingWhitespace` | Removes trailing whitespace before the document is saved | `true` |
| `mergeHtml` | Merges field values that contain formatted HTML, enclosed in an `<html>` element | `false` |
| `culture` | The culture for date and currency values, such as `"fr-FR"` or `"de-DE"` | `"en-US"` |
| `dataSourceCulture` | The culture used to parse date and currency values of the data | The server language |
| `mergeCulture` | The culture used to format date and currency values in the document | The server language |
| `formFieldMergeType` | How form fields are merged, as a number: `0` not at all, `1` preselected with the data where possible, `2` replaced by the data | `0` |
| `formattingPrinter` | The name of a printer whose text dimensions are used to format the document | None |
| `documentTitle`, `documentSubject`, `author`, `creatorApplication` | The document properties of the result. In PDF, title and subject are limited to 2,000 characters | None |
| `creationDate`, `lastModificationDate` | The dates saved in the document, as ISO 8601 date strings such as `"2026-09-29"` | None |
| `userPassword` | The password needed to open the created document | None |

In C#, the same settings are properties of the `MergeSettings` class, such as `RemoveEmptyFields`, `Culture`, `DocumentTitle` and `Author`, and `FormFieldMergeType` takes the values `None`, `Preselect` and `Replace` of the enumeration of the same name:

```csharp
mergeBody.MergeSettings = new MergeSettings
{
    DocumentTitle = "Invoice",
    Author = "Sales Department",
    RemoveEmptyFields = false,
    Culture = "de-DE"
};
```

### Next Steps

- [Find and Replace](https://docs.textcontrol.com/ds-server/document-processing/find-replace/llms-full.txt): fill a template with plain text placeholders instead of merge fields.
- [Document Information](https://docs.textcontrol.com/ds-server/document-processing/document-info/llms-full.txt): list the merge fields and merge blocks of a template.
- [Appending Documents](https://docs.textcontrol.com/ds-server/document-processing/appending/llms-full.txt): combine merged documents with a cover page or other files.
- [PDF Documents](https://docs.textcontrol.com/ds-server/document-processing/pdf/llms-full.txt): create signed PDF documents with attachments.

---

## Nearby

- **Previous:** [Converting Documents](https://docs.textcontrol.com/ds-server/document-processing/converting/llms-full.txt)
- **Next:** [Appending Documents](https://docs.textcontrol.com/ds-server/document-processing/appending/llms-full.txt)
