Training Workshop
Create professional documents, labels & reports
Use arrow keys to navigate • Press S for speaker notes
{{.CustomerName}}, provide data in JSON format, and DataMagik merges them into a finished PDF. The {{ }} syntax is the template language — this training will teach you everything you need.
Section 2
The building blocks of dynamic documents
Everything dynamic in a template lives inside {{ }} delimiters:
<h1>Invoice #{{.InvoiceNumber}}</h1>
<p>Customer: {{.CustomerName}}</p>
<p>Date: {{.InvoiceDate}}</p>
Anything outside {{ }} is plain HTML — rendered as-is.
. (dot) represents the current data context. {{.FieldName}} accesses a field from your JSON data.<!-- Simple field access -->
{{.CompanyName}}
<!-- Nested field access -->
{{.Customer.Name}}
{{.Customer.Address.City}}
<!-- Array element access -->
{{index .Items 0}}
<!-- Length of an array -->
{{len .Items}} items
{
"CompanyName": "Acme Corp",
"Customer": {
"Name": "Jane Smith",
"Address": {
"City": "Portland"
}
},
"Items": ["Widget", "Gadget"]
}
Acme Corp
Jane Smith
Portland
Widget
2 items
Store values in variables with := for reuse:
{{$customerName := .Customer.Name}}
{{$total := printf "%.2f" .Total}}
<p>Dear {{$customerName}},</p>
<p>Your total is ${{$total}}</p>
Comments are invisible in the output:
{{/* This comment won't appear in the PDF */}}
{{/*
Multi-line comments work too.
Great for documenting complex template logic.
*/}}
<!-- Simple if/else -->
{{if .IsVIP}}
<span class="badge vip">VIP Customer</span>
{{else}}
<span class="badge">Standard</span>
{{end}}
<!-- Else if -->
{{if eq .Status "paid"}}
<span style="color: green;">PAID</span>
{{else if eq .Status "overdue"}}
<span style="color: red;">OVERDUE</span>
{{else}}
<span style="color: orange;">PENDING</span>
{{end}}
<!-- Combining conditions -->
{{if and .IsVIP (gt .TotalSpent 10000)}}
<p>Thank you for being a valued
VIP customer!</p>
{{end}}
{{eq .A .B}}. Nest with parens: {{and (gt .X 5) (lt .X 10)}}{{if}} must have a matching {{end}}. Missing {{end}} is the most common syntax error.{{with}} changes the dot context and handles nil safely:
<!-- Without 'with' — risky if Customer is nil -->
{{.Customer.Name}}
{{.Customer.Email}}
<!-- With 'with' — safe and cleaner -->
{{with .Customer}}
<p>Name: {{.Name}}</p>
<p>Email: {{.Email}}</p>
{{with .Address}}
<p>City: {{.City}}</p>
{{end}}
{{else}}
<p>No customer data available</p>
{{end}}
{{with}} block, . (dot) refers to the value after with, not the root data. Use $ to access the root: {{$.CompanyName}}{{range .Items}}
<tr>
<td>{{.Description}}</td>
<td>${{printf "%.2f" .UnitPrice}}</td>
</tr>
{{end}}
{{range $i, $item := .Items}}
<td>{{add $i 1}}</td>
<td>{{$item.Description}}</td>
{{end}}
{{range .Items}}
<p>{{.Name}}</p>
{{else}}
<p>No items found</p>
{{end}}
. (dot) becomes the current item. Use $index for a 0-based counter, add $index 1 for 1-based.{{eq .Status "active"}} <!-- equal -->
{{ne .Count 0}} <!-- not equal -->
{{gt .Age 18}} <!-- greater than -->
{{ge .Score 90}} <!-- greater or equal -->
{{lt .Price 100}} <!-- less than -->
{{le .Quantity 0}} <!-- less or equal -->
<!-- AND -->
{{if and .IsActive .IsVerified}}
<!-- OR -->
{{if or .IsAdmin .IsModerator}}
<!-- NOT -->
{{if not .IsDeleted}}
<!-- Combined -->
{{if and (gt .Total 0) (not .IsCancelled)}}
{{eq .A .B}} not {{.A eq .B}}. When nesting, use parentheses: {{and (gt .X 5) (lt .X 10)}}{{printf "Hello %s" .Name}}
{{printf "$%.2f" .Price}}
{{printf "%05d" .OrderNum}}
{{upper .Status}}
{{lower .Email}}
{{title .Name}}
{{trim .Input}}
{{add .Price .Tax}}
{{sub .Total .Discount}}
{{mul .Quantity .UnitPrice}}
{{div .Total .Count}}
{{mod .Index 2}}
<p>Subtotal: ${{printf "%.2f" .Subtotal}}</p>
<p>Tax ({{printf "%.1f" .TaxRate}}%): ${{printf "%.2f" .TaxAmount}}</p>
<p><strong>Total: ${{printf "%.2f" .Total}}</strong></p>
<!-- Format a date string -->
{{dateFormat .InvoiceDate "01/02/2006"}}
{{dateFormat .DueDate "January 2, 2006"}}
{{dateFormat .CreatedAt "2006-01-02"}}
<!-- Date reference format:
01 = month, 02 = day, 06 = year
15 = hour, 04 = minute, 05 = second
-->
<!-- Check if field exists -->
{{if isSet .Discount}}
Discount: {{.Discount}}
{{end}}
<!-- Check non-empty -->
{{if notEmpty .Notes}}
<p>{{.Notes}}</p>
{{end}}
<!-- Type conversion -->
{{toString .NumericField}}
"01/02/2006" means MM/DD/YYYY. "2006-01-02" means YYYY-MM-DD.In the template editor, create a template that displays customer information.
Sample data (JSON tab):
{
"Company": "Acme Corp",
"Contact": {
"Name": "Jane Smith",
"Email": "jane@acme.com",
"Phone": "555-0123"
},
"IsVIP": true,
"AccountNumber": "ACM-2024-0042"
}
<h1>IsVIP is true{{if .IsVIP}} for the conditional badge.<h1>{{.Company}}</h1>
{{if .IsVIP}}
<span style="background: gold; color: #333; padding: 4px 12px;
border-radius: 4px; font-weight: bold;">VIP Customer</span>
{{end}}
<h3>Contact Information</h3>
<ul>
<li><strong>Name:</strong> {{.Contact.Name}}</li>
<li><strong>Email:</strong> {{.Contact.Email}}</li>
<li><strong>Phone:</strong> {{.Contact.Phone}}</li>
</ul>
<p>Account #: {{.AccountNumber}}</p>
{{.Company}} accesses a top-level field from the JSON data{{.Contact.Name}} accesses a nested field{{if .IsVIP}} conditionally shows the badgeCreate a table of order line items using {{range}}.
Sample data (JSON tab):
{
"OrderNumber": "ORD-2024-1234",
"Items": [
{
"SKU": "WDG-001",
"Description": "Industrial Widget",
"Quantity": 10,
"UnitPrice": 24.99
},
{
"SKU": "GDG-002",
"Description": "Premium Gadget",
"Quantity": 0,
"UnitPrice": 49.99
},
{
"SKU": "SPR-003",
"Description": "Steel Sprocket",
"Quantity": 25,
"UnitPrice": 12.50
}
]
}
{{range $i, $item := .Items}} to loop and show row numbers<h2>Order {{.OrderNumber}}</h2>
<table>
<thead><tr>
<th>#</th><th>SKU</th><th>Description</th><th>Qty</th><th>Unit Price</th>
</tr></thead>
<tbody>
{{range $i, $item := .Items}}
<tr>
<td>{{add $i 1}}</td>
<td>{{$item.SKU}}</td>
<td>{{$item.Description}}</td>
<td>{{if eq $item.Quantity 0}}<span style="color:red;">Out of Stock</span>{{else}}{{$item.Quantity}}{{end}}</td>
<td>${{printf "%.2f" $item.UnitPrice}}</td>
</tr>
{{end}}
</tbody>
</table>
Section 3
Your workspace for building documents
| Tab | Purpose | Language |
|---|---|---|
| HTML | Template body — HTML with Go template syntax | HTML |
| CSS | Stylesheet — styling for the document | CSS |
| JSON | Sample Data — test data for preview | JSON |
| Settings | Page size, margins, headers, footers, TTL | — |
| Docs | Built-in reference for all template functions | — |
| ZPL | Label templates (ZPL mode only) | ZPL |
| Export | Custom export config (export mode only) | — |
The editor uses Monaco (same as VS Code) with syntax highlighting and autocomplete.
Standard PDF documents:
Product and shipping labels:
Non-PDF output: CSV, Excel, XML, JSON, Oracle ASN, custom text formats. Configure column mappings and field discovery.
The JSON tab provides test data for previewing your template:
{
"CompanyName": "Acme Corp",
"InvoiceNumber": "INV-2024-0042",
"InvoiceDate": "2024-03-15",
"Customer": {
"Name": "Jane Smith",
"Address": "123 Main St, Portland, OR 97201"
},
"Items": [
{
"Description": "Widget",
"Quantity": 10,
"UnitPrice": 24.99
},
{
"Description": "Gadget",
"Quantity": 5,
"UnitPrice": 49.99
}
],
"Subtotal": 499.85,
"TaxRate": 8.5,
"TaxAmount": 42.49,
"Total": 542.34
}
{{ }}. {{.InvoiceNumber}} requires a JSON key called "InvoiceNumber" (case-sensitive).The Docs tab contains a built-in reference for everything you can use in templates:
Navigate the Document Designer and create your first template:
<h1>Hello, Document Designer!</h1>You should now be familiar with:
Design a complete JSON data structure for an invoice and use it in your template:
{{range}} to display the line items{
"CompanyName": "DataMagik Inc.",
"CompanyAddress": "456 Tech Ave",
"InvoiceNumber": "INV-2024-0042",
"InvoiceDate": "2024-03-15",
"DueDate": "2024-04-14",
"Customer": {
"Name": "Jane Smith",
"Street": "123 Main St",
"City": "Portland",
"State": "OR",
"Zip": "97201"
},
"Items": [
{
"Description": "Widget A",
"Quantity": 10,
"UnitPrice": 24.99
},
{
"Description": "Gadget B",
"Quantity": 5,
"UnitPrice": 49.99
}
],
"Subtotal": 299.85,
"TaxRate": 8.5,
"Total": 325.34
}
<h1>{{.CompanyName}}</h1>
<p>{{.CompanyAddress}}</p>
<h2>Invoice #{{.InvoiceNumber}}</h2>
<p>Date: {{.InvoiceDate}} | Due: {{.DueDate}}</p>
<h3>Bill To:</h3>
<p>{{.Customer.Name}}</p>
<p>{{.Customer.Street}}</p>
<p>{{.Customer.City}}, {{.Customer.State}} {{.Customer.Zip}}</p>
<table>
<tr><th>Description</th><th>Qty</th><th>Price</th></tr>
{{range .Items}}
<tr><td>{{.Description}}</td><td>{{.Quantity}}</td>
<td>${{printf "%.2f" .UnitPrice}}</td></tr>
{{end}}
</table>
<p><strong>Total: ${{printf "%.2f" .Total}}</strong></p>
Section 4
Making your documents look professional
/* In the CSS tab */
.invoice-header {
display: flex;
justify-content: space-between;
border-bottom: 2px solid #333;
padding-bottom: 20px;
margin-bottom: 30px;
}
.items-table th {
background: #f0f0f0;
padding: 10px;
text-align: left;
}
Reusable, clean, easy to maintain
<!-- In the HTML tab -->
<div style="display: flex;
justify-content: space-between;
border-bottom: 2px solid #333;
padding-bottom: 20px;">
...
</div>
Quick for one-offs, gets messy fast
style="color: {{if eq .Status "overdue"}}red{{else}}green{{end}}"The editor includes a CSS Presets library with ready-to-use styles:
Apply a preset to get started quickly, then customize to your needs.
.invoice-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
}
.company-info { text-align: left; }
.invoice-info { text-align: right; }
.items-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.items-table th {
background: #2d3748;
color: white;
padding: 10px 12px;
text-align: left;
}
.items-table td {
padding: 8px 12px;
border-bottom: 1px solid #e2e8f0;
}
.items-table tr:nth-child(even) {
background: #f7fafc;
}
.section-break {
page-break-before: always;
}
.keep-together {
page-break-inside: avoid;
}
@media print {
body { font-size: 11pt; color: #000; }
.no-print { display: none; }
}
<div class="section">
<h2>Section 1: Summary</h2>
</div>
<div class="section section-break">
<h2>Section 2: Details</h2>
<!-- starts on a new page -->
</div>
page-break-before: always forces new page. page-break-inside: avoid keeps elements together (great for table rows).body {
font-family: 'Helvetica Neue', Arial, sans-serif;
font-size: 10pt; /* pt not px for print */
line-height: 1.5;
color: #1a202c;
margin: 0; padding: 40px;
}
h1 { font-size: 20pt; color: #2d3748; margin-bottom: 5px; }
h2 { font-size: 14pt; color: #4a5568; margin-top: 25px; }
h3 { font-size: 12pt; color: #4a5568; }
.totals { text-align: right; margin-top: 20px; }
.totals .grand-total {
font-size: 14pt; font-weight: bold;
border-top: 2px solid #2d3748; padding-top: 8px;
}
Add professional CSS styling to your invoice template from Exercise 4:
.invoice-header { display: flex; justify-content: space-between; }body { font-family: Arial, sans-serif; font-size: 10pt; color: #333; padding: 30px; }
.header { display: flex; justify-content: space-between; border-bottom: 2px solid #2d3748; padding-bottom: 15px; margin-bottom: 20px; }
.bill-to { background: #f7fafc; padding: 15px; border-radius: 6px; margin: 15px 0; }
.items-table { width: 100%; border-collapse: collapse; margin: 20px 0; }
.items-table th { background: #2d3748; color: white; padding: 10px; text-align: left; }
.items-table td { padding: 8px 10px; border-bottom: 1px solid #e2e8f0; }
.items-table tr:nth-child(even) { background: #f7fafc; }
.totals { text-align: right; margin-top: 15px; }
.totals p { margin: 4px 0; }
.grand-total { font-size: 14pt; font-weight: bold; border-top: 2px solid #2d3748; padding-top: 8px; }
<div class="header">
<div>
<h1>{{.CompanyName}}</h1>
<p>{{.CompanyAddress}}</p>
<p>{{.CompanyPhone}}</p>
</div>
<div style="text-align:right;">
<h2>INVOICE</h2>
<p>#{{.InvoiceNumber}}</p>
<p>Date: {{.InvoiceDate}}</p>
<p>Due: {{.DueDate}}</p>
</div>
</div>
<div class="bill-to">
<h3>Bill To:</h3>
<p>{{.Customer.Name}}</p>
<p>{{.Customer.Street}}</p>
<p>{{.Customer.City}}, {{.Customer.State}} {{.Customer.Zip}}</p>
</div>
Create a report template with multiple sections on separate pages:
page-break-before: always to put each section on its own page{{range}} for a data table.section-break { page-break-before: always; } on the 2nd and 3rd section divs.<div class="section">
<h1>Monthly Sales Report</h1>
<div class="kpi-row">
<div class="kpi"><h3>${{printf "%.0f" .TotalRevenue}}</h3><p>Revenue</p></div>
<div class="kpi"><h3>{{.TotalOrders}}</h3><p>Orders</p></div>
</div>
</div>
<div class="section section-break">
<h2>Sales Details</h2>
<table>{{range .Products}}
<tr><td>{{.Name}}</td><td>${{printf "%.2f" .Revenue}}</td></tr>
{{end}}</table>
</div>
<div class="section section-break">
<h2>Appendix</h2>
</div>
.section-break {
page-break-before: always;
}
.kpi-row {
display: flex;
gap: 20px; margin: 20px 0;
}
.kpi {
background: #f7fafc;
padding: 20px;
border-radius: 8px;
text-align: center; flex: 1;
}
Section 5
Configuring output and understanding the pipeline
| Setting | Options | Use Case |
|---|---|---|
| Page Size | Letter (8.5"×11"), A4 (210×297mm), Custom | Letter for US, A4 for international |
| Orientation | Portrait, Landscape | Landscape for wide tables/dashboards |
| Custom Size | Width × Height in mm | Labels, tickets, custom forms |
Set margins for top, right, bottom, left (in points or inches).
Typical values: 20–25mm on all sides for professional documents.
| Mode | Description |
|---|---|
| Company Defaults | Uses company-wide header/footer templates |
| Template-Specific | Select a specific header/footer template |
| Inline | Write header/footer HTML directly in settings |
| None | No header or footer |
Page {{.PageNumber}} of {{.TotalPages}} in your footer template for automatic page numbering.| Format | Use Case |
|---|---|
| Invoices, reports, official documents | |
| HTML | Email-ready content, web preview |
| CSV / Excel | Data exports, spreadsheet reports |
| XML / JSON | System integrations, data exchange |
Documents are generated asynchronously — the main app never creates PDFs directly:
1. API Request POST /api/document-designer/generate
↓
2. Quota Check QuotaEnforcementService checks limits
↓
3. Queue to Redis Published to priority queue
↓
4. Return Immediately Response with request_id for polling
↓
5. Worker Processes Worker dequeues → calls PDF service → uploads to S3
↓
6. Status Check GET /api/document-designer/status/{request_id}
Configure your invoice template's document settings:
Page {{.PageNumber}} of {{.TotalPages}}In the Settings tab, you should have configured:
| Setting | Value |
|---|---|
| Page Size | Letter (8.5" × 11") |
| Orientation | Portrait |
| Top Margin | 25mm |
| Right Margin | 20mm |
| Bottom Margin | 25mm |
| Left Margin | 20mm |
| Header Mode | Inline |
| Footer Mode | Inline |
| Print Backgrounds | Enabled |
| TTL | 90 days |
Section 6
Scannable codes for documents and labels
<!-- Renders an <img> tag -->
{{code128 .SKU 200 50}}
General purpose, alphanumeric. Best for SKUs, order numbers, serial numbers.
{{code39 .InventoryCode 200 50}}
{{ean13 .EANCode 200 80}}
{{pdf417 .DocumentInfo 300 80}}
Code 39 for inventory, EAN-13 for products, PDF417 for dense data.
<img> tag with a base64-encoded PNG. Just use {{code128 .Data 200 50}} — no need to write the img tag yourself.<!-- QR Code (takes data and size) -->
{{qrcode .PaymentURL 150}}
<!-- Data Matrix (compact, good error correction) -->
{{datamatrix .SerialData 80}}
<!-- Generic barcode function -->
{{barcode "qr" .URL 150 150}}
{{qrcode .PaymentURL 150}}{{qrcode .TrackingURL 120}}{{qrcode .VCardData 150}}{{qrcode .ProductURL 100}}| Function | Parameters | Returns |
|---|---|---|
code128 | data, width, height | HTML img tag |
code39 | data, width, height | HTML img tag |
qrcode | data, size | HTML img tag |
ean13 | data, width, height | HTML img tag |
datamatrix | data, size | HTML img tag |
pdf417 | data, width, height | HTML img tag |
barcode | type, data, width, height | HTML img tag |
Add _base64 suffix for raw base64 (manual img tags):
<!-- Base64 variant — you build the img tag yourself -->
<img src="data:image/png;base64,{{code128_base64 .SKU 200 50}}" alt="Barcode" style="display:block;">
<!-- Centered barcode with label -->
<div style="text-align: center; margin: 20px 0;">
{{code128 .InvoiceNumber 250 60}}
<p style="font-family: monospace; font-size: 10pt; margin-top: 5px;">
{{.InvoiceNumber}}
</p>
</div>
<!-- Barcode in header (right-aligned) -->
<div class="header">
<div class="company-info">
<h1>{{.CompanyName}}</h1>
</div>
<div style="text-align: right;">
{{code128 .InvoiceNumber 200 50}}
<p>Invoice #{{.InvoiceNumber}}</p>
</div>
</div>
{{if .SKU}}{{code128 .SKU 200 50}}{{end}}Enhance your invoice template with barcodes:
"PaymentURL": "https://pay.example.com/inv/0042"{{if}} checks{{code128 .InvoiceNumber 200 50}} for the barcode, {{qrcode .PaymentURL 120}} for the QR code.<!-- In the header div, right side -->
<div style="text-align: right;">
<h2>INVOICE</h2>
{{if .InvoiceNumber}}
{{code128 .InvoiceNumber 200 50}}
<p style="font-family: monospace; font-size: 9pt;">#{{.InvoiceNumber}}</p>
{{end}}
<p>Date: {{.InvoiceDate}}</p>
<p>Due: {{.DueDate}}</p>
</div>
<!-- ... items table and totals ... -->
<!-- Payment QR code at the bottom -->
{{if .PaymentURL}}
<div style="text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e2e8f0;">
<p style="font-size: 9pt; color: #666;">Scan to pay online:</p>
{{qrcode .PaymentURL 120}}
<p style="font-size: 8pt; color: #999;">{{.PaymentURL}}</p>
</div>
{{end}}
Create a product label template:
{{ean13 .EANCode 180 70}}{
"Name": "Industrial Widget",
"SKU": "WDG-001",
"EANCode": "5901234123457",
"Price": 24.99,
"ProductURL": "https://shop.example.com/p/WDG-001"
}
<div style="width: 300px; border: 2px solid #000; padding: 15px; text-align: center; font-family: Arial, sans-serif;">
<h2 style="margin: 0 0 10px; font-size: 14pt;">{{.Name}}</h2>
<p style="font-family: monospace; color: #666; margin: 5px 0;">SKU: {{.SKU}}</p>
<div style="margin: 15px 0;">
{{if .EANCode}}
{{ean13 .EANCode 180 70}}
<p style="font-family: monospace; font-size: 9pt;">{{.EANCode}}</p>
{{end}}
</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
{{if .ProductURL}}{{qrcode .ProductURL 80}}{{end}}
</div>
<div style="font-size: 18pt; font-weight: bold; color: #e53e3e;">
${{printf "%.2f" .Price}}
</div>
</div>
</div>
Section 7
Line, bar, pie, and doughnut charts for reports
<!-- Line chart -->
{{lineChart .SalesData "sales" 800 400}}
<!-- Bar chart -->
{{barChart .CategoryData "cats" 800 400}}
<!-- Generic function -->
{{chart "line" .Data "id" 800 400}}
{{chart "bar" .Data "id" 800 400}}
Use line for trends over time, bar for comparisons.
<!-- Pie chart -->
{{pieChart .Distribution "dist" 600 600}}
<!-- Doughnut chart -->
{{doughnutChart .Distribution "dnut" 600 600}}
Use pie/doughnut for part-to-whole relationships.
(data, elementID, width, height). The elementID must be unique per chart on the page.{
"SalesData": {
"labels": ["January", "February", "March", "April"],
"datasets": [{
"label": "Revenue",
"data": [12000, 15000, 13500, 18000]
}]
}
}
{{lineChart .SalesData "salesChart" 800 400}}
<!-- No JSON data needed — data is inline -->
{{chart_line "Monthly Sales" "Jan,12000|Feb,15000|Mar,13500|Apr,18000" 800 400}}
{{chart_bar "By Category" "Widgets,45|Gadgets,30|Sprockets,25" 800 400}}
{{chart_pie "Market Share" "Product A,45|Product B,35|Product C,20" 600 600}}
"label1,value1|label2,value2|..."| Function | Parameters | Data Format |
|---|---|---|
chart | type, data, elementID, width, height | Object |
lineChart | data, elementID, width, height | Object |
barChart | data, elementID, width, height | Object |
pieChart | data, elementID, width, height | Object |
doughnutChart | data, elementID, width, height | Object |
chart_line | title, csvData, width, height | CSV |
chart_bar | title, csvData, width, height | CSV |
chart_pie | title, csvData, width, height | CSV |
All functions also have _base64 variants (e.g., chart_line_base64) that return base64-encoded images.
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
<div class="chart-card">
<h3>Revenue Trend</h3>
{{lineChart .RevenueTrend "revChart" 400 250}}
</div>
<div class="chart-card">
<h3>Sales by Category</h3>
{{pieChart .CategorySales "catChart" 400 250}}
</div>
<div class="chart-card" style="grid-column: 1 / -1;">
<h3>Monthly Comparison</h3>
{{barChart .MonthlyData "monthChart" 800 300}}
</div>
</div>
.chart-card {
background: #f9f9f9;
border-radius: 8px;
padding: 15px;
border: 1px solid #e0e0e0;
}
.chart-card h3 { margin-top: 0; }
Create a sales report template with charts:
{{chart_bar "Monthly Revenue" "Jan,12000|Feb,15000|Mar,13500|Apr,18000|May,21000|Jun,19500" 800 400}}{{chart_pie "By Category" "Widgets,45|Gadgets,30|Sprockets,25" 600 400}}<h1>Monthly Sales Report</h1>
<p>Report Period: January – June 2024</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0;">
<div class="chart-card">
<h3>Revenue by Month</h3>
{{chart_bar "Monthly Revenue" "Jan,12000|Feb,15000|Mar,13500|Apr,18000|May,21000|Jun,19500" 400 300}}
</div>
<div class="chart-card">
<h3>Revenue by Category</h3>
{{chart_pie "By Category" "Widgets,45|Gadgets,30|Sprockets,25" 400 300}}
</div>
</div>
<h3>Top Products</h3>
<table class="items-table">
<tr><th>Product</th><th>Units Sold</th><th>Revenue</th></tr>
{{range .TopProducts}}
<tr><td>{{.Name}}</td><td>{{.Units}}</td><td>${{printf "%.2f" .Revenue}}</td></tr>
{{end}}
</table>
Build a one-page dashboard with KPIs and charts:
{
"TotalRevenue": 125000,
"TotalOrders": 842,
"AvgOrderValue": 148.46,
"ReturnRate": 3.2,
"DailyOrders": {
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
"datasets": [{
"label": "Orders",
"data": [120, 145, 132, 168, 155]
}]
}
}
<h1>Sales Dashboard</h1>
<div style="display: flex; gap: 15px; margin: 20px 0;">
<div class="kpi"><h2>${{printf "%.0f" .TotalRevenue}}</h2><p>Total Revenue</p></div>
<div class="kpi"><h2>{{.TotalOrders}}</h2><p>Total Orders</p></div>
<div class="kpi"><h2>${{printf "%.2f" .AvgOrderValue}}</h2><p>Avg Order Value</p></div>
<div class="kpi"><h2>{{printf "%.1f" .ReturnRate}}%</h2><p>Return Rate</p></div>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 2;">
<h3>Daily Orders</h3>
{{lineChart .DailyOrders "dailyChart" 600 300}}
</div>
<div style="flex: 1;">
<h3>By Category</h3>
{{chart_pie "Categories" "Widgets,40|Gadgets,35|Sprockets,25" 300 300}}
</div>
</div>
.kpi { flex: 1; background: #f0f4f8; padding: 15px; border-radius: 8px; text-align: center; }
.kpi h2 { margin: 0; color: #2d3748; font-size: 24pt; }
.kpi p { margin: 5px 0 0; color: #718096; font-size: 10pt; }
Section 8
Track changes, collaborate safely, rollback when needed
main branch (the production version)main branch by default1. Create branch → "add-discount-column"
2. Edit template → make changes on the branch
3. Preview → verify changes look correct
4. Commit → save with a descriptive message
5. Merge to main → promote to production
Write clear messages describing what changed and why:
Practice the full version control workflow:
<div class="payment-terms">
<h3>Payment Terms</h3>
<p>{{.PaymentTerms}}</p>
<p>Please make payment by {{.DueDate}}</p>
</div>
{{.PaymentTerms}} and {{.DueDate}}<div class="payment-terms"> blockSection 9
Quality control for template changes
1. Author commits a version
2. Author submits an approval request
3. Reviewer receives notification
4. Reviewer views the diff and tests the template
5. Reviewer approves or rejects (with comments)
6. If approved, version becomes active on main
Found an issue? Click "Withdraw" to cancel the approval request, make fixes, and re-submit.
Practice the approval process (pair with a colleague if possible):
Section 10
Generate documents programmatically from scripts
The Script Engine provides two functions for document generation:
// Wait for the PDF to be ready
const result = documents.generateSync(
"My Invoice Template",
{
InvoiceNumber: "INV-2024-0042",
Customer: { Name: "Jane Smith" },
Items: [
{ Description: "Widget", Quantity: 10, UnitPrice: 24.99 }
],
Total: 249.90
}
);
console.log(result.url); // PDF URL
// Fire and forget
const requestId = documents.generate(
"My Invoice Template",
{
InvoiceNumber: "INV-2024-0042",
Customer: { Name: "Jane Smith" },
Total: 249.90
}
);
// Check status later
console.log("Request ID:", requestId);
generateSync when you need the PDF URL immediately (e.g., to email it). Use generate for batch/bulk generation where you don't need the URL right away.The second argument is a JavaScript object whose keys become template fields:
// Script Engine
const data = {
CompanyName: "Acme Corp", // → {{.CompanyName}}
Customer: {
Name: "Jane Smith", // → {{.Customer.Name}}
Address: { City: "Portland" } // → {{.Customer.Address.City}}
},
Items: [ // → {{range .Items}}
{ Description: "Widget", Quantity: 10, UnitPrice: 24.99 }
],
Total: 249.90 // → {{.Total}}
};
const result = documents.generateSync("My Invoice Template", data);
{{.InvoiceNumber}}, you must pass InvoiceNumber (case-sensitive) in the data object.const result = documents.generateSync("Invoice Template", data, {
branch: "main", // Which branch to use (default: "main")
priority: "high", // Queue priority: "high", "normal", "low"
outputFormat: "pdf", // "pdf", "html", "csv", "excel", "xml", "json"
returnType: "url" // "url" (default) or "base64"
});
// Response
console.log(result.url); // Download URL
console.log(result.documentId); // Document ID for tracking
// Generate and email
const pdf = documents.generateSync("Invoice", invoiceData);
email.send({
to: customer.email,
subject: `Invoice #${invoiceData.InvoiceNumber}`,
body: `<p>Please find your invoice attached.</p>`,
attachments: [{ url: pdf.url, filename: "invoice.pdf" }]
});
// Schedule: Every Monday at 8:00 AM (cron: 0 8 * * 1)
function main(context) {
const salesData = http.get("https://api.example.com/sales/weekly");
const report = documents.generateSync("Weekly Sales Report", {
ReportDate: new Date().toISOString().split('T')[0],
TotalRevenue: salesData.totalRevenue,
TotalOrders: salesData.totalOrders,
TopProducts: salesData.topProducts
});
email.send({
to: "sales-team@company.com",
subject: "Weekly Sales Report",
body: "<p>This week's report is attached.</p>",
attachments: [{ url: report.url, filename: "weekly-report.pdf" }]
});
return { status: "sent", url: report.url };
}
0 8 * * 1 = every Monday at 8:00 AM. Set this in the Script Engine's schedule settings.Write a Script Engine script that generates an invoice PDF:
documents.generateSync("My First Invoice", orderData)console.log()function main(context) {
const orderData = {
// Build your data object here...
};
// Generate and return the result
}
function main(context) {
const orderData = {
CompanyName: "DataMagik Inc.",
CompanyAddress: "456 Tech Ave, Suite 200",
CompanyPhone: "(555) 867-5309",
InvoiceNumber: "INV-2024-0042",
InvoiceDate: "2024-03-15",
DueDate: "2024-04-14",
PaymentTerms: "Net 30",
Customer: {
Name: "Jane Smith",
Street: "123 Main St",
City: "Portland", State: "OR", Zip: "97201"
},
Items: [
{ Description: "Widget A", Quantity: 10, UnitPrice: 24.99 },
{ Description: "Gadget B", Quantity: 5, UnitPrice: 49.99 },
{ Description: "Sprocket C", Quantity: 25, UnitPrice: 12.50 }
],
Subtotal: 812.35, TaxRate: 8.5, TaxAmount: 69.05, Total: 881.40,
PaymentURL: "https://pay.example.com/inv/0042"
};
const result = documents.generateSync("My First Invoice", orderData);
console.log("PDF URL:", result.url);
return result;
}
Build an automation script that generates a report and emails it:
documents.generateSync("Monthly Sales Report", reportData)email.send()function main(context) {
// 1. Build report data
// 2. Generate PDF
// 3. Email the PDF
// 4. Log and return results
}
0 8 1 * * (1st of every month at 8 AM)function main(context) {
const reportData = {
ReportDate: new Date().toISOString().split('T')[0],
TotalRevenue: 125000, TotalOrders: 842, AvgOrderValue: 148.46,
TopProducts: [
{ Name: "Widget Pro", Units: 245, Revenue: 36750 },
{ Name: "Gadget Plus", Units: 189, Revenue: 28350 },
{ Name: "Sprocket XL", Units: 156, Revenue: 23400 }
]
};
// Generate the PDF
const report = documents.generateSync("Monthly Sales Report", reportData);
console.log("Report generated:", report.url);
// Email the report
email.send({
to: "manager@company.com",
subject: "Monthly Sales Report - " + reportData.ReportDate,
body: "<p>The monthly sales report is attached.</p>",
attachments: [{ url: report.url, filename: "monthly-report.pdf" }]
});
return { reportUrl: report.url, emailSent: true };
}
Section 11
Complete templates you can use as starting points
<div class="header">
<div>
<h1>{{.CompanyName}}</h1>
<p>{{.CompanyAddress}}</p>
<p>{{.CompanyPhone}}</p>
</div>
<div style="text-align: right;">
<h2>INVOICE</h2>
{{if .InvoiceNumber}}{{code128 .InvoiceNumber 200 50}}{{end}}
<p>#{{.InvoiceNumber}}</p>
<p>Date: {{dateFormat .InvoiceDate "01/02/2006"}}</p>
<p>Due: {{dateFormat .DueDate "01/02/2006"}}</p>
<p>Terms: {{.PaymentTerms}}</p>
</div>
</div>
<div class="bill-to">
<h3>Bill To:</h3>
{{with .Customer}}
<p><strong>{{.Name}}</strong></p>
<p>{{.Street}}</p>
<p>{{.City}}, {{.State}} {{.Zip}}</p>
{{end}}
</div>
<table class="items-table">
<thead><tr>
<th>#</th><th>Description</th><th>Qty</th><th>Unit Price</th><th>Total</th>
</tr></thead>
<tbody>
{{range $i, $item := .Items}}
<tr>
<td>{{add $i 1}}</td>
<td>{{$item.Description}}</td>
<td>{{$item.Quantity}}</td>
<td>${{printf "%.2f" $item.UnitPrice}}</td>
<td>${{printf "%.2f" (mul $item.Quantity $item.UnitPrice)}}</td>
</tr>
{{end}}
</tbody>
</table>
<div class="totals">
<p>Subtotal: ${{printf "%.2f" .Subtotal}}</p>
{{if .TaxRate}}<p>Tax ({{printf "%.1f" .TaxRate}}%): ${{printf "%.2f" .TaxAmount}}</p>{{end}}
<p class="grand-total">Total: ${{printf "%.2f" .Total}}</p>
</div>
{{if .PaymentURL}}
<div style="text-align:center; margin-top:20px; border-top:1px solid #ccc; padding-top:10px;">
<p>Scan to pay:</p>{{qrcode .PaymentURL 120}}
</div>
{{end}}
<div class="shipping-label">
<div class="label-row">
<div class="address-block">
<p class="label-header">FROM:</p>
<p>{{.ShipFrom.Name}}</p>
<p>{{.ShipFrom.Street}}</p>
<p>{{.ShipFrom.City}}, {{.ShipFrom.State}} {{.ShipFrom.Zip}}</p>
</div>
<div class="address-block">
<p class="label-header">TO:</p>
<p><strong>{{.ShipTo.Name}}</strong></p>
<p>{{.ShipTo.City}}, {{.ShipTo.State}} {{.ShipTo.Zip}}</p>
</div>
</div>
<div style="text-align: center; margin: 15px 0;">
{{code128 .TrackingNumber 300 60}}
<p style="font-family: monospace;">{{.TrackingNumber}}</p>
</div>
<div class="label-row">
<div>{{qrcode .TrackingURL 100}}</div>
<div class="service-badge">{{upper .ServiceLevel}}</div>
<div><p>Weight: {{printf "%.1f" .Weight}} lbs</p>
<p>Ship: {{dateFormat .ShipDate "01/02/2006"}}</p></div>
</div>
</div>
<h2>Packing Slip</h2>
<p>Order #{{.OrderNumber}} — {{dateFormat .OrderDate "January 2, 2006"}}</p>
<table class="items-table">
<thead><tr><th>Item</th><th>SKU</th><th>Ordered</th><th>Shipped</th><th>Status</th></tr></thead>
<tbody>
{{range .Items}}
<tr>
<td>{{.Description}}</td>
<td style="font-family: monospace;">{{.SKU}}</td>
<td>{{.QuantityOrdered}}</td>
<td>{{.QuantityShipped}}</td>
<td>{{if eq .QuantityOrdered .QuantityShipped}}<span style="color:green">Complete</span>
{{else if gt .QuantityShipped 0}}<span style="color:orange">Partial</span>
{{else}}<span style="color:red">Backordered</span>{{end}}</td>
</tr>
{{end}}
</tbody>
</table>
{{if .SpecialInstructions}}
<h3>Special Instructions</h3>
<p>{{.SpecialInstructions}}</p>
{{end}}
| Pattern | When to Use | Example |
|---|---|---|
| Guard with if | Optional fields or barcodes | {{if .SKU}}{{code128 .SKU 200 50}}{{end}} |
| Safe access with with | Nested objects that might be nil | {{with .Customer}} ... {{end}} |
| Default values | Missing text fields | {{if .Notes}}{{.Notes}}{{else}}No notes{{end}} |
| Alternating rows | Table readability | {{if mod $i 2}}class="odd"{{end}} |
| Conditional styling | Status indicators | style="color: {{if eq .Status "paid"}}green{{else}}red{{end}}" |
| Cache barcode | Reusing same barcode | {{$bc := code128_base64 .SKU 200 50}} |
Build a complete shipping label template from scratch:
{{if eq}}dateFormat){
"ShipFrom": {
"Name": "Acme Corp",
"Street": "456 Tech Ave",
"City": "Portland",
"State": "OR",
"Zip": "97201"
},
"ShipTo": {
"Name": "Jane Smith",
"Street": "123 Main St",
"City": "Seattle",
"State": "WA",
"Zip": "98101"
},
"TrackingNumber": "1Z999AA10123456784",
"TrackingURL": "https://track.example.com/1Z999AA10123456784",
"ServiceLevel": "OVERNIGHT",
"Weight": 5.2,
"ShipDate": "2024-03-15"
}
<div style="border:3px solid #000; padding:20px; font-family:Arial; max-width:500px;">
<div style="display:flex; justify-content:space-between; margin-bottom:15px;">
<div>
<p style="font-size:8pt; color:#666; text-transform:uppercase;">From:</p>
<p>{{.ShipFrom.Name}}</p>
<p>{{.ShipFrom.Street}}</p>
<p>{{.ShipFrom.City}}, {{.ShipFrom.State}} {{.ShipFrom.Zip}}</p>
</div>
<div>
<p style="font-size:8pt; color:#666; text-transform:uppercase;">To:</p>
<p><strong style="font-size:14pt;">{{.ShipTo.Name}}</strong></p>
<p>{{.ShipTo.Street}}</p>
<p>{{.ShipTo.City}}, {{.ShipTo.State}} {{.ShipTo.Zip}}</p>
</div>
</div>
<div style="text-align:center; border-top:2px solid #000; border-bottom:2px solid #000; padding:10px 0;">
{{code128 .TrackingNumber 300 60}}
<p style="font-family:monospace;">{{.TrackingNumber}}</p>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:10px;">
<div>{{qrcode .TrackingURL 90}}</div>
<div style="padding:8px 20px; font-weight:bold; font-size:14pt; border-radius:4px;
{{if eq .ServiceLevel "OVERNIGHT"}}background:#fed7d7; color:#c53030;
{{else}}background:#bee3f8; color:#2b6cb0;{{end}}">{{upper .ServiceLevel}}</div>
<div style="text-align:right; font-size:9pt;">
<p>Weight: {{printf "%.1f" .Weight}} lbs</p>
<p>Ship: {{dateFormat .ShipDate "Jan 2, 2006"}}</p></div>
</div>
</div>
Section 12
Fix common errors and review what you've learned
<!-- WRONG -->
{{if .IsVIP}}
<span>VIP</span>
<!-- RIGHT -->
{{if .IsVIP}}
<span>VIP</span>
{{end}}
<!-- WRONG -->
{{if .IsActive}
<p>Active</p>
{end}}
<!-- RIGHT -->
{{if .IsActive}}
<p>Active</p>
{{end}}
{{if}}, {{range}}, and {{with}} needs a matching {{end}}. Count your opening and closing tags!<!-- Error: "can't evaluate field FullName in type" -->
{{.Customer.FullName}} <!-- Field doesn't exist -->
{{.Customer.Name}} <!-- Correct -->
<!-- Error: "nil pointer evaluation" -->
{{.Customer.Address.Street}} <!-- Crashes if Address is nil -->
<!-- Safe: use 'with' -->
{{with .Customer}}
{{with .Address}}
{{.Street}}
{{end}}
{{end}}
<!-- Error: "function 'formatdate' not defined" -->
{{formatdate .Date "01/02/2006"}} <!-- Wrong -->
{{dateFormat .Date "01/02/2006"}} <!-- Correct -->
<!-- Show all available data -->
<pre>{{printf "%+v" .}}</pre>
<!-- Show a specific field -->
<pre>Customer: {{printf "%+v" .Customer}}</pre>
<!-- Check if a field exists -->
{{if isSet .Discount}}
Discount exists: {{.Discount}}
{{else}}
Discount field is nil
{{end}}
<!-- Check if non-empty -->
{{if notEmpty .Notes}}
Notes: {{.Notes}}
{{else}}
Notes field is empty or nil
{{end}}
<pre>{{printf "%+v" .}}</pre> at the top of your template to see all available data. Remove it when done debugging.| Problem | Check |
|---|---|
| Template won't parse | Count {{if}}/{{range}}/{{with}} vs {{end}} tags |
| Field shows blank | Verify JSON key name matches exactly (case-sensitive) |
| Nil pointer error | Wrap nested access in {{with}} or {{if}} |
| Function not found | Check spelling and case: dateFormat not formatDate |
| Barcode not showing | Guard with {{if .Field}}, check data isn't empty |
| Chart not rendering | Verify data format (labels + datasets) and unique elementID |
| Page breaks not working | Use page-break-before: always on a block element |
| Backgrounds missing in PDF | Enable "Print backgrounds" in Settings |
| Wrong date format | Use Go reference date: 01=month, 02=day, 2006=year |
| Styles not applying | Check CSS tab for typos; verify class names match |
| Section | Key Skills |
|---|---|
| Go Template Syntax | Fields, conditionals, loops, functions, variables |
| Template Editor | HTML/CSS/JSON tabs, preview, drafts, docs |
| CSS Styling | Layout, page breaks, print styles, typography |
| Document Settings | Page size, margins, headers/footers, TTL |
| Barcodes & QR Codes | Code 128, EAN-13, QR, styling, base64 variants |
| Charts | Line, bar, pie, doughnut, CSV & object formats |
| Version Control | Branches, commits, diffs, merges, rollback |
| Approvals | Request, review, approve/reject workflow |
| Script Integration | generateSync, generate, email delivery, scheduling |
| Real-World Examples | Invoice, shipping label, report, packing slip |
| Troubleshooting | Debug tools, common errors, defensive patterns |
You're now ready to create professional documents with DataMagik
Happy templating!