DS Server

Merging Templates

Topic

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:

PropertyContent
mergeDataThe data as a JSON array. Each element is one record
mergeDataJsonInstead of mergeData, the same array as a string that contains JSON. The .NET package sends its MergeDataJson property here
templateThe template as a Base64 string, in the format RTF, DOC, DOCX or TX. On Linux, use DOCX, as Limits on Linux explains
mergeSettingsOptional. The merge options and the document properties of the result, listed in Set Merge Options

Two query parameters control the result:

ParameterContent
returnFormatThe format of the created documents: PDF, PDFA, DOCX, DOC, RTF, HTML, TX or TXT. The default is PDF
appendWith 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, 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 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 -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:

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:

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:

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

SettingEffectDefault
removeEmptyFieldsRemoves fields that receive no datatrue
removeEmptyBlocksRemoves the content of merge blocks that receive no datatrue
removeEmptyImagesRemoves images that receive no datafalse
removeEmptyLinesRemoves lines that contain only empty fields and no other contentfalse
removeTrailingWhitespaceRemoves trailing whitespace before the document is savedtrue
mergeHtmlMerges field values that contain formatted HTML, enclosed in an <html> elementfalse
cultureThe culture for date and currency values, such as "fr-FR" or "de-DE""en-US"
dataSourceCultureThe culture used to parse date and currency values of the dataThe server language
mergeCultureThe culture used to format date and currency values in the documentThe server language
formFieldMergeTypeHow form fields are merged, as a number: 0 not at all, 1 preselected with the data where possible, 2 replaced by the data0
formattingPrinterThe name of a printer whose text dimensions are used to format the documentNone
documentTitle, documentSubject, author, creatorApplicationThe document properties of the result. In PDF, title and subject are limited to 2,000 charactersNone
creationDate, lastModificationDateThe dates saved in the document, as ISO 8601 date strings such as "2026-09-29"None
userPasswordThe password needed to open the created documentNone

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