How-toOracle NetSuiteAdvanced PDF/HTML Templates

How to Customize NetSuite Advanced PDF/HTML Templates

Question
how to customize NetSuite advanced PDF HTML template

Also searched as

  • NetSuite advanced PDF template FreeMarker tutorial
  • how to add a table column to NetSuite invoice PDF
  • NetSuite advanced HTML template not showing custom field
  • customize NetSuite PO template layout

Short answer

NetSuite Advanced PDF/HTML templates use FreeMarker markup inside an XML-for-PDF or HTML wrapper, edited under Customization > Forms > Advanced PDF/HTML Templates. You reference transaction body fields with ${record.fieldid}, loop line items with a list directive over record.item, and control layout with standard XSL-FO style tags in the barcode/pdf namespace.

Applies to: NetSuite Advanced PDF/HTML Templates (SuiteBuilder), all editions that have Advanced PDF/HTML Templates enabled, transaction forms (invoice, sales order, purchase order, statement)

How to edit an Advanced PDF/HTML template

  1. 1Go to Customization > Forms > Advanced PDF/HTML Templates, click a standard template (e.g. Standard Invoice) and choose Customize to create an editable copy.
  2. 2In the template editor, locate the field you want using the Insert Field Type dropdown on the right, which generates the correct ${record.fieldid} or ${record.item.fieldid} syntax to paste in.
  3. 3For body fields use ${record.entity}, ${record.trandate}, ${record.total}; for custom body fields use ${record.custbody_yourfield}.
  4. 4For line items, work inside the existing <#list record.item as item> ... </#list> block and add a new <td>${item.custcol_yourfield}</td> cell matching the existing column structure.
  5. 5Add conditional sections with <#if record.custbody_flag>...</#if> so blocks only render when a field is set or non-empty.
  6. 6Preview with a real transaction using the Preview button in the template editor before saving, since some fields only render correctly against live data.
  7. 7Assign the customized template to the transaction form under Customization > Forms > Transaction Forms > your form > Printing/Email tab.
  8. 8If a field shows blank, confirm it is checked Store Value and Available Without Login is not required, and that the field is on the form's field list (sourced fields sometimes need the source field added too).

FreeMarker basics for NetSuite templates

Advanced PDF/HTML templates are rendered by FreeMarker, a Java templating language, wrapped in either an XSL-FO-like <pdf> tag set for PDF output or plain HTML for HTML/email templates. The record object exposes the transaction's body fields directly, and record.item (or record.expense, record.time depending on transaction type) exposes the sublist as an iterable list.

Custom fields follow NetSuite's standard prefix convention: custbody_ for transaction body fields, custcol_ for line item column fields, custentity_ for entity-level fields you may need to pull via record.entity.custentity_yourfield. Getting the prefix and internal ID exactly right is the most common source of blank output.

Looping and formatting sublists

The existing <#list record.item as item> block already handles item, quantity, rate, amount. Add new columns inside the same <tr> structure rather than creating a second loop, otherwise the PDF renders two separate item tables or misaligned rows.

Number and date formatting uses FreeMarker built-ins: ${item.rate?string("#,##0.00")} for currency, ${record.trandate?string("MM/dd/yyyy")} for dates. Skipping the format string is the usual cause of PDFs showing raw unformatted numbers or ISO date strings to customers.

<#list record.item as item>
<tr>
<td>${item.item}</td>
<td>${item.custcol_lot_number!""}</td>
<td align="right">${item.quantity}</td>
<td align="right">${item.rate?string("#,##0.00")}</td>
</tr>
</#list>

Conditional logic and subtotals

Use <#if> / <#else> / </#if> to show or hide blocks, such as a discount line that only appears when record.discounttotal is non-zero, or a different footer for international customers based on record.shipcountry.

For running totals across a filtered subset of lines (for example only taxable lines), FreeMarker does not aggregate automatically inside the template; either add a saved search-driven summary field on the transaction beforehand, or use a <#assign total = 0> accumulator variable incremented inside the list loop.

<#assign taxableTotal = 0>
<#list record.item as item>
  <#if item.taxcode != "NT">
    <#assign taxableTotal = taxableTotal + item.amount>
  </#if>
</#list>
Taxable subtotal: ${taxableTotal?string("#,##0.00")}

Common rendering errors

FTL parsing errors on save usually point to an unclosed <#if> or <#list> tag, or a field reference with a typo in the internal ID; the error message gives a line number in the template source, not the rendered output, so count from the top of the FreeMarker markup.

A field that renders as 'null' rather than blank means the field exists but returned a null value that was not handled; wrap it with the FreeMarker default operator, e.g. ${record.custbody_note!""}, to fall back to an empty string.

Common pitfalls

  • !Editing the standard template directly instead of Customize-ing a copy, which blocks changes on the next NetSuite release upgrade.
  • !Forgetting the ! default operator on optional custom fields, causing 'null' to print on records where the field was never set.
  • !Adding a second <#list record.item as item> block instead of extending the existing one, which duplicates or misaligns the item table.
  • !Not adding the sourced field to the transaction form's field list when the PDF references a field sourced from another record.
  • !Testing only against one transaction that happens to have every field populated, then finding blank fields break layout on real customer records with sparse data.
  • !Confusing custbody_/custcol_/custentity_ prefixes, which is the single most common cause of a field simply not rendering.

How an ERP-grounded AI assistant handles this

ERPray, grounded on your NetSuite account's actual form and field configuration, can generate the exact FreeMarker snippet for a requested change, including the correct custbody_/custcol_ internal IDs pulled from your account rather than guessed, and flag when a referenced field is not yet on the transaction form's field list before you hit a blank-render surprise in production.

Frequently asked questions

Can I use JavaScript inside an Advanced PDF/HTML template?

No. Advanced PDF/HTML templates render server-side with FreeMarker only; there is no client-side JavaScript execution in the PDF generation pipeline. Any dynamic logic must be expressed in FreeMarker directives or precomputed on the record before printing.

Why does my custom field show on the form but not the PDF?

The PDF template references fields by internal ID independently of the form layout. Confirm the field's internal ID and prefix (custbody_/custcol_/custentity_) match exactly what you typed in ${record.fieldid}, and that Store Value is checked on the field definition.

How do I test template changes without emailing real customers?

Use the Preview button in the template editor against an existing transaction, or print to PDF from a sandbox account transaction record before assigning the template to a live transaction form in production.

Can Advanced PDF templates pull data from a different record type?

Yes, via dot-notation through a relationship the record already exposes, such as record.entity.custentity_field for a customer-level custom field, but you cannot arbitrarily query unrelated records the way a SuiteScript search can.

Related

How-to

How to Import CSV Data Into NetSuite Reliably

NetSuite's CSV Import Assistant, under Setup > Import/Export > Import CSV Records, maps spreadsheet columns to record fields, matches existing records by internal ID or a chosen field, and can create, update, or upsert in a single run. The most reliable imports use a saved import map, reference records by internal ID where possible, and stage the file with a header row that exactly matches expected column types.

How-to

How to Build NetSuite Dashboards and KPI Scorecards

NetSuite dashboards are assembled from portlets (KPI, KPI Scorecard, List, Trend Graph, Report Snapshot) added via Personalize Dashboard, where KPI portlets show a single metric with a comparison period and KPI Scorecards show a table of multiple KPIs across time periods and can be published to a role for standardized reporting.

How-to

How to use formula fields in a NetSuite saved search

In the saved search Results tab, add a column, set Field to "Formula (Text)", "Formula (Numeric)", "Formula (Date)" or "Formula (Currency)", then type an Oracle SQL expression into the Formula box using curly braces around field IDs, e.g. {trandate} or {item.custitem_weight}. Formula fields can also go on the Criteria tab so you can filter on the calculated value itself.

How-to

How to build a workflow in NetSuite with SuiteFlow

Go to Customization > Workflow > Workflows > New, pick the record type and select Server, Client, or both as the trigger context, then build states and transitions on the workflow diagram, attaching actions (Set Field Value, Send Email, Create Record, Custom Action Script) to each state or transition. Release the workflow (top right dropdown, Testing to Released) once validated, since a workflow left in Testing only fires for the workflow owner.

Error fix

Fix NetSuite SSS_USAGE_LIMIT_EXCEEDED Error

SSS_USAGE_LIMIT_EXCEEDED fires when a SuiteScript execution consumes all the governance units (usage points) allotted to its script type before it finishes. Fix it by checking runtime.getCurrentScript().getRemainingUsage() before expensive calls, yielding or rescheduling in Scheduled scripts, and moving heavy record-count work into Map/Reduce, which yields automatically across stages.

Error fix

Fix NetSuite INVALID_FLD_VALUE Error

INVALID_FLD_VALUE means NetSuite rejected a value you tried to set on a field because it does not match the field's expected type, list option, or reference record. Fix it by confirming the internal ID or text value actually exists on that field's source list and matches the field's value type (text versus list versus record reference) before setting it.

AI for ERP

AI for NetSuite, Beyond the Built-In Text Tools

NetSuite's built-in AI covers text generation, not grounded answers on your own data. See how a private LLM over SuiteQL adds real Q&A and controls.

AI for ERP

AI agents for NetSuite manufacturing operations

AI agents for NetSuite manufacturing: WIP tracking, routing exceptions, and work order status grounded in SuiteQL, with human approval on anything that writes back.

Stuck on Oracle NetSuite?

Talk to engineers who work inside Oracle NetSuite every week, and who build private AI that answers these questions from your own ERP data.