Carbone is a templating engine designed to generate dynamic documents (PDFs, DOCX, XLSX, PPTX, etc.) by merging templates with JSON data:
- The JSON Data-set coming from your application, database, or API.
- The template must be designed using a Text Editor like Word, LibreOffice or Google Docs. To inject the content dynamically you must write Tags (a.k.a. placeholder), it will be replaced with the corresponding values from the JSON data.
Carbone’s tag syntax offers a lot of possibilities! This article focuses on the essentials, but if you want to dive deeper or see more examples, check out the official documentation.
Essential Carbone Templating Syntax
1. Basic Placeholder
The placeholder tag injects the value of a property from the root of the JSON data object (d). It is the simplest way to insert dynamic data into your template. For instance, if the JSON dataset is:
{
"firstname": "John"
}
Insert the following tag in the template:
{d.firstname}
and it will be replaced with "John" in the generated document.
2. Nested Placeholder
This tag accesses nested properties in the JSON data. It allows you to traverse deeper into the JSON structure to retrieve specific values. There is no limit to access in depth of nested properties. For instance, If your JSON is:
{
"user": {
"name": "Alice"
}
}
The tag in the template file:
{d.user.name}
will be replaced with "Alice".
3. Access Parent Property
The double dot (..) notation lets you access a property of the parent object from a nested context. To access the grandparent object’s property, use three dots (...). You can chain as many dots as needed to navigate up the object hierarchy. If your JSON is:
{
"country": "France",
"movie": {
"name": "Inception",
"sub": {
"a" : "test"
}
}
}
The tag in the template:
{d.movie.sub..name}
{d.movie.sub...country}
The first tag will be replaced with "Inception".
The second tag will be replaced with "France"
4. Array Access with Static Index
This tag accesses a specific element in an array by its index using the square bracket notation [index]. It is useful for retrieving a particular item from an array without looping. If your JSON is:
{
"movies": [
{ "name": "Inception" },
{ "name": "Matrix" }
]
}
The tag in the template:
{d.movies[0].name}
will be replaced with "Inception".
5. Conditional Logic
This tag uses the :ifEQ formatter to check if a value meets a specific condition. If the condition is met, it prints the specified text. It exists many conditional formatters (e.g. :ifIN, :ifGT, etc...), and it can be chained (using :and, :or) for complex logic and used in inline conditions, or conditional sections. If your JSON is:
{
"isActive": true
"message": "Your subscription is disabled."
}
The tag in the template:
{d.isActive:ifEQ(true):show('Yes'):elseShow(.message)}
will be replaced with the static text "Yes" if the value is true, otherwise the value of the "message" field is printed.
6. Default Value
Still using conditional formatters, the :ifEM formatter checks if a value is empty (null, undefined, empty string, empty array, or empty object). If it is, it prints the specified default value. If your JSON is:
{
"user": {
"name": null
}
}
The tag in the template:
{d.user.name:ifEM:show('Unknown')}
will be replaced with the static text "Unknown".
7. Loop (Array Iteration)
Use the square bracket notation [i] to iterate over arrays, The character i is the loop iterator (automatically managed by Carbone). Then insert the [i+1] tag, which signals the end of the pattern and is automatically removed during rendering.
This allows you to repeat content for each item in an array. If your JSON is:
{
"users": [
{ "id": 1, "name": "Alice", "role": "Admin" },
{ "id": 2, "name": "Bob", "role": "Editor" },
{ "id": 3, "name": "Charlie", "role": "Viewer" }
]
}
The tag in the template:
{d.users[i].id} - {d.users[i].name}
<!-- End of loop pattern -->
{d.users[i+1]}
will print each user's id and name in a new line.
Note: You do not need to repeat each [i] tag with [i+1], just the one [i+1] is enough for Carbone to recognize the loop pattern.
8. Loop of Nested Arrays
Use the square bracket notation [i] and [i+1] (explained the previous point) to iterate over arrays, including nested ones. You can nest loops as deeply as needed. Finally, always use i as the loop iterator, even for nested arrays: Do not ever use "j" or other characters. If your JSON is:
[
{
"brand": "Toyota",
"models": [
{ "size": "Prius 4", "power": 125 },
{ "size": "Prius 5", "power": 139 }
]
},
{
"brand": "Kia",
"models": [
{ "size": "EV4", "power": 450 },
{ "size": "EV6", "power": 500 }
]
}
]
The tags loops in the template:
{d[i].brand}
Models
{d[i].models[i].size} - {d[i].models[i].power}
{d[i].models[i+1]}
{d[i+1]}
Will generate the following content:
Toyota
Models
Prius 4 - 125
Prius 5 - 139
Kia
Models
EV4 - 450
EV6 - 500
9. Filter a Loop
In a loop, it is possible to filters Arrays to only include items that meet a specific condition. Only matching items are printed in the loop. You can add as many conditions as you want, based on the attributes in your list. To filter an array, use the following syntax in your loop tag:
{d.array[i, condition1, condition2, ...]}
If you have the following JSON:
{
"items": [
{"name": "Strawberries", "price": 268, "category": "fruit"},
{"name": "Apple", "price": 50, "category": "fruit"},
{"name": "Banana", "price": 150, "category": "fruit"},
{"name": "Carrot", "price": 80, "category": "vegetable"}
]
}
The tag in the template:
{d.items[i, price > 100, category="fruit"].name}
{d.items[i+1, price > 100, category="fruit"]}
will only print "Strawberries" and "Banana". The loop prints items where the price is greater than 100 and the category is "fruit".
10. Sort a Loop
In a loop, it is possible to sorts Array by a one or multiple attributes before iterating over it. The array will be sorted in ascending order based on the first attribute, then by the second attribute if values are equal, and so on. The syntax is:
{d.array[attribute1, attribute2, ..., i]}
If your JSON is:
{
"items": [
{"name": "Apple", "power": 2},
{"name": "Banana", "power": 1}
]
}
The tag in the template:
{d.items[power, i].name}
{d.items[power+1, i+1].name}
will print "Banana" followed by "Apple". You can specify as many attributes as you want (e.g., power, name, etc.).
Important note: while sorting, the i iterator must always be the last element in the squared brackets.
11. Formatters
Formatters are functions appended to tags with a colon (:) that modify data before display. They can accept static or dynamic parameters.
11.1 Text Formatters
These formatters transform text in various ways, such as changing case, extracting substrings, replacing text, and more.
| Formatter | Description | Example |
|---|---|---|
| :lowerCase() | Converts text to lowercase | {d.name:lowerCase()} |
| :upperCase() | Converts text to uppercase | {d.name:upperCase()} |
| :substr(start, length) | Extracts a substring | {d.text:substr(0, 5)} |
| :replace(search, replace) | Replaces text | {d.text:replace('old', 'new')} |
| :len() | Returns the length of a string or array | {d.text:len()} |
| :convCRLF() | Converts line breaks to <br>
|
{d.text:convCRLF()} |
| :preserveCharRef() | Preserves character references | {d.text:preserveCharRef()} |
| :t | Translates text (Learn more on the dedicated section below) | {d.greeting:t} |
11.2 Number Formatters
These formatters format numbers, including setting precision, rounding, and performing arithmetic operations.
| Formatter | Description | Example |
|---|---|---|
| :formatN(decimals) | Formats with the specified number of decimal places. | {d.price:formatN(2)} |
| :round(decimals) | Rounds with the specified number of decimal places. | {d.value:round(2)} |
| :floor() | Rounds down to the nearest integer. | {d.value:floor()} |
| :ceil() | Rounds up to the nearest integer. | {d.value:ceil()} |
| :add(value) | Adds a value | {d.value:add(10)} |
| :sub(value) | Subtracts a value | {d.value:sub(10)} |
| :mul(value) | Multiplies by a value | {d.value:mul(2)} |
| :div(value) | Divides by a value | {d.value:div(2)} |
| :mod(value) | Modulo operation | {d.value:mod(2)} |
| :abs() | Absolute value | {d.value:abs()} |
11.3 Currency Formatters
These formatters format numbers as currency, including setting precision and converting between currencies.
| Formatter | Description | Example |
|---|---|---|
| :formatC(precision, currency) | Formats a number as currency | {d.price:formatC(2, 'USD')} |
| :convCurr(source, target) | Converts currency | {d.price:convCurr('EUR', 'USD')} |
11.4 Date Formatters
These formatters format dates according to specified patterns.
| Formatter | Description | Example |
|---|---|---|
| :formatD(pattern) | Formats a date | {d.date:formatD('YYYY-MM-DD')} |
| :formatI(pattern) | Formats an interval | {d.interval:formatI('HH:mm')} |
11.5 Array Formatters
These formatters manipulate arrays, such as joining elements, and mapping values, and performing aggregations (Process a set of values and returns a single aggregated result).
| Formatter | Description | Example |
|---|---|---|
| :arrayJoin(separator) | Joins array elements | {d.array:arrayJoin(', ')} |
| :arrayMap(separator, keySeparator, key) | Maps array elements | {d.array:arrayMap(', ', ':', 'id')} |
| :aggStr(separator) | Aggregator to concatenate array elements | {d.array[].value:aggStr(', ')} |
| :aggSum() | Aggregator to sums array elements | {d.array[].value:aggSum()} |
| :aggAvg() | Aggregator to get the average | {d.array[].value:aggAvg()} |
| :aggMin() | Aggregator to get the minimum value | {d.array[].value:aggMin()} |
| :aggMax() | Aggregator to get the maximum value | {d.array[].value:aggMax()} |
| :aggCount() | Aggregator that return the total number of elements | {d.cars[sort>20].qty:aggCount} |
| :cumSum() | Aggregator for running totals: Calculates and prints the cumulative sum of data | {d[i].qty:cumSum} {d[i+1]} |
| :cumCount() | Aggregator that print a sequential integer to each row in a list. | {d[i].qty:cumCount} {d[i+1]} |
12. Colors
The :color formatter lets you dynamically apply colors to text, paragraphs, cells, rows, or shapes in your templates. You specify the target element and the color value from your JSON data. The syntax is:
{d.colorValue:color(scope, types)}
If your JSON is:
{
"warningHexa": "#FF0000",
"highlightHexa": "#FFFF00"
}
The tag in the template:
<!-- For Text -->
{d.warningHexa:color(p)}This text will be red.
<!-- For Tables -->
{d.highlightHexa:color(cell, background)}This cell will have a yellow background.
will color the text "This text will be red." in red and the cell background of "This cell will have a yellow background." in Yellow.
13. Inject HTML from WYSIWYG or Code Editors
The :html formatter allows you to inject raw HTML strings from your JSON data directly into your template (supported formats: DOCX, ODT and PDF). Without this formatter, "<" and ">" characters are escaped, and HTML is not rendered. If your JSON is:
{
"welcomeMessage": "<strong>Welcome!</strong> Here’s your dashboard."
}
The tag in the DOCX template:
{d.welcomeMessage:html}
the welcome message is injected, and the HTML is rendered in the generated document.
14. Merge PDFs
The :appendFile(position) formatter lets you dynamically insert a PDF into your generated PDF document. The file can be provided as a Base64 string or a URL in your JSON data. If your JSON data is:
{
"contractPDF": "base64EncodedPdfFile",
"links": {
"welcomePDF": "https://domain/welcome.pdf"
}
}
The tag in the template:
{d.links.welcomePDF:appendFile(start)}
{d.contractPDF:appendFile(end)}
15. Images
To inject images dynamically, a placeholder image must be defined in the template with the tag as alternative text. Then Carbone will replace the placeholder images with images specified in your JSON data-set. In order you have to:
- Insert a placeholder/dummy image in your template.
- Place the tag in the alternative text of the placeholder image.
- Specify the image in your JSON as either a Base64-encoded string or a URL.
If your JSON data is:
{
"logoBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"profilePictureUrl": "https://example.com/profile.jpg"
}
The tag in the template:
<!-- In the alternative text of an image -->
{d.logoBase64}
will be replaced with the image from your JSON data.
16. Charts
The :chart formatter lets you generate charts using ECharts v5 configuration. Just Insert a placeholder image in your template (DOCX, ODT, etc.), then the tag must be placed in the alternative text of the placeholder image, and finally, chain the tag with the :chart formatter. If your JSON data is:
{
"salesChart": {
"type": "echarts@v5",
"width": 600,
"height": 400,
"option": {
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"yAxis": {
"type": "value"
},
"series": [{
"data": [150, 230, 224, 218, 135, 147, 260],
"type": "line"
}]
}
}
}
The tag in the template:
<!-- In the alternative text of an image -->
{d.salesChart:chart}
will render the Echart as SVG image in the generated document.
Important Note: All ECharts configuration options are supported, but external scripts or dependencies are not.
17. Barcodes
It is possible to insert barcodes into your documents using the :barcode(type) formatter. The barcode is rendered as an SVG image and can be embedded directly into your template. The option type is required to define the format of barcode expected. How to Use:
- Define the barcode data in your JSON.
- Insert a placeholder image in your template.
- Place the tag in the alternative text of the placeholder image, chain the :barcode(type) formatter (don't forget the type of barcode as argument).
If you JSON is:
{
"productCode": "123456789",
}
The tag in the template:
<!-- In the alternative text of an image -->
{d.productCode:barcode(ean13)}
Will inject a barcode image as SVG in the generated document.
18. Translations
For multi-language documents, static and dynamic translations are supported using localization dictionaries. During rendering, the language must be provided using the lang option in the report configuration. Both static and dynamic translations rely on the same dictionary.
- Static translation: use {t('key')} to translate static text in your template. The translation is based on the language set in the report options.
- Dynamic translation: Use the :t formatter to translate dynamic values from your JSON data.
If your JSON is:
{
"name": "John",
"status": "pending"
}
AND if the localization dictionary is:
{
"fr-fr" : {
"greeting" : "Bonjour",
"pending" : "En cours"
},
"en-us" : {
"greeting" : "Hello",
"pending" : "In progress"
},
}
The tag in the template:
{t('greeting')} {d.name} 👋
Payment Status: {d.status:t}.
will generate the following document if the selected language is fr:
Bonjour John 👋
Payment Status: En cours.
19. Hyperlinks
For inserting clickable links in document, insert a hyperlink in your template, and set the URL to a Carbone tag, e.g., {d.documentationUrl}, that's it. If your JSON is:
{
"title": {
"url": "https://carbone.io/documentation",
"name": "Documentation"
}
}
The tag in the template with the hyperlink {d.title.url}:
{d.title.name}
will render in the document the title "My Profile", and redirect to the page https://carbone.io/documentation.
20. Electronic Signatures
Two ways are available to inject signatures in documents:
- Insert a signature image: using dynamic images.
- Use a third-party e-signature service: To do this, prepare your document by placing tags with the :sign formatter where you want signatures to appear. If the JSON is:
{
"signer": {
"name": "John Doe",
"email": "john.doe@example.com"
}
}
The tag in the template:
{d.signer:sign}
will generate a document with the signature position and signer details. This information are used by third-party services like DocuSign, Yousign, Signwell, or Documenso to handle the signature process.
Conclusion
We’ve covered the most important and commonly used syntax of Carbone's templating. You can now automate the creation of contracts, reports, invoices, and other documents in many fields: finance, healthcare, accounting, legal, and science, more!
Feel free to join our Official Discord — it’s a friendly place to connect and get help from the Carbone community.
Enjoy creating stunning documents with Carbone! Cheers 🍻

Top comments (0)