Design

TXT templates

Write a plain text template, add Carbone tags, and generate text files from JSON data, like SMS, receipts, bank files and ZPL labels.
COMMUNITY FEATURE Available for:
Carbone Cloud
Carbone On-premise
Embedded Carbone JS
  v5.0+ 

Any plain text file can be a template. Write Carbone tags in it, send your JSON data to the API, and get back a text file with the same characters, spaces and line breaks. Use it for messages, printer code and files read by other systems.

Basic

Write a tag where a value goes. In {d.customer.firstName}, d is the root of the data and customer.firstName is the path to the value. The rest of the file is printed as is, like the text of this SMS.

data.jsonDownload
{
  "carrier": "Acme Logistics",
  "customer": { "firstName": "Jane" },
  "parcel": {
    "tracking": "AL7829341056",
    "deliveryDate": "2026-11-18",
    "slot": "morning"
  },
  "trackingUrl": "https://track.acme.example/AL7829341056"
}
template.txtDownload
Hi {d.customer.firstName}, your parcel {d.parcel.tracking} arrives on {d.parcel.deliveryDate:formatD('dddd, MMMM D')} ({d.parcel.slot}).
Track or reschedule: {d.trackingUrl}
{d.carrier}
report.txtDownload
Hi Jane, your parcel AL7829341056 arrives on Wednesday, November 18 (morning).
Track or reschedule: https://track.acme.example/AL7829341056
Acme Logistics

Learn more about tags.

Loops

A loop repeats the text from its [i] tag to its [i+1] tag, for each item of an array:

In this receipt, :padr and :padl add spaces to align the columns, :substr(0, 24) cuts the long names, and :aggSum computes the subtotal.

data.jsonDownload
{
  "receipt": { "id": "R-104582", "date": "2026-11-09", "cashier": "Maria" },
  "items": [
    { "name": "Hex bolts M8x40, box of 50", "qty": 2, "price": 12.49 },
    { "name": "Safety glasses, clear", "qty": 1, "price": 8.99 },
    { "name": "Nitrile gloves L, 100 pcs", "qty": 3, "price": 14.5 },
    { "name": "Measuring tape 25 ft", "qty": 1, "price": 19.95 }
  ],
  "taxRate": 0.0725,
  "payment": { "card": "VISA", "last4": "4242" }
}
template.txtDownload
              ACME HARDWARE
     1200 Industrial Pkwy, Columbus OH
             (614) 555-0142

Receipt {d.receipt.id:padr(14)}{d.receipt.date:formatD('ll'):padl(20)}
Cashier: {d.receipt.cashier}
------------------------------------------
ITEM                     QTY        AMOUNT
{d.items[i].name:substr(0, 24):padr(24)}{d.items[i].qty:padl(5)}{d.items[i].qty:mul(.price):formatN(2):padl(13)}
{d.items[i+1]}------------------------------------------
{d.items[].qty:mul(.price):aggSum:set(c.subtotal)}SUBTOTAL{c.subtotal:formatN(2):padl(34)}
TAX{c.subtotal:mul(d.taxRate):formatN(2):padl(39)}
TOTAL{c.subtotal:mul(d.taxRate):add(c.subtotal):formatN(2):padl(37)}

Paid by {d.payment.card} ending in {d.payment.last4}

          Thank you for shopping!
report.txtDownload
              ACME HARDWARE
     1200 Industrial Pkwy, Columbus OH
             (614) 555-0142

Receipt R-104582               Nov 9, 2026
Cashier: Maria
------------------------------------------
ITEM                     QTY        AMOUNT
Hex bolts M8x40, box of     2        24.98
Safety glasses, clear       1         8.99
Nitrile gloves L, 100 pc    3        43.50
Measuring tape 25 ft        1        19.95
------------------------------------------
SUBTOTAL                             97.42
TAX                                   7.06
TOTAL                               104.48

Paid by VISA ending in 4242

          Thank you for shopping!

Learn more about loops, aggregators and :set.

Conditions

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

This order confirmation is the plain-text part of an email. The discount line is removed because the discount is 0.

data.jsonDownload
{
  "customer": { "firstName": "Jane" },
  "order": {
    "id": "SO-58213",
    "subtotal": 97.42,
    "discount": 0,
    "total": 97.42,
    "paid": false,
    "pickup": true,
    "readyDate": "2026-11-12",
    "giftMessage": "Happy birthday, Tom!"
  },
  "store": { "name": "Acme Hardware Columbus", "address": "1200 Industrial Pkwy, Columbus OH" }
}
template.txtDownload
Subject: Order {d.order.id} confirmed

Hi {d.customer.firstName},

Thanks for your order {d.order.id}.

Subtotal: {d.order.subtotal:formatC}{d.order.discount:ifEQ(0):hideBegin}
Discount: -{d.order.discount:formatC}{d.order.discount:hideEnd}
Total:    {d.order.total:formatC} ({d.order.paid:ifEQ(true):show('paid'):elseShow('pay at pickup')}){d.order.pickup:ifEQ(false):hideBegin}

Pick it up at {d.store.name}, {d.store.address},
from {d.order.readyDate:formatD('dddd, MMMM D')}. Bring this email.{d.order.pickup:hideEnd}{d.order.giftMessage:ifEM:hideBegin}

Gift message: "{d.order.giftMessage}"{d.order.giftMessage:hideEnd}

Questions? Reply to this email.
Acme Hardware
report.txtDownload
Subject: Order SO-58213 confirmed

Hi Jane,

Thanks for your order SO-58213.

Subtotal: $97.42
Total:    $97.42 (pay at pickup)

Pick it up at Acme Hardware Columbus, 1200 Industrial Pkwy, Columbus OH,
from Thursday, November 12. Bring this email.

Gift message: "Happy birthday, Tom!"

Questions? Reply to this email.
Acme Hardware

The tags can also start the lines, like loops: the begin tag at the start of the first line of the block, and the end tag at the start of the line after it. Both placements remove the block without leaving an empty line. Learn more about inline conditions and conditional blocks.

Fixed-width files

Banks, payroll providers and legacy systems often read fixed-width files: each field starts at a set position and has a set length. Build the fields with formatters:

This payment file has a header record H, one detail record D per payment, and a trailer record T with the number of payments and their total. Each record is 64 characters long.

data.jsonDownload
{
  "company": { "id": "ACME000142", "name": "Acme Corporation" },
  "batch": { "date": "2026-11-12" },
  "payments": [
    { "account": "4471209386", "name": "Müller Präzisionsteile GmbH", "amount": 12480, "reference": "INV-2041" },
    { "account": "88120034", "name": "Café Lumière", "amount": 356.9, "reference": "INV-2042" },
    { "account": "5500123497", "name": "Northwind Industrial Supply Co.", "amount": 7015.25, "reference": "INV-2043" }
  ]
}
template.txtDownload
H{d.batch.date:formatD('YYYYMMDD')}{d.company.id:padr(10)}{d.company.name:upperCase:substr(0, 45):padr(45)}
{d.payments[i]}D{d.payments[i].account:padl(18, '0')}{d.payments[i].name:unaccent:upperCase:substr(0, 25):padr(25)}{d.payments[i].amount:mul(100):round(0):padl(12, '0')}{d.payments[i].reference:substr(0, 8):padr(8)}
{d.payments[i+1]}T{d.payments:len:padl(6, '0')}{d.payments[].amount:aggSum:mul(100):round(0):padl(15, '0'):padr(57)}
report.txtDownload
H20261112ACME000142ACME CORPORATION                             
D000000004471209386MULLER PRAZISIONSTEILE GM000001248000INV-2041
D000000000088120034CAFE LUMIERE             000000035690INV-2042
D000000005500123497NORTHWIND INDUSTRIAL SUPP000000701525INV-2043
T000003000000001985215                                          

Learn more about :padl, :padr and :substr.

ZPL labels

Zebra label printers read ZPL, a text language: ^FO places a field, ^FD holds its text and ^BC draws a Code 128 barcode. Write the ZPL of your label with Carbone tags in the ^FD fields, then send the report to the printer. A loop around ^XA and ^XZ prints one shipping label per package.

data.jsonDownload
{
  "orderId": "SO-58213",
  "service": "GROUND",
  "shipper": { "name": "Acme Hardware DC", "street": "4500 Logistics Way", "city": "Columbus, OH 43217" },
  "recipient": {
    "name": "Ana Sánchez",
    "company": "Northwind Industrial Supply",
    "street": "780 Harbor Blvd",
    "city": "Long Beach, CA 90802"
  },
  "packages": [
    { "number": 1, "weight": 12.4, "tracking": "1AC2026111200001" },
    { "number": 2, "weight": 8.75, "tracking": "1AC2026111200002" }
  ]
}
template.txtDownload
{d.packages[i]}^XA
^CI28
^PW812^LL1218
^FO40,40^A0N,28,28^FDFROM: {d.shipper.name}^FS
^FO40,75^A0N,28,28^FD{d.shipper.street}, {d.shipper.city}^FS
^FO40,130^GB732,3,3^FS
^FO40,160^A0N,30,30^FDSHIP TO:^FS
^FO40,205^A0N,45,45^FD{d.recipient.name}^FS
^FO40,260^A0N,45,45^FD{d.recipient.company}^FS
^FO40,315^A0N,45,45^FD{d.recipient.street}^FS
^FO40,370^A0N,45,45^FD{d.recipient.city}^FS
^FO40,450^GB732,3,3^FS
^FO40,480^A0N,70,70^FD{d.service}^FS
^FO500,495^A0N,40,40^FDPKG {d.packages[i].number} OF {d.packages:len}^FS
^FO80,600^BY3^BCN,220,Y,N,N^FD{d.packages[i].tracking}^FS
^FO40,920^A0N,30,30^FDORDER {d.orderId}  WEIGHT {d.packages[i].weight:formatN(1)} LB^FS
^XZ
{d.packages[i+1]}
report.txtDownload
^XA
^CI28
^PW812^LL1218
^FO40,40^A0N,28,28^FDFROM: Acme Hardware DC^FS
^FO40,75^A0N,28,28^FD4500 Logistics Way, Columbus, OH 43217^FS
^FO40,130^GB732,3,3^FS
^FO40,160^A0N,30,30^FDSHIP TO:^FS
^FO40,205^A0N,45,45^FDAna Sánchez^FS
^FO40,260^A0N,45,45^FDNorthwind Industrial Supply^FS
^FO40,315^A0N,45,45^FD780 Harbor Blvd^FS
^FO40,370^A0N,45,45^FDLong Beach, CA 90802^FS
^FO40,450^GB732,3,3^FS
^FO40,480^A0N,70,70^FDGROUND^FS
^FO500,495^A0N,40,40^FDPKG 1 OF 2^FS
^FO80,600^BY3^BCN,220,Y,N,N^FD1AC2026111200001^FS
^FO40,920^A0N,30,30^FDORDER SO-58213  WEIGHT 12.4 LB^FS
^XZ
^XA
^CI28
^PW812^LL1218
^FO40,40^A0N,28,28^FDFROM: Acme Hardware DC^FS
^FO40,75^A0N,28,28^FD4500 Logistics Way, Columbus, OH 43217^FS
^FO40,130^GB732,3,3^FS
^FO40,160^A0N,30,30^FDSHIP TO:^FS
^FO40,205^A0N,45,45^FDAna Sánchez^FS
^FO40,260^A0N,45,45^FDNorthwind Industrial Supply^FS
^FO40,315^A0N,45,45^FD780 Harbor Blvd^FS
^FO40,370^A0N,45,45^FDLong Beach, CA 90802^FS
^FO40,450^GB732,3,3^FS
^FO40,480^A0N,70,70^FDGROUND^FS
^FO500,495^A0N,40,40^FDPKG 2 OF 2^FS
^FO80,600^BY3^BCN,220,Y,N,N^FD1AC2026111200002^FS
^FO40,920^A0N,30,30^FDORDER SO-58213  WEIGHT 8.8 LB^FS
^XZ
^FH^FD{d.recipient.company:replace('_', '_5F'):replace('^', '_5E'):replace('~', '_7E')}^FS

Translations

Write static text in {t( )} tags, and add the :t formatter to translate values from the data, like the delivery slot. Carbone uses the translations for the lang of the report. This example uses "lang": "fr-fr" and the dictionary in the Translations tab. Dates follow the language too: :formatD('dddd LL') prints the date in French.

data.jsonDownload
{
  "carrier": "Acme Logistics",
  "customer": { "firstName": "Jane" },
  "parcel": {
    "tracking": "AL7829341056",
    "deliveryDate": "2026-11-18",
    "slot": "morning"
  },
  "trackingUrl": "https://track.acme.example/AL7829341056"
}
translations.jsonDownload
{
  "fr-fr": {
    "Hi": "Bonjour",
    "your parcel": "votre colis",
    "arrives on": "arrive le",
    "morning": "matin",
    "afternoon": "après-midi",
    "Track or reschedule:": "Suivre ou reprogrammer :"
  }
}
template.txtDownload
{t(Hi)} {d.customer.firstName}, {t(your parcel)} {d.parcel.tracking} {t(arrives on)} {d.parcel.deliveryDate:formatD('dddd LL')} ({d.parcel.slot:t}).
{t(Track or reschedule:)} {d.trackingUrl}
{d.carrier}
report.txtDownload
Bonjour Jane, votre colis AL7829341056 arrive le mercredi 18 novembre 2026 (matin).
Suivre ou reprogrammer : https://track.acme.example/AL7829341056
Acme Logistics

Put the punctuation in the translation when it changes with the language, like the space before a colon in French: {t(Track or reschedule:)}. Learn more about translations.

Best practices

Automate a TXT file

To generate a text file from a TXT template with the Carbone API:

  1. Prepare two files: a TXT template with Carbone tags, like {d.name}, and the JSON data to populate it. You can use the files of the Fixed-width files example.
  2. Generate the 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 fixed-width-data.json),
    \"template\": \"$(base64 -i fixed-width-template.txt)\",
    \"lang\": \"en-us\"
  }" \
  --output payments.txt
💡 Linux users: replace base64 -i fixed-width-template.txt with base64 -w 0 fixed-width-template.txt

Key parameters

Parameter Description
template Your TXT 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.date:formatD('ll')} as Nov 9, 2026.
convertTo Optional: "txt" by default. "pdf", "docx", "odt" or "html" print the text in a monospace font.
?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.