Design

Markdown templates

Learn how to use Markdown as a template format in Carbone to generate PDF, DOCX, ODT, PNG, and JPG files with dynamic data injection, loops, and conditional content.
COMMUNITY FEATURE Available for:
Carbone Cloud
Carbone On-premise
Embedded Carbone JS
  v5.0+ 

Carbone supports Markdown as both an input template and an output format, enabling you to generate PDFs, DOCX, ODT, PNG, JPG, and other formats directly from Markdown templates. Since large language models like ChatGPT, Claude, and DeepSeek natively output Markdown, Carbone is the ideal bridge between AI-generated content and professional documents, turning raw LLM output into polished, data-injected deliverables in a single API call.

Markdown templates support a wide range of features, including substitutions, repetitions, formatters, translations, conditions, and all enterprise features, making them a lightweight and versatile choice for automated document generation.

Basic

Carbone needs 2 elements to generate a document:

The d in {d.user.firstName} refers to the root of your JSON data. The path after d (e.g., user.firstName) must match the structure of your JSON. You can use any valid Markdown structure; Carbone will only replace the tags with the corresponding data.

data
{
  "user": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@carbone.io"
  },
  "membershipLevel": "Premium"
}
template
# 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}
Carbone Merge Icon
result
# Welcome, John Doe!Thank you for registering with us.Your email address is: john.doe@carbone.ioYour membership level: Premium

Loops

Loops in Carbone allow you to repeat a section of your template for each item in an array from your JSON data. In Markdown templates, loops are supported inside tables. Insert the loop tags inside table cells, and place the end iterator tag in the following row.

A loop in Carbone requires two tags:

All content (text, Carbone tags, and styles) between the start and end rows is repeated.

data
{
  "users": [
    { "id": 1, "name": "Alice", "role": "Admin" },
    { "id": 2, "name": "Bob", "role": "Editor" },
    { "id": 3, "name": "Charlie", "role": "Viewer" }
  ]
}
template
# User List| ID | Name | Role |
| -- | ---- | ---- |
| {d.users[i].id} | {d.users[i].name} | {d.users[i].role} |
| {d.users[i+1]} | | |
Carbone Merge Icon
result
# User List| ID | Name | Role |
| -- | ---- | ---- |
| 1 | Alice | Admin |
| 2 | Bob | Editor |
| 3 | Charlie | Viewer |

Unlike traditional programming, Carbone loops do not require keywords like "for" or "foreach". The loop is defined implicitly by the tags.

Conditions

Three methods are available to conditionally show, hide, or style elements in your Markdown templates. Each method is suited to different use cases, depending on the complexity of your conditions and the structure of your content:

Inline Conditions

Carbone supports inline conditional logic directly within Markdown content. Use the :ifEQ, :show, and :elseShow formatters to render different values based on your data.

data
{
  "status": "active",
  "score": 95,
  "isUrgent": true
}
template
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')}
Carbone Merge Icon
result
Status: ✅ ActiveScore: ExcellentPriority: **HIGH**

Conditional Sections

The pair of :showBegin/:showEnd and :hideBegin/:hideEnd formatters allow you to conditionally display a section of content in your Markdown templates. These formatters are useful when you want to show or hide whole groups of paragraphs, tables, or headings based on data conditions. Syntax:

showBegin / showEnd Example

The details section containing a list is hidden using the showBegin/showEnd formatters, only if the showDetails field is true.

data
{
  "showDetails": false,
  "product": "Carbone Pro"
}
template
Product: {d.product}{d.showDetails:ifEQ(true):showBegin}
## Details- Feature 1
- Feature 2{d.showDetails:ifEQ(true):showEnd}Thank you for using Carbone!
Carbone Merge Icon
result
Product: Carbone ProThank you for using Carbone!

hideBegin / hideEnd Example

The greetings section is deleted when the field name is undefined/null/empty.

data
{
  "name": null
}
template
## Dashboard{d.name:ifEM:hideBegin}
Welcome, **{d.name}**!
{d.name:ifEM:hideEnd}
Carbone Merge Icon
result
## Dashboard

Drop / Keep via HTML Blocks

⚠️ Coming soon: Drop / Keep via HTML blocks is not yet supported in Markdown templates.

Pictures

Dynamic pictures allow you to inject images into your documents using data from your JSON input. In Markdown templates, use the standard Markdown image syntax with a Carbone tag as the src:

![Alt text]({d.imageUrl})

Carbone supports the following image sources:

data
{
  "userProfile": {
    "name": "John Doe",
    "profilePictureUrl": "https://carbone.io/images/john-doe.jpg",
    "profilePictureBase64": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD..."
  }
}
template
# Welcome, {d.userProfile.name}!![Profile Picture]({d.userProfile.profilePictureUrl})
Carbone Merge Icon
result
# Welcome, John Doe!![Profile Picture](https://carbone.io/images/john-doe.jpg)

To control image dimensions, use an HTML <img> tag directly in your Markdown template (Markdown supports inline HTML):

<img src="{d.userProfile.profilePictureUrl}" width="200" height="auto" alt="Profile Picture">

Charts

⚠️ Coming soon: Dynamic charts are not yet supported in Markdown templates.

Colors

You can dynamically set text colors and styles in your Markdown templates using Carbone tags combined with inline HTML. Since Markdown itself has limited native styling, use HTML <span> or <div> elements for color control.

data
{
  "theme": "dark",
  "textColor": "#ffffff",
  "bgColor": "#1a1a1a",
  "alertColor": "#ff4444",
  "isUrgent": true
}
template
## Status Report<span style="color: {d.alertColor};">{d.isUrgent:ifEQ(true):show('⚠️ Urgent Alert'):elseShow('All Clear')}</span><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')}
Carbone Merge Icon
result
## Status Report<span style="color: #ff4444;">⚠️ Urgent Alert</span><div style="color: #ffffff; background-color: #1a1a1a; padding: 10px;">
  This section uses colors from the dataset.
</div>Theme: 🌙 Dark Mode

Formatters

Carbone provides built-in formatters to transform data values directly inside your Markdown templates. Formatters are chained to a Carbone tag using the : operator (e.g. {d.value:formatterName(args)}). The sections below cover the most commonly used formatters for dates, numbers, and currencies. For the full list of available formatters, see the Formatters documentation.

Date Formatting

Use the :formatD() formatter to display dates in any format. The pattern follows Day.js tokens (e.g. YYYY, MM, DD, dddd).

data
{
  "orderDate": "2025-03-15T14:30:00Z",
  "deliveryDate": "2025-04-01T00:00:00Z"
}
template
## Order SummaryOrder date: {d.orderDate:formatD('DD/MM/YYYY')}
Delivery date: {d.deliveryDate:formatD('dddd DD MMMM YYYY')}
Carbone Merge Icon
result
## Order SummaryOrder date: 15/03/2025
Delivery date: Tuesday 01 April 2025

For the full list of supported date patterns and options, see the Date formatter documentation.

Number Formatting

Use the :formatN() formatter to control decimal precision and thousands separators. The output format automatically adapts to the lang option passed at render time.

data
{
  "quantity": 1234567.891,
  "rating": 4.5
}
template
Quantity: {d.quantity:formatN(2)}
Rating: {d.rating:formatN(1)}
Carbone Merge Icon
result
Quantity: 1,234,567.89
Rating: 4.5

For the full list of options, see the Number formatter documentation.

Currency Formatting

Use the :formatC() formatter to display monetary values with the correct currency symbol, decimal precision, and locale-aware formatting.

data
{
  "price": 4999.99,
  "discount": 250
}
template
Price: {d.price:formatC(2, 'EUR')}
Discount: {d.discount:formatC(2, 'USD')}
Carbone Merge Icon
result
Price: €4,999.99
Discount: $250.00

For the full list of supported currencies and options, see the Currency formatter documentation.

Custom Styling

⚠️ Coming soon: Custom CSS styling for Markdown templates is not yet supported.

Barcodes

⚠️ Coming soon: Dynamic barcodes are not yet supported in Markdown templates.

You can dynamically set the URL in Markdown links using Carbone tags. This is useful for generating personalized or data-driven links.

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"
    }
}
template
<!-- 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(' ', '-')})
Carbone Merge Icon
result
[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

⚠️ Coming soon: Injecting HTML into Markdown templates is not yet supported.

Page Breaks

⚠️ Coming soon: Page breaks are not yet supported in Markdown templates.

Table of Content

⚠️ Coming soon: The Table of Contents is not yet supported in Markdown templates.

Convert Markdown to PDF

Using Carbone with AI-generated content: LLMs like ChatGPT, Gemini and Le Chat (Mistral) natively output Markdown. A typical workflow is: prompt your LLM to generate a structured document → inject dynamic fields with Carbone tags (e.g. {d.clientName}, {d.date}) → call the Carbone API with your JSON data to render the final PDF or DOCX. This is particularly effective for automated reports, personalised proposals, AI-drafted contracts, and weekly summaries.

To generate a PDF from a Markdown template using the Carbone API, follow this workflow:

  1. Prepare Two Files: Create a Markdown Template (with Carbone tags e.g. {d.name}) and a JSON dataset to populate the template.
  2. Generate a PDF. You have three options:
    • Use the official Carbone integrations for platforms like N8N, Make, Zapier, Airtable, or Bubble.
    • Or, Use one of the Carbone SDKs (Node.js, Go, Python, Java, JavaScript, or PHP).
    • Or, Send a POST /render/template HTTP request to the Carbone API. Here's an example using curl:
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: replace base64 -i document.md with base64 -w 0 document.md

Key Parameters

Parameter Description
template Your Markdown template, encoded as a Base64 string.
data JSON dataset used to populate the template.
convertTo The desired 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.

Unlike HTML templates, Markdown templates do not support PDF conversion options (paper size, margins, headers, footers, etc.). For advanced PDF layout control, use an HTML template instead.

Translations and i18n

Carbone can generate documents in any language from a single Markdown template. Instead of maintaining one template per language, you define a localization dictionary and pass a lang option at render time — Carbone automatically substitutes all translated strings.

Two mechanisms are available:

Locale-aware formatters such as :formatD(), :formatN(), and :formatC() automatically adapt their output to the target language as well.

data
{
  "data"         : { "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"
    }
  }
}
template
# {t(Invoice)}{t(Date)}: {d.date:formatD('DD/MM/YYYY')}| Item | Quantity |
| ---- | -------- |
| {d.tool:t} | 1 |
| {d.protection:t} | 2 |
Carbone Merge Icon
result
# InvoiceDate: 15/03/2025| Item | Quantity |
| ---- | -------- |
| Screwdrivers | 1 |
| Gloves | 2 |

The localization dictionary can be passed directly in the API request body (translations field). For the complete reference including CLI tools and dictionary generation, see the Translations i18n documentation.

Comments

Carbone processes all text in your Markdown template, including HTML comments (<!-- -->). If you include Carbone tags (e.g., {d.user.name}) inside comments, they will be evaluated and replaced with data before the Markdown is rendered.

Best Practices for Notes: If you must include Carbone tags in comments for taking notes, you can "escape" them by breaking the syntax so Carbone won't interpret them: <!-- Note: Use d.user.name (escaped) for the username -->.

data
{
  "showComment": true,
  "user": {
    "name": "John"
  }
}
template
# User Profile<!-- Printing Carbone Tags within Comments: -->
<!-- {d.user.name} -->
Carbone Merge Icon
result
# User Profile<!-- Printing Carbone Tags within Comments: -->
<!-- John -->