Design
Markdown templates
Write a template in Markdown, add Carbone tags, and generate it from JSON data as a PDF, a Word file or an image.
COMMUNITY FEATURE
Available for:
Carbone Cloud
Carbone On-premise
Embedded Carbone JS
v5.0+
Any Markdown file can be a template. Write Carbone tags in its text and tables, send your JSON data to the API, and convert the Markdown to PDF, DOCX, ODT, PNG or JPG. AI models write Markdown natively, so Carbone turns their output into finished documents.
Basic
Carbone needs two elements to generate a document:
- JSON data from your application, database or API, like the following JSON.
- A Markdown template written in any text or code editor. The following template includes Carbone tags:
{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. This path must match the structure of your JSON. Use any valid Markdown structure: Carbone replaces only the tags and keeps the rest of the text.
{
"user": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@carbone.io"
},
"membershipLevel": "Premium"
}
# Welcome, {d.user.firstName} {d.user.lastName}!Thank you for registering with us.Your email address is: {d.user.email}Your membership level: {d.membershipLevel}
# Welcome, John Doe!Thank you for registering with us.Your email address is: john.doe@carbone.ioYour membership level: Premium
Style
A Markdown template can take the look of a DOCX or ODT template: text styles, page, header and footer. Design your company layout once in Word or LibreOffice, and every Markdown document uses it, including reports drafted by an AI model.
- Create the style template in Word or LibreOffice. Style the headings, the body text and the table text, then set up the page, header and footer.
- Upload it in Carbone Studio or with the upload API. Copy its template ID to use the latest deployed version, or its version ID to use one exact version.
- Add the tag
{o.styleSource=ID}anywhere in the Markdown template, for example on the first line.
{
"quarter": "Q3 2026",
"publishedAt": "2026-10-12",
"revenue": 4820000,
"growth": 18.4,
"newCustomers": 126,
"customers": 2140,
"retention": 112,
"regions": {
"northAmerica": 2310000,
"europe": 1480000,
"asiaPacific": 720000,
"latinAmerica": 310000
}
}


The style template sets:
- Text styles: the headings (
Heading 1,Heading 2...),Body Textfor paragraphs and list items, andTable HeadingandTable Contentsfor the text in table cells. In Word, create the last two as paragraph styles with these exact names. - Page: size, margins, header and footer, with their pictures and page numbers.
The style template is also a Carbone template. Its tags receive the data of the report, like {d.quarter} in the header. Its body is not copied into the report.
The style is applied when the Markdown template is converted to PDF. Table borders are not part of the style: tables keep thin black borders.
Loops
A loop repeats a part of the template for each item of an array in your JSON data. In Markdown templates, loops are supported inside tables. Insert the loop tags inside table cells, start the repeated row with its first tag (no leading |), and place the end iterator tag alone on the next line.
A loop needs two tags:
- Start tag: marks the beginning of the repeated block, like
{d.users[i].id}.iis the loop iterator, managed by Carbone. - End tag: marks the end of the repeated block, like
{d.users[i+1]}.i+1does not refer to an array item: it signals the end of the pattern. This line is removed from the final output.
All the content between the start and end tags is repeated: text, Carbone tags and styles.
{
"users": [
{ "id": 1, "name": "Alice", "role": "Admin" },
{ "id": 2, "name": "Bob", "role": "Editor" },
{ "id": 3, "name": "Charlie", "role": "Viewer" }
]
}
# User ListID | Name | Role
-- | ---- | ----
{d.users[i].id} | {d.users[i].name} | {d.users[i].role}
{d.users[i+1]}
# User ListID | Name | Role
-- | ---- | ----
1 | Alice | Admin
2 | Bob | Editor
3 | Charlie | Viewer
Carbone loops need no keyword like "for" or "foreach". The tags define the loop.
Conditions
Conditions show or hide content based on the data:
- Inline conditions print one value or another in the text, with the condition formatters.
- Conditional sections show or hide several paragraphs, tables or headings, with
:showBegin/showEndor:hideBegin/hideEnd. - HTML blocks with
:drop()and:keep()are coming soon.
Inline conditions
Use :ifEQ, :show and :elseShow to print one value or another, depending on the data.
{
"status": "active",
"score": 95,
"isUrgent": true
}
Status: {d.status:ifEQ('active'):show('✅ Active'):elseShow('❌ Inactive')}Score: {d.score:ifGT(90):show('Excellent'):elseShow('Good')}Priority: {d.isUrgent:ifEQ(true):show('**HIGH**'):elseShow('Normal')}
Status: ✅ ActiveScore: ExcellentPriority: **HIGH**
Conditional sections
Use :showBegin/:showEnd or :hideBegin/:hideEnd to show or hide a whole section, with its paragraphs, tables or headings, depending on the data:
:showBeginor:hideBeginmarks the start of the section to show or hide when the condition is true.:showEndor:hideEndmarks the end of the section.
showBegin / showEnd Example
The showBegin/showEnd formatters show the details section, with its list, only if the showDetails field is true: here it is false, so the section is removed.
{
"showDetails": false,
"product": "Carbone Pro"
}
Product: {d.product}{d.showDetails:ifEQ(true):showBegin}
## Details- Feature 1
- Feature 2{d.showDetails:ifEQ(true):showEnd}Thank you for using Carbone!
Product: Carbone ProThank you for using Carbone!
hideBegin / hideEnd Example
The greetings section is deleted when the field name is undefined/null/empty.
{
"name": null
}
## Dashboard{d.name:ifEM:hideBegin}
Welcome, **{d.name}**!
{d.name:ifEM:hideEnd}
## Dashboard
Drop / keep via HTML blocks
Pictures
Write a tag as the source of a Markdown image to insert a picture from the data:

The image source can be:
- An absolute URL, like
https://carbone.io/img/carbone-logo.svg. Relative paths, like/images/logo.pngor../assets/photo.jpg, are not supported. - 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..."
}
}
# Welcome, {d.userProfile.name}!
# Welcome, John Doe!
To set the size of the image, write an HTML <img> tag in the template:
<img src="{d.userProfile.profilePictureUrl}" width="200" height="auto" alt="Profile Picture">
Charts
The :chart formatter draws a chart from an ECharts configuration object in the data. Chain it to the tag of this object, and use the tag as the image source: Carbone replaces it with an SVG image of the chart.
Use an HTML <img> tag to set the width and height of the chart in the converted PDF, DOCX or ODT:
<img src="{d.chartOptions:chart}" width="600" height="400" alt="Sales Overview"/>
The Markdown image syntax  also works, but it cannot set the size, and the chart appears as a very small image.
The top-level width and height properties of the ECharts configuration set the size of the generated image, not its size in the document. Set the size in the document with the width and height attributes of the <img> tag.
Learn more about charts.
Colors
Markdown has no syntax for colors. Write tags in the style attribute of an HTML element that starts the line, like <p> or <div>, to set the colors of the text from the data.
{
"theme": "dark",
"textColor": "#ffffff",
"bgColor": "#1a1a1a",
"alertColor": "#ff4444",
"isUrgent": true
}
## Status Report<p style="color: {d.alertColor};">{d.isUrgent:ifEQ(true):show('⚠️ Urgent Alert'):elseShow('All Clear')}</p><div style="color: {d.textColor}; background-color: {d.bgColor}; padding: 10px;">
This section uses colors from the dataset.
</div>Theme: {d.theme:ifEQ('dark'):show('🌙 Dark Mode'):elseShow('☀️ Light Mode')}
## Status Report<p style="color: #ff4444;">⚠️ Urgent Alert</p><div style="color: #ffffff; background-color: #1a1a1a; padding: 10px;">
This section uses colors from the dataset.
</div>Theme: 🌙 Dark Mode
To color a word inside a sentence, put the <span> in a <p> element: <p>Status: <span style="color: {d.alertColor};">{d.status}</span></p>.
Formatters
Formatters transform a value before Carbone prints it. Chain them to a tag with :, like {d.value:formatterName(args)}. The sections below cover the most common 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"
}
## Order SummaryOrder date: {d.orderDate:formatD('DD/MM/YYYY')}
Delivery date: {d.deliveryDate:formatD('dddd DD MMMM YYYY')}
## Order SummaryOrder date: 15/03/2025
Delivery date: Tuesday 01 April 2025
For all the date patterns and options, see the Date formatter documentation.
Number formatting
:formatN() sets the number of decimals and adds the thousands separators. The output follows the lang option passed at render time.
{
"quantity": 1234567.891,
"rating": 4.5
}
Quantity: {d.quantity:formatN(2)}
Rating: {d.rating:formatN(1)}
Quantity: 1,234,567.89
Rating: 4.5
For all the options, see the Number formatter documentation.
Currency formatting
:formatC() prints an amount with its currency symbol and decimals, formatted for the language of the report.
{
"price": 4999.99,
"discount": 250
}
Price: {d.price:formatC(2, 'EUR')}
Discount: {d.discount:formatC(2, 'EUR')}
Price: 4,999.99 €
Discount: 250.00 €
For all the currencies and options, see the Currency formatter documentation.
Barcodes
The :barcode(type) formatter draws a barcode from a value. type is the barcode symbology, like qrcode, ean13 or code128.
Use an HTML <img> tag to set the width and height of the barcode in the converted PDF, DOCX or ODT:
<img src="{d.productCode:barcode(qrcode)}" width="150" height="150" alt="Product QR code"/>
The Markdown image syntax }) also works, but it cannot set the size, and the barcode appears as a very small image.
Pass the barcode options as extra arguments, like {d.productCode:barcode(qrcode, includetext:false)}. Their width and height options set the size of the generated barcode, not its size in the document. Set the size in the document with the width and height attributes of the <img> tag.
Learn more about barcodes.
Hyperlinks
Write tags in the address or the text of a Markdown link to build personalized links from the data.
{
"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"
}
}
<!-- Directly inject a URL -->
[Read the Docs]({d.documentationUrl})<!-- Conditionally set a URL -->
[{d.isLoggedIn:ifEQ(true):show('Your Profile'):elseShow('Login')}]({d.isLoggedIn:ifEQ(true):show(d.profileUrl):elseShow(d.defaultUrl)})<!-- Inject a URL from a nested object -->
[Learn about {d.product.name}]({d.product.url})<!-- Combine static and dynamic paths -->
[Documentation](/docs/{d.product.name:lowerCase():replace(' ', '-')})
[Read the Docs](https://carbone.io/documentation/)[Your Profile](https://account.carbone.io/)[Learn about Carbone Cloud](https://carbone.io/pricing.html)[Documentation](/docs/carbone-cloud)
Inject HTML in Markdown
By default, Carbone encodes the < and > characters of a value, so HTML from the data, like {d.richText}, is printed as literal text. Add the :html formatter to render it as formatted content in the converted document.
{
"richText": "<p>The engine is <u>powered</u> by <i>cryogenic liquid methane</i> and <b>liquid oxygen</b> (LOX).</p>"
}
### Engine{d.richText:html}
### Engine<p>The engine is <u>powered</u> by <i>cryogenic liquid methane</i> and <b>liquid oxygen</b> (LOX).</p>
Learn more about the :html formatter.
Translations and i18n
One Markdown template can generate documents in any language. Define a localization dictionary and pass the lang option at render time. Carbone replaces the text with its translation:
{t( )}translates the static text of the template, like labels and headings.- The
:tformatter translates values from the JSON data, used as dictionary keys.
:formatD(), :formatN() and :formatC() follow the language too.
{
"data" : { "date": "2025-03-15", "tool": "key1", "protection": "key2" },
"convertTo" : "md",
"lang" : "en-us",
"translations" : {
"fr-fr" : {
"Invoice" : "Facture",
"Date" : "Date",
"key1" : "Tournevis",
"key2" : "Gants"
},
"en-us" : {
"Invoice" : "Invoice",
"Date" : "Date",
"key1" : "Screwdrivers",
"key2" : "Gloves"
}
}
}
# {t(Invoice)}{t(Date)}: {d.date:formatD('DD/MM/YYYY')}| Item | Quantity |
| ---- | -------- |
| {d.tool:t} | 1 |
| {d.protection:t} | 2 |
# InvoiceDate: 15/03/2025| Item | Quantity |
| ---- | -------- |
| Screwdrivers | 1 |
| Gloves | 2 |
Pass the localization dictionary in the translations field of the API request body. For the complete reference, including the CLI tools and the dictionary generation, see the Translations i18n documentation.
Comments
Carbone reads all the text of the template, including HTML comments (<!-- -->). A tag inside a comment, like {d.user.name}, is replaced with data before the Markdown is rendered.
To mention a tag in a note, break its syntax so Carbone ignores it: <!-- Note: Use d.user.name (escaped) for the username -->.
{
"user": {
"name": "John"
}
}
# User Profile<!-- Printing Carbone Tags within Comments: -->
<!-- {d.user.name} -->
# User Profile<!-- Printing Carbone Tags within Comments: -->
<!-- John -->
Automate a Markdown file
Store the template once, then generate a Markdown file for each set of data. These commands use the files of the Style example.
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=@quarterly-report-template.md'
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 quarterly-report-data.json) }" \
--output report.md
Without convertTo, the report has the format of the template. To get a PDF or a DOCX, see Convert Markdown to PDF. Learn more about template storage.
Convert Markdown to PDF
Using Carbone with AI-generated content: AI assistants like ChatGPT, Gemini and Mistral Vibe write Markdown natively. Ask the assistant for a structured 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 or DOCX. It fits automated reports, personalized proposals, AI-drafted contracts and weekly summaries.
To generate a PDF from a Markdown template with the Carbone API:
- Prepare two files: a Markdown 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 document.md)\",
\"convertTo\": \"pdf\",
\"converter\": \"L\"
}" \
--output result.pdf
💡 Linux users: replacebase64 -i document.mdwithbase64 -w 0 document.md
Key parameters
| Parameter | Description |
|---|---|
template |
Your Markdown template, encoded as a Base64 string. |
data |
JSON data used to populate the template. |
convertTo |
Output format: pdf, docx, odt, jpg, png or md. |
?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.
Markdown templates do not support the PDF conversion options of HTML templates, like the paper size, margins, headers and footers. Set them with a style template instead, or use an HTML template for advanced control of the PDF layout.