Design
HTML templates
Write a template in HTML and CSS, add Carbone tags, and generate it from JSON data as a PDF or an image.
COMMUNITY FEATURE
Available for:
Carbone Cloud
Carbone On-premise
Embedded Carbone JS
v5.0+
Any HTML file can be a template. Write Carbone tags in the markup, send your JSON data to the API, and get back a PDF or an image that looks exactly like the page in a browser. Loops, conditions and formatters run inside the template, so you need no separate templating layer.
Basic
Carbone needs two inputs to generate a document:
- JSON data, from your application, database or API, like the JSON below.
- An HTML template, taken from an existing web page or email, or written in a code editor. The following template includes Carbone tags: the
{d.user.firstName},{d.user.lastName},{d.user.email}, and{d.membershipLevel}. These tags will be replaced with the corresponding values from the JSON data.
In {d.user.firstName}, d is the root of the JSON data and user.firstName is the path to the value. The path must match the structure of the JSON. Any valid HTML works: Carbone replaces only the tags.
{
"user": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@carbone.io"
},
"membershipLevel": "Premium"
}
<!DOCTYPE html>
<html>
<head>
<title>Welcome Email </title>
</head>
<body>
<h1>Welcome, {d.user.firstName} {d.user.lastName}! </h1>
<p>Thank you for registering with us. </p>
<p>Your email address is: {d.user.email} </p>
<p>Your membership level: {d.membershipLevel} </p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome Email</title>
</head>
<body>
<h1>Welcome, John Doe!</h1>
<p>Thank you for registering with us.</p>
<p>Your email address is: john.doe@carbone.io</p>
<p>Your membership level: Premium </p>
</body>
</html>
Loops
A loop repeats a part of the template for each item of an array, like the rows of a table, the items of a list or a series of images. A loop needs two tags:
- The start tag, like
{d.users[i].id}, marks the start of the repeated part.iis the loop iterator, managed by Carbone. - The end tag, like
{d.users[i+1]}, marks the end of the repeated part.i+1does not refer to an item of the array.
Carbone repeats everything between the two tags: text, tags, HTML and styles.
{
"users": [
{ "id": 1, "name": "Alice", "role": "Admin" },
{ "id": 2, "name": "Bob", "role": "Editor" },
{ "id": 3, "name": "Charlie", "role": "Viewer" }
]
}
<!DOCTYPE html>
<html>
<body>
<h1>User List</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<!-- Start of loop -->
<tr>
<td>{d.users[i].id}</td>
<td>{d.users[i].name}</td>
<td>{d.users[i].role}</td>
</tr>
<!-- End of loop -->
<tr>
<td>{d.users[i+1]}</td>
</tr>
</tbody>
</table>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<h1>User List</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Role</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Alice</td>
<td>Admin</td>
</tr>
<tr>
<td>2</td>
<td>Bob</td>
<td>Editor</td>
</tr>
<tr>
<td>3</td>
<td>Charlie</td>
<td>Viewer</td>
</tr>
</tbody>
</table>
</body>
</html>
Carbone loops need no keyword like for or foreach. The [i] and [i+1] tags define the loop.
Conditions
Show, hide or style the elements of the template based on the data, in one of three ways:
- Inline conditions set a CSS value, CSS properties, a style attribute or an HTML block from a data field. Use them to show or hide a single element.
- Smart conditions remove or keep a whole element with
:drop()or:keep(): a table, a column, a row, a paragraph, adivor aspan. - Conditional blocks show or hide a large part of the template with
:showBegin/:showEndor:hideBegin/:hideEnd, like several elements, paragraphs, tables or pages.
Inline HTML/CSS Conditions
Write a tag in a style attribute, or anywhere in the markup, to apply styles or HTML from the data. Add an inline condition to the tag to show or hide an element, or to change its style:
- Inject CSS values: set the value of a CSS property, like
display,visibilityoropacity. - Inject CSS properties: insert whole CSS properties from the data.
- Inject a style attribute: insert a full
styleattribute from a data field. - Inject HTML blocks: insert raw HTML from the data with the
:htmlformatter.
Inject CSS Values Example
In the following example, the display, visibility, and opacity properties are conditionally set using Carbone tags:
- The
<div>element is hidden ifd.showDivis false. - The <p> element is hidden if
d.showParagraphis false. - The
<span>element is invisible ifd.showSpanis false.
{
"display": "none",
"showDiv": false,
"showParagraph": false,
"showSpan": false
}
<!DOCTYPE html>
<html>
<body>
<!-- Inject CSS values -->
<div style="display:{d.display};">This element is hidden and takes no space, only if 'display' is 'none'.</div>
<!-- Inject CSS values based on conditions -->
<div style="display:{d.showDiv:ifEQ(true):show('block'):elseShow('none')};">This element is hidden and takes no space, only if 'showDiv' is 'false'.</div>
<p style="visibility:{d.showParagraph:ifEQ(true):show('visible'):elseShow('hidden')};"> This paragraph is hidden but occupies space, only if 'showParagraph' is 'false'.</p>
<span style="opacity:{d.showSpan:ifEQ(true):show('1'):elseShow('0')};">This element is invisible but still clickable, only if the 'showSpan' is 'false'.</span>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<!-- Inject CSS values -->
<div style="display:none;">This element is hidden and takes no space, only if 'display' is 'none'.</div>
<!-- Inject CSS values based on conditions -->
<div style="display:none;">This element is hidden and takes no space, only if 'showDiv' is 'false'.</div>
<p style="visibility:hidden;"> This paragraph is hidden but occupies space, only if 'showParagraph' is 'false'.</p>
<span style="opacity:0;">This element is invisible but still clickable, only if the 'showSpan' is 'false'.</span>
</body>
</html>
Inject CSS Properties Example
This example demonstrates how to dynamically inject entire CSS properties into an HTML element using Carbone tags. Two methods are available:
- Direct Injection: The first
<img>tag injects the value of d.imageStyle directly into its style attribute. - Conditional Injection: The second
<img>tag uses a condition to determine which CSS property to apply, either the value ofd.imageStyleor a static string "display:block".
{
"showImage": true,
"imageStyle": "display: none;"
}
<!DOCTYPE html>
<html>
<body>
<!-- Inject CSS properties -->
<img src="image.jpg" style="width: 150px;{d.imageStyle}border-radius: 8px;" alt="Hidden Image">
<!-- Inject CSS properties based on a condition -->
<img src="image.jpg" style="{d.showImage:ifEQ(true):show('display:block;'):elseShow(d.imageStyle)}" alt="Visible Image">
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<!-- Inject CSS properties -->
<img src="image.jpg" style="width: 150px;display: none;border-radius: 8px;" alt="Hidden Image">
<!-- Inject CSS properties based on a condition -->
<img src="image.jpg" style="display:block;" alt="Visible Image">
</body>
</html>
Inject Style Attribute Example
This example shows how to inject an entire style attribute from your dataset into an HTML element.
- Direct Injection: The first button uses the style defined in
d.buttonStyles1. - Conditional Injection: The second button applies the value of
d.buttonStyles1ifd.buttonThemeis "forest", otherwise it uses the value ofd.buttonStyles2.
{
"buttonTheme": "ocean",
"buttonStyles1": "style="background-color: green;color: white;border: none;"",
"buttonStyles2": "style="background-color: black;color: red;border: 1px solid white;""
}
<!DOCTYPE html>
<html>
<body>
<!-- 3. Inject CSS Style Attribute -->
<button {d.buttonStyles1}>Click Me</button>
<!-- Inject CSS Style Attribute based on a condition -->
<button {d.buttonTheme:ifEQ('forest'):show(d.buttonStyles1):elseShow(d.buttonStyles2)}>Click Me</button>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<!-- 3. Inject CSS Style Attribute -->
<button style="background-color: green;color: white;border: none;">Click Me</button>
<!-- Inject CSS Style Attribute based on a condition -->
<button style="background-color: black;color: red;border: 1px solid white;">Click Me</button>
</body>
</html>
Inject HTML Block
This example demonstrates how to inject raw HTML blocks from your dataset into your template using conditions and the :html formatter.
- Direct Injection: The first line directly renders the HTML stored in
d.noteSection. - Conditional Injection: The second line conditionally renders either
d.noteSectionifd.displayNoteis true, ord.nothingSectionif false.
{
"displayNote": false,
"noteSection": "<div style='border-left: 3px solid #ff9800; padding: 8px;'><strong>Note:</strong> Maintenance at 2 AM UTC.</div>",
"nothingSection": "<strong style='text-align: center; padding: 10px; background: #f0f8ff;'>Nothing Significant To Report.</strong>"
}
<!DOCTYPE html>
<html>
<body>
<!-- Inject HTML Block -->
{d.noteSection:html}
<!-- Inject HTML Block based on a condition -->
{d.displayNote:ifEQ(true):show(d.noteSection):elseShow(d.nothingSection):html}
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<!-- Inject HTML Block -->
<div style='border-left: 3px solid #ff9800; padding: 8px;'><strong>Note:</strong> Maintenance at 2 AM UTC.</div>
<!-- Inject HTML Block based on a condition -->
<strong style='text-align: center; padding: 10px; background: #f0f8ff;'>Nothing Significant To Report.</strong>
</body>
</html>
Drop / Keep
:drop() and :keep() remove or keep the element that contains their tag, like a table, a row or a paragraph, based on a condition:
:drop(element)removes the element when the condition is true.:keep(element)keeps the element when the condition is true.
| Element | Description | Tag position | Example |
|---|---|---|---|
| p | Drop/keep a paragraph | Within a paragraph | {d.text:ifEM:drop(p)} |
| row | Drop/keep a table row | In a table row | {d.condition:ifNE(value):keep(row)} |
| col | Drop/keep an entire table column | In any cell of the target column | {d.condition:ifEQ(value):drop(col)} |
| table | Drop/keep an entire table | Inside the table | {d.condition:ifEQ(value):drop(table)} |
| div | Drop/keep a div block | Within a div element | {d.condition:ifEQ(value):drop(div)} |
| span | Drop/keep a span element | Within a span element | {d.condition:ifEQ(value):drop(span)} |
- Add a count to drop or keep several paragraphs or rows:
:drop(p, 3)removes the paragraph of the tag and the next two.coldoes not accept a count. - Do not chain another formatter after
:drop()or:keep(). - HTML templates support only these elements:
table,col,row,p,divandspan. - The tag prints nothing in the report.
Drop a Table Example
{
"showTable": false,
"list": [
{
"name": "Model 5"
},
{
"name": "Model 3"
},
{
"name": "Falcon Heavy"
}
]
}
<!DOCTYPE html>
<html>
<body>
<table>{d.showTable:ifEQ(false):drop(table)}
<tr><td>{d.list[i].name}</td></tr>
<tr><td>{d.list[i+1].name}</td></tr>
</table>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
</body>
</html>
Drop a Table Row Example
[
{ "name": "Falcon 9" },
{ "name": "Model 5" },
{ "name": "Model 3" },
{ "name": "Falcon Heavy" }
]
<!DOCTYPE html>
<html>
<body>
<table>
<tr><td>{d[i].name}{d[i].name:ifIN('Falcon'):drop(row)}</td></tr>
<tr><td>{d[i+1].name}</td></tr>
</table>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<table>
<tr><td>Model 5</td></tr>
<tr><td>Model 3</td></tr>
</table>
</body>
</html>
Drop a Paragraph Example
{ "text": "" }
<!DOCTYPE html>
<html>
<body>
<p>{d.text}{d.text:ifEM:drop(p, 2)}This paragraph and the next one will be dropped if 'text' is empty.</p>
<p>This paragraph will also be dropped.</p>
<p>This paragraph will remain.</p>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<p>This paragraph will remain.</p>
</body>
</html>
Conditional Sections
:showBegin/:showEnd and :hideBegin/:hideEnd show or hide a part of the template based on a condition. Use them around several elements, like groups of paragraphs, tables or whole pages:
:showBeginor:hideBeginmarks the start of the block to show or hide when the condition is true.:showEndor:hideEndmarks the end of the block.
showBegin / showEnd Example
The details section containing a list is shown with the showBegin/showEnd formatters only if the showDetails field is true. Here showDetails is false, so the section is removed.
{
"showDetails": false,
"product": "Carbone Pro"
}
<!DOCTYPE html>
<html>
<body>
<p>Product: {d.product}</p>
{d.showDetails:ifEQ(true):showBegin}
<p>Details:</p>
<ul>
<li>Feature 1</li>
<li>Feature 2</li>
</ul>
{d.showDetails:ifEQ(true):showEnd}
<p>Thank you for using Carbone!</p>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<p>Product: Carbone Pro</p>
<p>Thank you for using Carbone!</p>
</body>
</html>
hideBegin / hideEnd Example
The greetings section is deleted when the field name is undefined/null/empty.
{
"name": null
}
<!DOCTYPE html>
<html>
<body>
<h2> Dashboard </h2>
{d.name:ifEM:hideBegin}
<strong style='margin: 0; font-size: 14px;'>
Welcome {d.name}!
</strong>
{d.name:ifEM:hideEnd}
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<h2> Dashboard </h2>
</body>
</html>
Pictures
Write a tag in the src attribute of an <img> element to insert an image from the data. The value must be one of these:
- An absolute URL, like
https://carbone.io/img/carbone-logo.svg. Relative paths are not supported, like/images/logo.pngor../assets/photo.jpg. - A Base64 data URI, like
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD....
{
"userProfile": {
"name": "John Doe",
"profilePictureUrl": "https://carbone.io/images/john-doe.jpg",
"profilePictureBase64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD..."
}
}
<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<h1>Welcome, {d.userProfile.name}!</h1>
<div>
<img src="{d.userProfile.profilePictureUrl}" alt="Profile Picture" style="width:200px; height:auto;"/>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<h1>Welcome, John Doe!</h1>
<div>
<img src="data:image/jpg...IMAGE BASE 64" alt="Profile Picture" style="width:200px; height:auto;"/>
</div>
</body>
</html>
Set the size of the image with HTML attributes or CSS:
- The
widthandheightattributes set a fixed size:<img src="{d.imageUrl}" width="300" height="200" alt="Dynamic Image">. - CSS sets the size and the aspect ratio, for example with an inline style:
<img src="{d.imageUrl}" style="width:300px; height:auto;" alt="Dynamic Image">.
Chromium has no network access when it converts HTML to PDF. Carbone downloads the images, stylesheets and fonts of the page itself. A resource that cannot be downloaded is not loaded. On-Premise, URLs that point to a local network are refused by default (see securityLevel).
Charts
Write a tag with the :chart formatter in the src attribute of an <img> element to insert a chart.
{
"chartOptions": {
"type": "echarts@v5a",
"width": 600,
"height": 400,
"theme": null,
"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"
}]
}
}
}
<html>
<body>
<div class="chart-container">
<h2 class="chart-title">Sales Overview</h2>
<img src="{d.chartOptions:chart}" alt="Chart Image" width="600" height="400">
</div>
</body>
</html>
<html>
<body>
<div class="chart-container">
<h2 class="chart-title">Sales Overview</h2>
<img src="data:image/svg+xml CHART BASE 64" alt="Chart Image" width="600" height="400">
</div>
</body>
</html>
In this example, the {d.chartOptions:chart} tag is replaced with an SVG image of the chart, which is dynamically generated based on the data provided in your dataset.
- Carbone supports
echarts@v5(by default) andecharts@v5a, optimized for SVG compatibility. - Define the style of the chart in the JSON data.
- The chart configuration does not support external dependencies, like JavaScript files.
- Set the size of the chart in one of two ways:
- The
widthandheightattributes of the<img>element set the size of the chart in the page. - The
widthandheightof the ECharts configuration set the size from the JSON data.
- The
Colors
Write a tag in a CSS color property to set the color from the data, like the theme of a document or the color of a status. Two ways are available:
- Direct injection: insert the color value from the data in the
styleattribute. - Conditional injection: choose the color with
:ifEQ,:showand:elseShow.
{
"theme": "dark",
"textColor": "#ffffff",
"bgColor": "#1a1a1a",
"alertColor": "#ff4444",
"isUrgent": true
}
<!DOCTYPE html>
<html>
<body>
<!-- Directly inject color values -->
<div style="color: {d.textColor}; background-color: {d.bgColor}; padding: 10px;">
This div uses colors from the dataset.
</div>
<!-- Conditionally apply colors -->
<p style="color: {d.isUrgent:ifEQ(true):show(d.alertColor):elseShow('#333333')};">
{d.isUrgent:ifEQ(true):show('Urgent message!'):elseShow('Normal message.')}
</p>
<!-- Apply colors based on a theme -->
<button style="
color: {d.theme:ifEQ('dark'):show('#ffffff'):elseShow('#000000')};
background-color: {d.theme:ifEQ('dark'):show('#333333'):elseShow('#ffffff')};
border: none;
padding: 8px 16px;
">
{d.theme:ifEQ('dark'):show('Dark Mode'):elseShow('Light Mode')}
</button>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<div style="color: #ffffff; background-color: #1a1a1a; padding: 10px;">
This div uses colors from the dataset.
</div>
<p style="color: #ff4444;">
Urgent message!
</p>
<button style="color: #ffffff; background-color: #333333; border: none; padding: 8px 16px;">
Dark Mode
</button>
</body>
</html>
Formatters
Formatters transform a value in the template. Chain a formatter to a tag with :, like {d.value:formatterName(args)}. This section covers the formatters for dates, numbers and currencies. For the full list, see the formatters documentation.
Date Formatting
:formatD() prints a date in the format of your choice. The pattern uses Day.js tokens, like YYYY, MM, DD or dddd.
{
"orderDate": "2025-03-15T14:30:00Z",
"deliveryDate": "2025-04-01T00:00:00Z"
}
<!DOCTYPE html>
<html>
<body>
<p>Order date: {d.orderDate:formatD('DD/MM/YYYY')}</p>
<p>Delivery date: {d.deliveryDate:formatD('dddd DD MMMM YYYY')}</p>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<p>Order date: 15/03/2025</p>
<p>Delivery date: Tuesday 01 April 2025</p>
</body>
</html>
For all the date patterns and options, see the date formatter.
Number Formatting
:formatN() sets the number of decimals and adds thousands separators. The output follows the lang option of the request.
{
"quantity": 1234567.891,
"rating": 4.5
}
<!DOCTYPE html>
<html>
<body>
<p>Quantity: {d.quantity:formatN(2)}</p>
<p>Rating: {d.rating:formatN(1)}</p>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<p>Quantity: 1,234,567.89</p>
<p>Rating: 4.5</p>
</body>
</html>
For all the options, see the number formatter.
Currency Formatting
:formatC() prints an amount with its currency symbol and decimals, formatted for the lang of the report.
{
"price": 4999.99,
"discount": 250
}
<!DOCTYPE html>
<html>
<body>
<p>Price: {d.price:formatC(2, 'EUR')}</p>
<p>Discount: {d.discount:formatC(2, 'EUR')}</p>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<p>Price: 4,999.99 €</p>
<p>Discount: 250.00 €</p>
</body>
</html>
For all the currencies and options, see the currency formatter.
Barcodes
The :barcode formatter inserts a barcode as an SVG image:
- Write a tag with
:barcodein thesrcattribute of an<img>element. - Set the barcode type, like
qrcode,code128orean13. See the full list of supported barcode types. - Optional: set the size in the attributes of the
<img>element or in the formatter.
{
"productCode": "123456789",
"qrData": "https://example.com/product/123456789"
}
<!DOCTYPE html>
<html>
<body>
<!-- QR Code (dimensions set via img tag) -->
<img src="{d.qrData:barcode(qrcode)}" alt="QR Code" width="200" height="200">
<!-- EAN-13 Barcode (dimensions set via formatter) -->
<img src="{d.productCode:barcode(ean13, width:40, height:20)}" alt="EAN-13 Barcode">
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<!-- QR Code -->
<img src="data:image/svg+xml;base64,...[SVG_DATA]..." alt="QR Code" width="200" height="200">
<!-- EAN-13 Barcode -->
<img src="data:image/svg+xml;base64,...[SVG_DATA]..." alt="EAN-13 Barcode">
</body>
</html>
Hyperlinks
Write a tag in the href attribute of an <a> element to build the link from the data, like a personalized URL.
{
"documentationUrl": "https://carbone.io/documentation/",
"isLoggedIn": true,
"profileUrl": "https://account.carbone.io/",
"defaultUrl": "https://account.carbone.io/login",
"product": {
"name": "Carbone Cloud",
"url": "https://carbone.io/pricing.html"
}
}
<!DOCTYPE html>
<html>
<body>
<!-- Directly inject a URL -->
<a href="{d.documentationUrl}">Read the Docs</a>
<!-- Conditionally set a URL -->
<a href="{d.isLoggedIn:ifEQ(true):show(d.profileUrl):elseShow(d.defaultUrl)}">
{d.isLoggedIn:ifEQ(true):show('Your Profile'):elseShow('Login')}
</a>
<!-- Inject a URL from a nested object -->
<a href="{d.product.url}">Learn about {d.product.name}</a>
<!-- Combine static and dynamic paths -->
<a href="/docs/{d.product.name:lowerCase():replace(' ', '-')}">Documentation</a>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<a href="https://carbone.io/documentation/">Read the Docs</a>
<a href="https://account.carbone.io/">Your Profile</a>
<a href="https://carbone.io/pricing.html">Learn about Carbone Cloud</a>
<a href="/docs/carbone-cloud">Documentation</a>
</body>
</html>
Inject HTML in HTML
Add the :html formatter to a tag to insert HTML from the data as markup. Without it, Carbone encodes the < and > characters as < and >, and the HTML is printed as text.
{
"welcomeMessage": "<strong>Welcome!</strong> Here’s your dashboard.",
"alertMessage": "<div class='alert'>⚠️ Maintenance at 2 AM UTC.</div>",
"isAdmin": true,
"adminPanel": "<a href='/admin'>Go to Admin Panel</a>",
"userPanel": "<a href='/profile'>Go to Profile</a>"
}
<!DOCTYPE html>
<html>
<body>
<!-- Without :html (HTML is escaped) -->
<p>{d.welcomeMessage}</p>
<!-- With :html (HTML is rendered) -->
<p>{d.welcomeMessage:html}</p>
<!-- Conditional HTML injection -->
{d.isAdmin:ifEQ(true):show(d.adminPanel):elseShow(d.userPanel):html}
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<!-- Escaped HTML (displayed as text) -->
<p><strong>Welcome!</strong> Here’s your dashboard.</p>
<!-- Rendered HTML -->
<p><strong>Welcome!</strong> Here’s your dashboard.</p>
<!-- Conditionally injected HTML -->
<a href='/admin'>Go to Admin Panel</a>
</body>
</html>
Page Breaks
To start a new page in the PDF, define this CSS class in the <style> element of the template:
.page-break {
page-break-before: always;
break-before: page;
}
Then add a <div> or a <p> with the page-break class where the new page starts:
<div>Page 1 content</div>
<p class="page-break"></p>
<div>Page 2 content</div>
Page breaks apply to PDF output only. HTML and image output ignore them.
Table of Content
A table of contents links to the sections and subsections of a long document. Build it with standard HTML elements and internal links, and fill it and the headings with Carbone tags:
- List the sections. Write a
<ul>list. Each<li>item contains an<a>link whosehrefattribute points to theidof a heading. - Add an id to each heading. Add an
idattribute to the heading of each section and subsection, like<h1>or<h2>. The links of the table of contents point to these ids. - Style the table of contents with CSS (optional).
{
"sections": [
{
"id": 1,
"title": "First Point Header",
"subSections": [
{
"id": 11,
"title": "First Sub Point 1",
"content": "This is the content for the first sub-point. You can add as much detail as needed here."
},
{
"id": 12,
"title": "First Sub Point 2",
"content": "This is the content for the second sub-point. More details or paragraphs can go here."
}
]
},
{
"id": 2,
"title": "Second Point Header",
"subSections": []
},
{
"id": 3,
"title": "Third Point Header",
"subSections": []
}
]
}
<!DOCTYPE html>
<html>
<head>
<title>TOC Example</title>
<style>
.page-break {
page-break-before: always;
break-before: page;
}
</style>
</head>
<body>
<div id="toc_container">
<p class="toc_title">Contents</p>
<ul class="toc_list">
{d.sections[i]}
<li>
<a href="#section_{d.sections[i].id}">{d.sections[i].title}</a>
<ul>
<li><a href="#subSection_{d.sections[i].subSections[i].id}">{d.sections[i].subSections[i].title}</a>
</li>
</ul>
{d.sections[i].subSections[i+1]}
</li>
{d.sections[i+1]}
</ul>
</div>
<div>
{d.sections[i]}
<p class="page-break"></p>
<h1 id="section_{d.sections[i].id}">{d.sections[i].title}</h1>
<h2 id="subSection_{d.sections[i].subSections[i].id}">{d.sections[i].subSections[i].title}</h2>
<p>{d.sections[i].subSections[i].content}</p>
{d.sections[i+1].subSections[i+1]}
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>TOC Example</title>
<style>
.page-break {
page-break-before: always;
break-before: page;
}
</style>
</head>
<body>
<div id="toc_container">
<p class="toc_title">Contents</p>
<ul class="toc_list">
<li>
<a href="#section_1">First Point Header</a>
<ul>
<li><a href="#subSection_11">First Sub Point 1</a>
</li>
</ul>
<ul>
<li><a href="#subSection_12">First Sub Point 2</a>
</li>
</ul>
</li>
<li>
<a href="#section_2">Second Point Header</a>
</li>
<li>
<a href="#section_3">Third Point Header</a>
</li>
</ul>
</div>
<div>
<p class="page-break"></p>
<h1 id="section_1">First Point Header</h1>
<h2 id="subSection_11">First Sub Point 1</h2>
<p>This is the content for the first sub-point. You can add as much detail as needed here.</p>
<h2 id="subSection_12">First Sub Point 2</h2>
<p>This is the content for the second sub-point. More details or paragraphs can go here.</p>
<p class="page-break"></p>
<h1 id="section_2">Second Point Header</h1>
<p class="page-break"></p>
<h1 id="section_3">Third Point Header</h1>
</div>
</body>
</html>
Page Formats
Carbone generates PDFs in the A4 format by default. To set another standard page size, add the <carbone-pdf-options> custom element to the HTML template and set its paper-size option:
| Value | Description |
|---|---|
A0, A1, A2, A3, A4, A5, A6 |
ISO A-series paper sizes |
Letter |
US Letter (8.5 × 11 in) |
Legal |
US Legal (8.5 × 14 in) |
Tabloid |
Tabloid (11 × 17 in) |
Ledger |
Ledger (17 × 11 in) |
The PDF conversion options also set the margins, the scale, the display of headers and footers, and more.
Example
This sets the PDF to A3 size in landscape orientation.
<carbone-pdf-options paper-size="A3" landscape="true" />
Headers Footers
Add a header and a footer to every page of the PDF with two custom elements:
<carbone-pdf-header>defines the header of every page. See the header example.<carbone-pdf-footer>defines the footer of every page. See the footer example.
Headers and footers contain text, Carbone tags and page numbers:
<span class="pageNumber"></span>prints the current page number.<span class="totalPages"></span>prints the total number of pages.
Style them with inline CSS only: external stylesheets are not supported. Headers and footers apply to PDF output only. HTML output and image output (PNG, JPG, WebP) ignore them. They render in the PDF even when they are hidden or commented out in the HTML template.
Limitation
The header and the footer are the same on every page. You cannot turn a <carbone-pdf-header> or a <carbone-pdf-footer> on or off, or change it, on a specific page. For example, a CSS rule like .pageNumber:first-child { display: none; } does not hide the page number on the cover page: the rule is ignored, with no error. We are working on this limitation. Until then, use one of these workarounds:
- Split the document into two HTML templates. Put the cover page in a template without
<carbone-pdf-footer>, and the body of the document in a second template with<carbone-pdf-footer>. Join them with the:appendTemplateformatter. Page numbering restarts at 1 in the second template, because the two PDFs are merged after rendering. - Use a DOCX, PPTX or XLSX template. Office formats support different headers and footers on the first page, on odd and even pages, or per section. A single template can have a cover page without a footer and a footer on the other pages, with no second template to merge.
Custom Header Example
This example demonstrates a custom PDF header with dynamic content:
- Displays a centered title (Company Report | Q3 Financials).
- Shows the current date (Generated: 2025-10-17) using Carbone's built-in
{c.now}chained with:formatD('YYYY-MM-DD')to format dates. - Includes the user's name (User: John Doe) from the dataset.
{
"reportTitle": "Q3 Financials",
"user": { "name": "John Doe" }
}
<carbone-pdf-header>
<div style="font-size: 12px; text-align: center; width: 100%;">
<span>Company Report | {d.reportTitle}</span>
<span>Generated: {c.now:formatD('YYYY-MM-DD')}</span>
<span>User: {d.user.name}</span>
</div>
</carbone-pdf-header>
<carbone-pdf-header>
<div style="font-size: 12px; text-align: center; width: 100%;">
<span>Company Report | Q3 Financials</span>
<span>Generated: 2025-10-17</span>
<span>User: John Doe</span>
</div>
</carbone-pdf-header>
Custom Footer Example
This example demonstrates a custom PDF footer with:
- Page numbering (e.g., "Page 5 of 10") using
<span class="pageNumber"></span>and<span class="totalPages"></span>. - Dynamic copyright text injected from the dataset (
d.yearandd.company).
{
"year": "2025",
"company": "Acme Inc"
}
<carbone-pdf-footer>
<div style="font-size: 10px; text-align: center; width: 100%;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
<div style="font-size: 9px; text-align: right;">
© {d.year} {d.company}. All rights reserved.
</div>
</carbone-pdf-footer>
<carbone-pdf-footer>
<div style="font-size: 10px; text-align: center; width: 100%;">
Page 5 of 10
</div>
<div style="font-size: 9px; text-align: right;">
© 2025 Acme Inc. All rights reserved.
</div>
</carbone-pdf-footer>
Translations and i18n
Generate a report in any language from one HTML template. Define a translation dictionary, and set the lang option of the request. Carbone replaces the text with its translation in that language:
{t( )}translates static text of the template, like labels and headings.- The
:tformatter translates a value from the JSON data, used as a key of the dictionary.
Formatters like :formatD(), :formatN() and :formatC() follow the language too.
{
"data" : { "date": "2025-03-15", "tool": "key1", "protection": "key2" },
"convertTo" : "pdf",
"lang" : "en-us",
"translations" : {
"fr-fr" : {
"Invoice" : "Facture",
"Date" : "Date",
"key1" : "Tournevis",
"key2" : "Gants"
},
"en-us" : {
"Invoice" : "Invoice",
"Date" : "Date",
"key1" : "Screwdrivers",
"key2" : "Gloves"
}
}
}
<!DOCTYPE html>
<html>
<body>
<h1>{t(Invoice)}</h1>
<p>{t(Date)}: {d.date:formatD('DD/MM/YYYY')}</p>
<table>
<thead>
<tr><th>Item</th><th>Quantity</th></tr>
</thead>
<tbody>
<tr><td>{d.tool:t}</td><td>1</td></tr>
<tr><td>{d.protection:t}</td><td>2</td></tr>
</tbody>
</table>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<h1>Invoice</h1>
<p>Date: 15/03/2025</p>
<table>
<thead>
<tr><th>Item</th><th>Quantity</th></tr>
</thead>
<tbody>
<tr><td>Screwdrivers</td><td>1</td></tr>
<tr><td>Gloves</td><td>2</td></tr>
</tbody>
</table>
</body>
</html>
Send the dictionary in the translations field of the API request body. For the full reference, with the CLI tools and the generation of dictionaries, see the translations documentation.
Comments
Carbone reads the whole template, HTML comments (<!-- -->) included. A tag inside a comment, like {d.user.name}, is replaced with its value.
- Notes about tags: to mention a tag in a comment without Carbone replacing it, break its syntax:
<!-- Note: Use d.user.name (escaped) for the username -->. - Comments from the data: to insert a whole HTML comment from the data, add
:htmlat the end of the tag, as in Inject HTML in HTML.
{
"showComment": true,
"user": {
"name": "John"
},
"comment": "<!-- Injected -->"
}
<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<!-- Printing Carbone Tags within Comments: -->
<!-- {d.user.name} -->
<!-- Inject a whole comment section coming from the JSON data-set, using the :html formatter: -->
{d.showComment:ifEQ(true):show(.comment):elseShow(''):html}
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<!-- Printing Carbone Tags within Comments: -->
<!-- John -->
<!-- Inject a whole comment section coming from the JSON data-set, using the :html formatter: -->
<!-- Injected -->
</body>
</html>
Automate an HTML file
Store the template once, then generate an HTML file for each set of data.
1. Upload the template. Carbone returns its template ID in data.id:
curl --request POST 'https://api.carbone.io/template' \
--header 'carbone-version: 5' \
--header 'Authorization: Bearer API_TOKEN' \
--form 'versioning=true' \
--form 'template=@template.html'
2. Generate the document with the template ID and the JSON data:
curl --request POST 'https://api.carbone.io/render/TEMPLATE_ID?download=true' \
--header 'carbone-version: 5' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer API_TOKEN' \
--data-raw "{ \"data\": $(cat data.json) }" \
--output report.html
Without convertTo, the report has the format of the template. To get a PDF or an image, see Convert HTML to PDF. Learn more about template storage.
Convert HTML to PDF
Using Carbone with AI-generated content: AI assistants like ChatGPT, Claude and Gemini write HTML templates. Ask the assistant for a structured HTML document, add Carbone tags for the dynamic fields, like {d.clientName} or {d.date}, then call the Carbone API with your JSON data to render the PDF. It fits automated invoices, reports, contracts and certificates.
To generate a PDF from an HTML template with the Carbone API:
- Prepare two files: an HTML template with Carbone tags, like
{d.name}, and the JSON data to populate it. - Generate the PDF in one of three ways:
- Use an official Carbone integration, like N8N, Make, Zapier, Airtable or Bubble.
- Use a Carbone SDK: Node.js, Go, Python, Java, JavaScript or PHP.
- Send a
POST /render/templateHTTP request to the Carbone API, like this curl command:
curl --location --request POST 'https://api.carbone.io/render/template?download=true' \
--header 'carbone-version: 5' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer API_TOKEN' \
--data-raw "{
\"data\": {},
\"template\": \"$(base64 -i webpage.html)\",
\"convertTo\": \"pdf\",
\"converter\": \"C\"
}" \
--output result.pdf
💡 Linux users: replacebase64 -i webpage.htmlwithbase64 -w 0 webpage.html
Key parameters
| Parameter | Description |
|---|---|
template |
Your HTML template, encoded as a Base64 string. |
data |
JSON data used to populate the template. |
convertTo |
"pdf", or an image format. See Convert HTML to Image. |
converter |
Required: Chromium "C". The output matches the page in Chrome. |
?download=true |
Query parameter to download the document as a stream. |
Authorization |
Header required for the Carbone Cloud API. Get your API key here. |
For more details, refer to the Carbone API Documentation.
Convert HTML to Image
Chromium renders the HTML template to a PNG, JPG/JPEG or WebP image. The request is the same as for PDF: set convertTo to the image format, or to an object with formatName and formatOptions.
curl --location --request POST 'https://api.carbone.io/render/template?download=true' \
--header 'carbone-version: 5' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer API_TOKEN' \
--data-raw "{
\"data\": {},
\"template\": \"$(base64 -i webpage.html)\",
\"convertTo\": { \"formatName\": \"png\", \"formatOptions\": { \"PixelWidth\": 1200, \"Scale\": 2 } },
\"converter\": \"C\"
}" \
--output result.png
💡 Linux users: replacebase64 -i webpage.htmlwithbase64 -w 0 webpage.html
See Image options for the full rules, the limits and the error messages.
PDF Conversion Options
To customize the PDF, add the <carbone-pdf-options/> element to the HTML template and set the options as attributes. The options apply to PDF output only. Image output ignores them.
Example
This configures the PDF to use A3 size, landscape orientation, a 2.5-inch bottom margin, and disables background printing.
<carbone-pdf-options
paper-size="A3"
landscape="true"
margin-bottom="2.5"
print-background="false"
/>
Available Options
| Option | Type | Description |
|---|---|---|
landscape |
boolean | Landscape paper orientation. Defaults to false. |
display-header-footer |
boolean | Displays the header and the footer. Defaults to false, or true when the template has a header or a footer. |
print-background |
boolean | Prints the background graphics. Defaults to true. |
scale |
number | Scale of the page rendering. Defaults to 1. |
paper-size |
string | Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5 or A6. Defaults to A4. |
paper-width |
number | Overrides the paper width, in inches. Defaults to 8.27 (A4). |
paper-height |
number | Overrides the paper height, in inches. Defaults to 11.7 (A4). |
margin-top |
number | Overrides the top margin, in inches. Defaults to 0.5. |
margin-bottom |
number | Overrides the bottom margin, in inches. Defaults to 0.5. |
margin-left |
number | Overrides the left margin, in inches. Defaults to 0.5. |
margin-right |
number | Overrides the right margin, in inches. Defaults to 0.5. |
page-ranges |
string | Pages to print, like 1-5, 8, 11-13. Pages are printed in document order, and each page is printed once. An empty string prints the whole document. Invalid ranges are ignored. A range whose start page is greater than its end page returns an error. |
prefer-css-page-size |
boolean | Uses the page size defined in the CSS. Defaults to false: the content is scaled to fit the paper size. |
generate-tagged-pdf |
boolean | Generates a tagged PDF (PDF/UA) for accessibility, with a semantic structure for screen readers. Defaults to false. |