Merging Templates
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. 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 explains |
mergeSettings | Optional. The merge options and the document properties of the result, listed in 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 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:
[
{
"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,LastNameandEMail. 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
OrderItemswith the fieldsQtyandProductbecomes 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 and Merge Blocks 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 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:
DS=http://localhost:5000
TOKEN="<access_token>"
# Build the MergeBody from the template and the data
jq \
template <(base64 0 template.docx) \
data data.json \
'{template: $template, mergeData: $data[0]}' > merge.json
curl POST "$DS/documentprocessing/document/merge?returnFormat=PDF" \
"Authorization: Bearer $TOKEN" \
"Content-Type: application/json" \
@merge.json \
merged.json
# The response is an array; decode its first document
jq '.[0]' merged.json | base64 > invoice.pdf
The commands work as follows:
--rawfile templatereads the Base64 string of the template into the variable$template, and--slurpfile datareads 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 theMergeBodyto merge.json, which--data-binary @merge.jsonsends as the body. jq -r '.[0]'takes the first Base64 string of the response array, andbase64 -ddecodes 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:
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:
Templatetakes the template as a byte array; the package encodes it.MergeDataJsontakes the data as a JSON string. Instead of JSON,MergeDatatakes the records as a list of objects of one type, such as aList<Customer>.Mergereturns an array of byte arrays, one for each created document. Its overloads take theMergeBodyalone, which creates PDF, aReturnFormatas well, or aReturnFormatand theappendvalue.- The overloads without the
appendvalue sendappend=false, unlike the Web API's default. To get one document for all records, passtrueas 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:
curl POST "$DS/documentprocessing/document/merge?returnFormat=PDF&append=false" \
"Authorization: Bearer $TOKEN" \
"Content-Type: application/json" \
@merge.json \
merged.json
# Decode each document of the array into its own file
n=0
for document in $(jq '.[]' merged.json); do
n=$((n + 1))
echo "$document" | base64 > "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:
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:
jq \
template <(base64 0 template.docx) \
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:
mergeBody.MergeSettings = new MergeSettings
{
DocumentTitle = "Invoice",
Author = "Sales Department",
RemoveEmptyFields = false,
Culture = "de-DE"
};
Next Steps
- Find and Replace: fill a template with plain text placeholders instead of merge fields.
- Document Information: list the merge fields and merge blocks of a template.
- Appending Documents: combine merged documents with a cover page or other files.
- PDF Documents: create signed PDF documents with attachments.