# Managing Templates

> Keep the template storage current from code: upload, check, download and delete templates, read merge fields and nested blocks, with curl, .NET and PHP.

- **Product:** ReportingCloud
- **Section:** Documentation
- **Kind:** Topic
- **Path:** ReportingCloud > Document Processing > Managing Templates
- **4623 words**
- **Web URL:** https://docs.textcontrol.com/reporting-cloud/document-processing/templates/
- **LLMs URL:** https://docs.textcontrol.com/reporting-cloud/document-processing/templates/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/reporting-cloud/document-processing/templates/llms-full.txt

---

Every ReportingCloud account has a template storage, a private store on the service for the templates your application merges again and again. A merge request then names a stored template instead of carrying it. The templates endpoints of the Web API upload, list, inspect, download and delete these templates from code, so that a deployment script or an admin page of your application keeps the storage up to date. The portal does the same by hand, as [Template Storage](https://docs.textcontrol.com/reporting-cloud/portal/templates/llms-full.txt) shows.

### How the Template Storage Works

A stored template is known by its file name, such as *sample\_invoice.tx*, the demo template that every trial account starts with. The endpoints that work with a stored template take this name in the query parameter `templateName`: `POST /v1/document/merge` merges it with data, `POST /v1/document/findandreplace` replaces text in it, `GET /v1/templates/thumbnails` renders its pages and `GET /v1/document/share` shares it.

- **Formats.** The storage takes templates in the formats DOC, DOCX, RTF and TX, the TX Text Control format.
- **Unique names.** Template names are not case-sensitive: *Invoice.docx* and *invoice.DOCX* name the same template. An upload under a name that already exists, in any case, replaces the stored template, which keeps its stored name. Check the name with `GET /v1/templates/exists` first when you want to keep the stored one.
- **Storage quota.** An upload under a new name adds 1 to the template storage quota of your account, an upload that replaces a stored template doesn't, and deleting a template takes 1 off again. The plans hold from 10 templates (Basic) to 500 (Extreme), as [Plans and Licensing](https://docs.textcontrol.com/reporting-cloud/overview/licensing/llms-full.txt) lists.
- **Stored or sent with the request.** A merge can also carry the template in its request body, where it is used once and not stored. A stored template is merged faster when you use it many times, because it is not transferred with every request. The [Overview](https://docs.textcontrol.com/reporting-cloud/mail-merge/overview/llms-full.txt) of the Mail Merge section compares the two ways.

The endpoints of the storage:

| Endpoint | Method | What it does | Success response |
|---|---|---|---|
| [templates/upload](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/upload/post/llms-full.txt) | `POST` | Stores the template of the body under `templateName` | `201 Created` |
| [templates/list](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/list/get/llms-full.txt) | `GET` | Lists the stored templates | `200`, an array of objects with `templateName`, `modified` and `size` |
| [templates/count](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/count/get/llms-full.txt) | `GET` | Counts the stored templates | `200`, a number |
| [templates/exists](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/exists/get/llms-full.txt) | `GET` | Checks whether a template of this name is stored | `200`, `true` or `false` |
| [templates/info](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/info/get/llms-full.txt) | `GET` | Reads the merge fields, merge blocks and user-defined document properties | `200`, a `TemplateInfo` object |
| [templates/pagecount](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/pagecount/get/llms-full.txt) | `GET` | Counts the pages of a template | `200`, a number |
| [templates/download](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/download/get/llms-full.txt) | `GET` | Returns the template | `200`, the template as a Base64 string in a JSON string |
| [templates/delete](https://docs.textcontrol.com/reporting-cloud/reference/web-api/templates/delete/delete/llms-full.txt) | `DELETE` | Deletes the template | `204 No Content` |

A name that is not in the storage gets `400 Bad Request` from info, pagecount, download and delete, while exists answers `false`. An upload is refused with `400` when the extension of the name is not .doc, .docx, .rtf or .tx, or the template has a wrong format, isn't in the format of its extension or is corrupted. It is refused with `403 Forbidden` when the storage of your plan is full, and the same `403` comes for a name of more than 207 characters or with `#` or `/`. An upload whose body is not a JSON string of valid Base64 data gets `500 Internal Server Error`. [Errors and Status Codes](https://docs.textcontrol.com/reporting-cloud/document-processing/errors/llms-full.txt) lists every cause.

Every request carries an `Authorization` header, as [Authentication](https://docs.textcontrol.com/reporting-cloud/document-processing/authentication/llms-full.txt) describes. The samples below read the API key from the environment variable `REPORTINGCLOUD_API_KEY`.

### Upload a Template

The body of an upload is the template as a Base64 string in a JSON string, and `templateName` gives the name it gets in the storage. The following commands store the local file *invoice.docx* as *invoice.docx*:

```bash
# Wrap the Base64 string of the template in a JSON string
base64 -w 0 invoice.docx | jq -Rs . > template.json

curl -X POST "https://api.reporting.cloud/v1/templates/upload?templateName=invoice.docx" \
  -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @template.json
```

The commands work as follows:

- `base64 -w 0` encodes the file without line breaks, and `jq -Rs .` turns the encoded text into a JSON string with quotes. The body is this string alone, not an object around it.
- `--data-binary @template.json` sends the file unchanged, which keeps large templates out of the command line.
- The answer `201 Created` means the template is stored. A template stored under the same name before is replaced.

### List and Count the Stored Templates

`GET /v1/templates/list` returns one object per stored template:

```bash
curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/list"
```

```json
[
  {
    "templateName": "invoice.docx",
    "modified": "2016-06-02T09:25:22+00:00",
    "size": 3246
  },
  {
    "templateName": "sample_invoice.tx",
    "modified": "2019-06-28T07:22:34+00:00",
    "size": 35271
  }
]
```

`modified` is the time of the last change in UTC, and `size` the size of the template in bytes. When you need the number only, `GET /v1/templates/count` returns it as a plain number, such as `2`.

How many templates your plan still takes is in the account settings: [GET /v1/account/settings](https://docs.textcontrol.com/reporting-cloud/reference/web-api/account/settings/get/llms-full.txt) returns `uploadedTemplates` and `maxTemplates` along with the document and spell checking quotas.

### Check Whether a Template Exists

Before a merge names a template, or before an upload would replace one, ask the storage whether the name is taken:

```bash
curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/exists?templateName=sample_invoice.tx"
```

The answer is `true` or `false`. Unlike the other endpoints that take a name, exists doesn't answer `400` for a name that is not stored.

### Read the Merge Fields and Blocks of a Template

`GET /v1/templates/info` opens a stored template and returns its structure, without changing it. A merge field is a placeholder that the merge fills with the value of the JSON property of the same name, and a merge block is a part of the template that the merge repeats for each element of an array, such as a table row per invoice item:

```bash
curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/info?templateName=sample_invoice.tx"
```

The response for *sample\_invoice.tx*, shortened to one field per level:

```json
{
  "templateName": "sample_invoice.tx",
  "mergeBlocks": [
    {
      "name": "item",
      "mergeFields": [
        {
          "dateTimeFormat": "",
          "name": "qty",
          "numericFormat": "",
          "preserveFormatting": false,
          "text": "«qty»",
          "textAfter": "",
          "textBefore": ""
        }
      ],
      "mergeBlocks": []
    }
  ],
  "mergeFields": [
    {
      "dateTimeFormat": "",
      "name": "invoice_no",
      "numericFormat": "",
      "preserveFormatting": false,
      "text": "«invoice_no»",
      "textAfter": "",
      "textBefore": "#"
    }
  ],
  "userDocumentProperties": {}
}
```

The `TemplateInfo` object holds:

| Property | Content |
|---|---|
| `templateName` | The template name as the request gives it, whose case can differ from the name in the storage |
| `mergeFields` | The merge fields outside of any block |
| `mergeBlocks` | The merge blocks, each with its `name`, its own `mergeFields` and the `mergeBlocks` nested in it |
| `userDocumentProperties` | The user-defined document properties, as names and values |

Each merge field has a `name`, the formats `dateTimeFormat` and `numericFormat` it applies to date and numeric values, `preserveFormatting`, which says whether the formatting is preserved, its displayed `text` and the texts before and after the field, `textBefore` and `textAfter`. A block lists only its own fields and blocks, so walk the nested blocks to see the whole structure.

Your application can use the structure before it merges, for example to compare the field names with the keys of its JSON data, to check that it has an array for each block, or to build an input form with one field per merge field and merge the values the user enters. [Merge Fields](https://docs.textcontrol.com/reporting-cloud/mail-merge/fields/llms-full.txt) and [Merge Blocks](https://docs.textcontrol.com/reporting-cloud/mail-merge/blocks/llms-full.txt) show how names and data match.

### Count the Pages of a Template

`GET /v1/templates/pagecount` returns the number of pages of a stored template as a plain number:

```bash
curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/pagecount?templateName=sample_invoice.tx"
```

The number tells you, for example, which pages exist before you ask `GET /v1/templates/thumbnails` for images of a page range, as [Thumbnails](https://docs.textcontrol.com/reporting-cloud/document-processing/thumbnails/llms-full.txt) shows.

### Download a Template

`GET /v1/templates/download` returns a stored template as a Base64 string in a JSON string. The following commands save *sample\_invoice.tx* to a local file:

```bash
curl -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/download?templateName=sample_invoice.tx" \
  --output template.json

# Decode the Base64 string of the response
jq -r . template.json | base64 -d > sample_invoice.tx
```

`jq -r .` removes the quotes of the JSON string, and `base64 -d` decodes it into the file.

### Delete a Template

`DELETE /v1/templates/delete` removes a template from the storage and frees its place in the template storage quota:

```bash
curl -X DELETE -H "Authorization: ReportingCloud-APIKey $REPORTINGCLOUD_API_KEY" \
  "https://api.reporting.cloud/v1/templates/delete?templateName=invoice.docx"
```

The answer `204 No Content` has no body. A merge that names the deleted template afterward is refused with `400 Bad Request`.

### Manage Templates With .NET

The NuGet package `TXTextControl.ReportingCloud` sends the same requests from C#. The following console application uploads *invoice.docx* unless the storage already has a template of this name, lists the stored templates and prints the merge structure of the uploaded one:

```csharp
using TXTextControl.ReportingCloud;

var rc = new ReportingCloud(Environment.GetEnvironmentVariable("REPORTINGCLOUD_API_KEY"));

// Upload the template only if the storage has none of this name
if (!rc.TemplateExists("invoice.docx"))
{
    rc.UploadTemplate("invoice.docx", File.ReadAllBytes("invoice.docx"));
}

foreach (Template template in rc.ListTemplates())
{
    Console.WriteLine($"{template.TemplateName}: {template.Size} bytes, changed {template.Modified}");
}

TemplateInfo info = rc.GetTemplateInfo("invoice.docx");

foreach (MergeField field in info.MergeFields)
{
    Console.WriteLine($"Field: {field.Name}");
}

foreach (MergeBlock block in info.MergeBlocks)
{
    PrintBlock(block, 0);
}

// Prints a block with its fields and, indented, the blocks nested in it
void PrintBlock(MergeBlock block, int level)
{
    string indent = new string(' ', level * 2);
    Console.WriteLine($"{indent}Block: {block.Name}");

    foreach (MergeField field in block.MergeFields)
    {
        Console.WriteLine($"{indent}  Field: {field.Name}");
    }

    foreach (MergeBlock nested in block.MergeBlocks)
    {
        PrintBlock(nested, level + 1);
    }
}
```

The code works as follows:

- The `ReportingCloud` constructor takes the API key. The class sends it in the `Authorization` header of every request to `https://api.reporting.cloud`.
- `TemplateExists` and `UploadTemplate` call `GET /v1/templates/exists` and `POST /v1/templates/upload`. `UploadTemplate` takes the name in the storage and the template as a byte array, and does the Base64 encoding.
- `ListTemplates` returns a list of `Template` objects with `TemplateName`, `Modified` and `Size`.
- `GetTemplateInfo` returns a `TemplateInfo` object whose `MergeFields` and `MergeBlocks` hold `MergeField` and `MergeBlock` objects, so `PrintBlock` calls itself for each nested block.

Each endpoint of the storage has a method in the .NET and the PHP SDK:

| Endpoint | .NET | PHP |
|---|---|---|
| `templates/upload` | `UploadTemplate(templateName, template)` | `uploadTemplate($templateFilename)`, `uploadTemplateFromBase64($data, $templateName)` |
| `templates/list` | `ListTemplates()` | `getTemplateList()` |
| `templates/count` | `GetTemplateCount()` | `getTemplateCount()` |
| `templates/exists` | `TemplateExists(templateName)` | `templateExists($templateName)` |
| `templates/info` | `GetTemplateInfo(templateName)` | `getTemplateInfo($templateName)` |
| `templates/pagecount` | `GetTemplatePageCount(templateName)` | `getTemplatePageCount($templateName)` |
| `templates/download` | `DownloadTemplate(templateName)` | `downloadTemplate($templateName)` |
| `templates/delete` | `DeleteTemplate(templateName)` | `deleteTemplate($templateName)` |

A refused request makes the .NET methods throw an `ArgumentException` and the PHP methods a `RuntimeException`, as [Handle Errors in the SDKs](https://docs.textcontrol.com/reporting-cloud/document-processing/errors/#handle-errors-in-the-sdks) describes. [.NET](https://docs.textcontrol.com/reporting-cloud/sdks/dotnet/llms-full.txt) and [PHP](https://docs.textcontrol.com/reporting-cloud/sdks/php/llms-full.txt) in the SDKs section show how to install and set up each SDK.

### Next Steps

- [Template Storage](https://docs.textcontrol.com/reporting-cloud/portal/templates/llms-full.txt): upload, create and delete templates in the portal.
- [Overview](https://docs.textcontrol.com/reporting-cloud/mail-merge/overview/llms-full.txt): merge a stored template, or one sent with the request, with JSON data.
- [Thumbnails](https://docs.textcontrol.com/reporting-cloud/document-processing/thumbnails/llms-full.txt): render the pages of a stored template as images.
- [Sharing Documents](https://docs.textcontrol.com/reporting-cloud/document-processing/sharing/llms-full.txt): show a stored document on a web page.
- [Errors and Status Codes](https://docs.textcontrol.com/reporting-cloud/document-processing/errors/llms-full.txt): what a refused request means.

---

## Nearby

- **Previous:** [Thumbnails](https://docs.textcontrol.com/reporting-cloud/document-processing/thumbnails/llms-full.txt)
- **Next:** [Sharing Documents](https://docs.textcontrol.com/reporting-cloud/document-processing/sharing/llms-full.txt)
