Advanced PDF Templates
Create professional, branded PDF documents for invoices, purchase orders, and other transactions.
Overview
ADVANCED PDF TEMPLATE SYSTEM
═══════════════════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────────────────────┐
│ What It Is: │
│ • Built-in PDF template engine using BFO (Big Faceless Organization) │
│ • XML/FreeMarker-based templates │
│ • Access to record data via template variables │
│ • CSS-like styling support │
│ │
│ Best For: │
│ • Custom invoice layouts │
│ • Branded purchase orders │
│ • Professional packing slips │
│ • Customer statements │
│ • Any standard transaction document │
└─────────────────────────────────────────────────────────────────────────────┘
Navigation: Customization → Forms → Advanced PDF/HTML Templates
Template Structure
Basic Template
<?xml version="1.0"?>
<!DOCTYPE pdf PUBLIC "-//big.faceless.org//report" "report-1.1.dtd">
<pdf>
<head>
<link name="NotoSans" type="font" subtype="truetype"
src="${nsfont.NotoSans_Regular}" />
<style type="text/css">
body {
font-family: NotoSans, sans-serif;
font-size: 10pt;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #333;
color: white;
padding: 5px;
}
td {
padding: 5px;
border-bottom: 1px solid #ccc;
}
.header {
font-size: 18pt;
font-weight: bold;
}
.right {
text-align: right;
}
</style>
</head>
<body>
<!-- Template content here -->
</body>
</pdf>
Template Sections
TEMPLATE STRUCTURE
═══════════════════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────────────────────┐
│ <?xml version="1.0"?> XML Declaration │
│ <!DOCTYPE pdf ...> Document Type │
│ │
│ <pdf> │
│ <head> │
│ <link ... /> Font definitions │
│ <style> CSS styling │
│ ... │
│ </style> │
│ </head> │
│ │
│ <body header="..." footer="..."> Main content │
│ <!-- Record data and layout --> │
│ </body> │
│ │
│ <body header="..." footer="..."> Additional pages (optional) │
│ </body> │
│ </pdf> │
└─────────────────────────────────────────────────────────────────────────────┘
Accessing Record Data
FreeMarker Syntax
<!-- Simple field value -->
${record.tranid}
<!-- Entity name -->
${record.entity}
<!-- Formatted date -->
${record.trandate?string["MM/dd/yyyy"]}
<!-- Currency formatting -->
${record.total?string.currency}
<!-- Null-safe access -->
${record.memo!}
<!-- Default value if null -->
${record.memo!"No memo provided"}
<!-- Conditional display -->
<#if record.total gt 1000>
<p class="highlight">Large Order!</p>
</#if>
Common Record Fields
TRANSACTION FIELDS
═══════════════════════════════════════════════════════════════════════════════
Header Fields:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ${record.tranid} │ Transaction Number (INV-001) │
│ ${record.trandate} │ Transaction Date │
│ ${record.entity} │ Customer/Vendor Name │
│ ${record.total} │ Total Amount │
│ ${record.subtotal} │ Subtotal │
│ ${record.taxtotal} │ Tax Total │
│ ${record.duedate} │ Due Date │
│ ${record.memo} │ Memo │
│ ${record.terms} │ Payment Terms │
│ ${record.status} │ Status │
│ ${record.salesrep} │ Sales Rep │
└─────────────────────────────────────────────────────────────────────────────┘
Address Fields:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ${record.billaddress} │ Full Billing Address │
│ ${record.shipaddress} │ Full Shipping Address │
│ ${record.billaddressee} │ Bill To Name │
│ ${record.shipaddressee} │ Ship To Name │
└─────────────────────────────────────────────────────────────────────────────┘
Custom Fields:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ${record.custbody_po_number} │ Custom Body Field │
│ ${record.custbody_project} │ Custom Body Field │
└─────────────────────────────────────────────────────────────────────────────┘
Line Items
Iterating Over Lines
<!-- Items Sublist -->
<#list record.item as item>
<tr>
<td>${item.item}</td>
<td>${item.description!}</td>
<td class="right">${item.quantity}</td>
<td class="right">${item.rate?string.currency}</td>
<td class="right">${item.amount?string.currency}</td>
</tr>
</#list>
Complete Line Item Table
<table class="itemtable">
<thead>
<tr>
<th style="width: 15%">Item</th>
<th style="width: 40%">Description</th>
<th style="width: 10%">Qty</th>
<th style="width: 15%">Rate</th>
<th style="width: 20%">Amount</th>
</tr>
</thead>
<tbody>
<#list record.item as item>
<tr>
<td>${item.item}</td>
<td>${item.description!}</td>
<td class="right">${item.quantity}</td>
<td class="right">${item.rate?string.currency}</td>
<td class="right">${item.amount?string.currency}</td>
</tr>
</#list>
</tbody>
</table>
Complete Invoice Template Example
<?xml version="1.0"?>
<!DOCTYPE pdf PUBLIC "-//big.faceless.org//report" "report-1.1.dtd">
<pdf>
<head>
<link name="NotoSans" type="font" subtype="truetype"
src="${nsfont.NotoSans_Regular}" />
<link name="NotoSansBold" type="font" subtype="truetype"
src="${nsfont.NotoSans_Bold}" />
<style type="text/css">
body {
font-family: NotoSans, sans-serif;
font-size: 10pt;
margin: 0;
padding: 20px;
}
.company-name {
font-family: NotoSansBold;
font-size: 24pt;
color: #2c3e50;
}
.invoice-title {
font-size: 18pt;
font-weight: bold;
color: #333;
}
.header-table {
width: 100%;
margin-bottom: 20px;
}
.address-block {
background-color: #f8f9fa;
padding: 10px;
border-radius: 3px;
}
.label {
font-weight: bold;
color: #666;
}
.value {
color: #333;
}
.item-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.item-table th {
background-color: #2c3e50;
color: white;
padding: 8px;
text-align: left;
}
.item-table td {
padding: 8px;
border-bottom: 1px solid #ddd;
}
.item-table .right {
text-align: right;
}
.totals-table {
width: 300px;
margin-left: auto;
margin-top: 20px;
}
.totals-table td {
padding: 5px;
}
.grand-total {
font-size: 14pt;
font-weight: bold;
border-top: 2px solid #333;
}
.footer {
margin-top: 40px;
padding-top: 10px;
border-top: 1px solid #ddd;
text-align: center;
font-size: 9pt;
color: #666;
}
</style>
</head>
<body size="Letter" padding="0.5in 0.5in 0.5in 0.5in">
<!-- Header with Logo and Invoice Info -->
<table class="header-table">
<tr>
<td style="width: 50%; vertical-align: top;">
<span class="company-name">${companyinformation.companyname}</span>
<br/>
${companyinformation.mainaddress_text}
<br/>
Phone: ${companyinformation.phone}
</td>
<td style="width: 50%; text-align: right; vertical-align: top;">
<span class="invoice-title">INVOICE</span>
<br/><br/>
<table style="margin-left: auto;">
<tr>
<td class="label">Invoice #:</td>
<td class="value">${record.tranid}</td>
</tr>
<tr>
<td class="label">Date:</td>
<td class="value">${record.trandate}</td>
</tr>
<tr>
<td class="label">Due Date:</td>
<td class="value">${record.duedate!}</td>
</tr>
<tr>
<td class="label">Terms:</td>
<td class="value">${record.terms!}</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- Bill To / Ship To -->
<table style="width: 100%; margin-top: 20px;">
<tr>
<td style="width: 48%; vertical-align: top;">
<div class="address-block">
<strong>Bill To:</strong><br/>
${record.billaddress}
</div>
</td>
<td style="width: 4%"></td>
<td style="width: 48%; vertical-align: top;">
<div class="address-block">
<strong>Ship To:</strong><br/>
${record.shipaddress}
</div>
</td>
</tr>
</table>
<!-- PO Number and Sales Rep -->
<#if record.otherrefnum?has_content || record.salesrep?has_content>
<table style="width: 100%; margin-top: 15px;">
<tr>
<td style="width: 50%">
<#if record.otherrefnum?has_content>
<span class="label">PO Number:</span>
<span class="value">${record.otherrefnum}</span>
</#if>
</td>
<td style="width: 50%; text-align: right;">
<#if record.salesrep?has_content>
<span class="label">Sales Rep:</span>
<span class="value">${record.salesrep}</span>
</#if>
</td>
</tr>
</table>
</#if>
<!-- Line Items -->
<table class="item-table">
<thead>
<tr>
<th style="width: 15%">Item</th>
<th style="width: 35%">Description</th>
<th style="width: 10%" class="right">Qty</th>
<th style="width: 15%" class="right">Rate</th>
<th style="width: 10%" class="right">Disc%</th>
<th style="width: 15%" class="right">Amount</th>
</tr>
</thead>
<tbody>
<#list record.item as item>
<tr>
<td>${item.item}</td>
<td>${item.description!}</td>
<td class="right">${item.quantity}</td>
<td class="right">${item.rate?string.currency}</td>
<td class="right">
<#if item.custcol_discount?has_content>
${item.custcol_discount}%
</#if>
</td>
<td class="right">${item.amount?string.currency}</td>
</tr>
</#list>
</tbody>
</table>
<!-- Totals -->
<table class="totals-table">
<tr>
<td class="label">Subtotal:</td>
<td class="right">${record.subtotal?string.currency}</td>
</tr>
<#if record.discounttotal?has_content && record.discounttotal != 0>
<tr>
<td class="label">Discount:</td>
<td class="right">(${record.discounttotal?string.currency})</td>
</tr>
</#if>
<#if record.taxtotal?has_content && record.taxtotal != 0>
<tr>
<td class="label">Tax:</td>
<td class="right">${record.taxtotal?string.currency}</td>
</tr>
</#if>
<#if record.shippingcost?has_content && record.shippingcost != 0>
<tr>
<td class="label">Shipping:</td>
<td class="right">${record.shippingcost?string.currency}</td>
</tr>
</#if>
<tr class="grand-total">
<td class="label">Total:</td>
<td class="right">${record.total?string.currency}</td>
</tr>
</table>
<!-- Memo -->
<#if record.memo?has_content>
<div style="margin-top: 20px;">
<strong>Notes:</strong><br/>
${record.memo}
</div>
</#if>
<!-- Footer -->
<div class="footer">
Thank you for your business!<br/>
Payment is due within ${record.terms!"30 days"}.
</div>
</body>
</pdf>
Page Formatting
Headers and Footers
<body header="nlheader" footer="nlfooter"
header-height="100px" footer-height="50px">
<!-- Page content -->
</body>
<!-- Define header macro -->
<macrolist>
<macro id="nlheader">
<table style="width: 100%;">
<tr>
<td style="width: 50%;">
<img src="${companyinformation.logourl}"
style="max-height: 60px;"/>
</td>
<td style="width: 50%; text-align: right;">
Invoice: ${record.tranid}
</td>
</tr>
</table>
</macro>
<macro id="nlfooter">
<table style="width: 100%;">
<tr>
<td style="width: 50%;">
${companyinformation.companyname}
</td>
<td style="width: 50%; text-align: right;">
Page <pagenumber/> of <totalpages/>
</td>
</tr>
</table>
</macro>
</macrolist>
Page Breaks
<!-- Force page break before element -->
<div style="page-break-before: always;">
New page content
</div>
<!-- Force page break after element -->
<div style="page-break-after: always;">
This appears on its own page
</div>
<!-- Keep content together (avoid breaking) -->
<div style="page-break-inside: avoid;">
This block stays together
</div>
Images and Logos
<!-- Company logo from File Cabinet -->
<img src="${companyinformation.logourl}"
style="max-height: 80px;"/>
<!-- Image from File Cabinet by ID -->
<img src="https://system.netsuite.com/core/media/media.nl?id=123&c=ACCOUNT&h=abc" />
<!-- Inline barcode (if supported) -->
<barcode value="${record.tranid}" type="code128"
height="30px" showValue="true"/>
Conditional Logic
<!-- If statement -->
<#if record.total gt 10000>
<p style="color: red; font-weight: bold;">HIGH VALUE ORDER</p>
</#if>
<!-- If-else -->
<#if record.status == "Paid In Full">
<span style="color: green;">PAID</span>
<#elseif record.status == "Partially Paid">
<span style="color: orange;">PARTIAL</span>
<#else>
<span style="color: red;">UNPAID</span>
</#if>
<!-- Check if field exists -->
<#if record.memo?has_content>
<p>Memo: ${record.memo}</p>
</#if>
<!-- List with index -->
<#list record.item as item>
<tr class="${item?is_odd_item?then('odd', 'even')}">
<td>${item?counter}</td> <!-- 1, 2, 3, ... -->
<td>${item.item}</td>
</tr>
</#list>
Applying Template to Transactions
TEMPLATE ASSIGNMENT
═══════════════════════════════════════════════════════════════════════════════
1. Create/Edit Template:
Customization → Forms → Advanced PDF/HTML Templates
2. Assign to Transaction Form:
Customization → Forms → Transaction Forms → [Your Form]
→ Printing Fields → PDF Layout: [Select Template]
3. Assign to Transaction Type:
Setup → Company → Company Information
→ Printing & Fax → Transaction Forms
4. Role-Based Assignment:
Customization → Forms → Transaction Forms
→ Select form → Roles tab → Assign to specific roles
Best Practices
ADVANCED PDF BEST PRACTICES
═══════════════════════════════════════════════════════════════════════════════
DESIGN:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ✓ Use tables for layout (more reliable than CSS positioning) │
│ ✓ Keep font sizes readable (9-12pt for body text) │
│ ✓ Use null-safe operators (${field!} or ${field!"default"}) │
│ ✓ Test with various data lengths │
│ ✓ Include page numbers for multi-page documents │
└─────────────────────────────────────────────────────────────────────────────┘
PERFORMANCE:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ✓ Minimize image sizes │
│ ✓ Avoid complex nested tables │
│ ✓ Use efficient CSS (avoid redundant styles) │
│ ✓ Test with maximum line item count │
└─────────────────────────────────────────────────────────────────────────────┘
MAINTENANCE:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ✓ Comment sections of template │
│ ✓ Use consistent naming for styles │
│ ✓ Version control templates (SDF) │
│ ✓ Test in sandbox before production │
└─────────────────────────────────────────────────────────────────────────────┘
Next Steps
| Goal | Go To |
|---|---|
| Script-driven PDF | Suitelet PDF Generation → |
| Bulk PDF generation | Bulk PDF Generation → |
| Return to PDF overview | PDF Customization → |