Design

IDML templates

Design a document in Adobe InDesign, add Carbone tags, and generate it from JSON data as an IDML file.
COMMUNITY FEATURE Available for:
Carbone Cloud
Carbone On-premise
Embedded Carbone JS
  v3.0+ 

Any IDML file can be a template. Design the document in Adobe InDesign, write Carbone tags in its text frames, tables and parent pages, and export it as InDesign Markup (File > Export > InDesign Markup (IDML)). Send your JSON data to the API: you get back an IDML file with the layout, styles and colors of the template, ready to open in InDesign and export to PDF.

Basic

Write a tag in a text frame to insert a value from the JSON data. In {d.name}, d is the root of the data and name is the path to the value. The value takes the formatting of its tag: paragraph style, character style, size and color, like the product name in 50 pt.

data.jsonDownload
{
  "category": "Wireless speaker",
  "name": "Tessera One",
  "tagline": "Room-filling sound from a speaker the size of a book.",
  "price": 349,
  "monthly": 29.08,
  "release": "2026-10-15",
  "battery": 24,
  "weight": 1.8,
  "range": 30,
  "dealer": {
    "name": "Northwind Audio",
    "city": "Seattle",
    "website": "northwind.example",
    "url": "https://northwind.example/tessera-one"
  }
}
template.idmlDownload
IDML template of a product launch poster with Carbone tags in text frames, a table and the footer
report.idmlDownload
Product launch poster generated by Carbone and exported to PDF by InDesign

Learn more about tags.

Loops

A loop repeats paragraphs for each item of an array. Write the [i] tags in the paragraphs to repeat, like {d.products[i].name}, and an [i+1] tag in the paragraph after them: {d.products[i+1].name}. Carbone repeats the paragraphs with their styles and removes the [i+1] paragraph.

data.jsonDownload
{
  "season": "Fall 2026",
  "validFrom": "2026-09-01",
  "products": [
    {
      "name": "Tessera One",
      "finish": "Graphite or chalk",
      "sku": "AA-T1-100",
      "price": 349
    },
    {
      "name": "Tessera Mini",
      "finish": "Graphite, chalk or sage",
      "sku": "AA-TM-100",
      "price": 199
    },
    {
      "name": "Tessera Max",
      "finish": "Graphite",
      "sku": "AA-TX-100",
      "price": 899
    },
    {
      "name": "Halo Headphones",
      "finish": "Graphite or sand",
      "sku": "AA-HH-100",
      "price": 449
    },
    {
      "name": "Halo Buds",
      "finish": "Graphite or sand",
      "sku": "AA-HB-100",
      "price": 229
    },
    {
      "name": "Desk Stand",
      "finish": "Brushed aluminum",
      "sku": "AA-DS-100",
      "price": 79
    }
  ]
}
template.idmlDownload
IDML template of a price list with a loop on two paragraphs
report.idmlDownload
Price list with one entry per product generated by Carbone

Learn more about loops.

Nested loops

A loop can contain another loop. In this catalog, {d.lines[i].name} repeats the product lines, and {d.lines[i].items[i].name} repeats the products of each line. Write the two [i+1] tags in the same Loop end paragraph: {d.lines[i].items[i+1].name}{d.lines[i+1].name}.

data.jsonDownload
{
  "edition": "Fall 2026",
  "intro": "Speakers, headphones and accessories, designed to work together.",
  "lines": [
    {
      "name": "Speakers",
      "items": [
        { "name": "Tessera One", "sku": "AA-T1-100", "price": 349, "text": "Room-filling sound from a speaker the size of a book. 24 hours of battery." },
        { "name": "Tessera Mini", "sku": "AA-TM-100", "price": 199, "text": "The pocket version of Tessera One, with the same tuning and 16 hours of battery." },
        { "name": "Tessera Max", "sku": "AA-TX-100", "price": 899, "text": "Seven drivers and a 6.5-inch woofer for large rooms and outdoor parties." },
        { "name": "Tessera Sub", "sku": "AA-TS-100", "price": 699, "text": "A compact subwoofer that pairs with any Tessera speaker in one tap." }
      ]
    },
    {
      "name": "Headphones",
      "items": [
        { "name": "Halo Headphones", "sku": "AA-HH-100", "price": 449, "text": "Over-ear, with adaptive noise cancellation and 40 hours of battery." },
        { "name": "Halo Buds", "sku": "AA-HB-100", "price": 229, "text": "In-ear, with a charging case that adds 24 hours of listening." },
        { "name": "Halo Sport", "sku": "AA-HS-100", "price": 179, "text": "Sweat-resistant buds with ear hooks that stay in place." }
      ]
    },
    {
      "name": "Accessories",
      "items": [
        { "name": "Desk Stand", "sku": "AA-DS-100", "price": 79, "text": "Brushed aluminum stand that tilts Tessera One towards you." },
        { "name": "Wall Mount", "sku": "AA-WM-100", "price": 49, "text": "Black steel mount for Tessera One and Tessera Mini." },
        { "name": "Travel Case", "sku": "AA-TC-100", "price": 59, "text": "Padded case with room for a speaker and its cable." },
        { "name": "Charging Dock", "sku": "AA-CD-100", "price": 89, "text": "Charges a speaker and a pair of buds at the same time." }
      ]
    }
  ]
}
template.idmlDownload
IDML template of a catalog with a loop of product lines containing a loop of products
report.idmlDownload
Catalog of two pages generated by Carbone, with one heading per product line
report.idml · Page 2
Catalog of two pages generated by Carbone, with one heading per product line, Page 2

The template has one page and the report has two: InDesign adds the pages, see Text flow.

Text flow

InDesign opens the report with the pages and frames of the template, and flows the text like in any document. Here, one story runs through the four badges, in threaded text frames. The first paragraph of each attendee starts in the next frame (Paragraph panel menu > Keep Options > Start Paragraph: In Next Frame), so each attendee gets a badge.

data.jsonDownload
{
  "event": { "name": "Acme Design Summit", "city": "Lisbon 2026" },
  "attendees": [
    { "firstName": "Maya", "lastName": "Chen", "company": "Northwind Labs", "role": "Speaker" },
    { "firstName": "Lucas", "lastName": "Martin", "company": "Bluebird Logistics", "role": "Attendee" },
    { "firstName": "Amara", "lastName": "Okafor", "company": "Juniper Health", "role": "Press" },
    { "firstName": "Theo", "lastName": "Laurent", "company": "Acme Corporation", "role": "Staff" }
  ]
}
template.idmlDownload
IDML template of event badges with one story threaded through four text frames
report.idmlDownload
Four event badges generated by Carbone, one per attendee

Conditions

Conditions print a value or remove paragraphs, depending on the data:

data.jsonDownload
{
  "year": 2026,
  "previousYear": 2025,
  "yearEnd": "2026-09-30",
  "record": true,
  "revenue": 482.6,
  "growth": 18.4,
  "units": "1.2M",
  "markets": 38,
  "margin": 41.2,
  "dividend": 0.42,
  "payDate": "2027-01-15",
  "guidance": ""
}
template.idmlDownload
IDML template of an annual report page with inline conditions and conditional blocks
report.idmlDownload
Annual report page generated by Carbone, without the Guidance update block

:hideBegin and :hideEnd remove a block when the condition is true. Learn more about inline conditions and conditional blocks.

Translations

Write static text in {t( )} tags, and add :t to translate values from the data, like the session types. Carbone uses the translations of the lang of the report. This example uses "lang": "fr-fr" and the dictionary of the Translations tab. Dates and amounts follow the language: :formatD('dddd D MMMM YYYY') prints jeudi 15 octobre 2026, and :formatC prints 290,00 €.

data.jsonDownload
{
  "event": "Acme Design Summit",
  "city": "Lisbonne",
  "date": "2026-10-15",
  "venue": "Centre des congrès",
  "fee": 290,
  "sessions": [
    { "time": "09:00", "title": "Accueil et café", "type": "Break", "room": "Atrium" },
    { "time": "09:30", "title": "Le design au service du son", "type": "Keynote", "room": "A" },
    { "time": "11:00", "title": "Prototyper un objet connecté en une semaine", "type": "Workshop", "room": "B" },
    { "time": "14:00", "title": "Matériaux durables et production locale", "type": "Panel", "room": "A" },
    { "time": "16:00", "title": "Du croquis à la boutique", "type": "Talk", "room": "C" }
  ]
}
translations.jsonDownload
{
  "fr-fr": {
    "Program": "Programme",
    "Room": "Salle",
    "Registration": "Inscription",
    "Includes lunch and the evening reception": "Déjeuner et soirée inclus",
    "Break": "Pause",
    "Keynote": "Conférence plénière",
    "Workshop": "Atelier",
    "Panel": "Table ronde",
    "Talk": "Conférence"
  }
}
template.idmlDownload
IDML template of an event program with translation tags
report.idmlDownload
Event program translated in French by Carbone

A translation can be longer than the original text: leave room in the frames, or let them grow with auto-sizing. Learn more about translations.

Best practices

Automate an IDML file

To generate an IDML file from an IDML template with the Carbone API:

  1. Prepare two files: an IDML template with Carbone tags, like {d.name}, and the JSON data to populate it. You can use the files of the Loops example.
  2. Generate the IDML file 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/template HTTP 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\": $(cat loops-data.json),
    \"template\": \"$(base64 -i loops-template.idml)\",
    \"lang\": \"en-us\"
  }" \
  --output report.idml
💡 Linux users: replace base64 -i loops-template.idml with base64 -w 0 loops-template.idml

Key parameters

Parameter Description
template Your IDML template, encoded as a Base64 string.
data JSON data used to populate the template.
lang Language of the report, used by the formatters and translations: "en-us" prints {d.price:formatC(0)} as $349.
convertTo Omit it: an IDML template generates an IDML file.
?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.