Skip to main content

Template Syntax

Create your own DOCX templates: write a regular Word document, drop in placeholders where you want data, and NullReport fills it all in on export.

Cheat sheet

Want to…Syntax
Insert a value{{title}}
Section content{{sections.scope}}
Summary count{{critical_count}}
Loop over findings{{#findings}}{{/findings}}
Number a finding{{_num}} or {{_num | changeID:'FINDING'}}
Show conditionally{{#if critical_count > 0}}{{/if}}
Fallback value{{assessor | d:'N/A'}}
Format a date{{assessment_date | date:'long'}}
Force a page break{{pageBreak}}
Bookmark / link to it{{bookmark}} / {{ref:title}}
The number-one gotcha: Word splits placeholders

When you type {{title}}, Word sometimes splits it into fragments behind the scenes ({{, title, }}), usually if you pause, change formatting mid-placeholder, or paste part of it. The placeholder then won't resolve. Fix: delete it and retype it in one go, or type it in a plain-text editor and paste the whole thing in.

Placeholders

Wrap a variable name in double curly braces: {{variable_name}}.

Report

PlaceholderBecomes
{{title}}Report title
{{client}}Client name
{{status}}Report status

Metadata fields become snake_case (Assessment Date becomes {{assessment_date}}, Client Contact becomes {{client_contact}}):

PlaceholderSource
{{assessment_date}}Assessment Date
{{report_version}}Report Version
{{assessor}}Assessor

Sections can be placed by name, with either form:

{{sections.executive_summary}}
{{section_scope}}
tip

Section content is rich text from the editor, and NullReport converts it to native Word formatting (bold, lists, tables, images, and all). The same applies to finding fields like {{description}}.

Summary counts: {{total_findings}}, {{critical_count}}, {{high_count}}, {{medium_count}}, {{low_count}}, {{info_count}}.

Loops over findings

Everything between {{#findings}} and {{/findings}} repeats once per finding:

{{#findings}}
...finding placeholders...
{{/findings}}

Finding variables: {{title}}, {{severity}}, {{cvss_score}}, {{cvss_vector}}, {{description}}, {{details}}, {{impact}}, {{remediation}}, plus any custom field in snake_case (Affected Systems becomes {{affected_systems}}).

Loop variables:

PlaceholderBecomesExample
{{_num}}Auto-incrementing number1, 2, 3…
{{_index}}Zero-based index0, 1, 2…
{{_first}}True on the first findingtrue/false
{{_last}}True on the last findingtrue/false

If the loop markers sit inside a table row, the whole row duplicates per finding (the most common pattern). Inside a list item, the item duplicates:

| {{#findings}}{{_num}} | {{title}} | {{severity}} | {{cvss_score}}{{/findings}} |

Conditionals

Show or hide content with {{#if}}{{/if}} (optionally {{else}}). The block renders when the value is truthy (not zero, empty, or null):

{{#if critical_count}}
This report contains critical findings.
{{else}}
No critical findings were identified.
{{/if}}

Comparisons use ==, !=, >, >=, <, and <=:

{{#if severity == 'Critical'}} ... {{/if}}
{{#if cvss_score >= 9.0}} ... {{/if}}
{{#if total_findings < 5}} ... {{/if}}

Conditionals work inside loops with each finding's data:

{{#findings}}
{{title}}
{{#if cvss_score >= 9.0}}CVSS: {{cvss_score}} (Critical){{/if}}
{{/findings}}

Filters

Transform a value with the pipe |:

Assessor: {{assessor | d:'Not assigned'}}
FilterExampleResult
upper{{title | upper}}SQL INJECTION
lower{{title | lower}}sql injection
title{{name | title}}John Doe
capitalize{{status | capitalize}}Draft
d / default{{assessor | d:'N/A'}}N/A (if empty)
changeID{{_num | changeID:'FINDING'}}FINDING-001

Dates: {{assessment_date | date:'short'}} gives 2/20/2026, …'long' gives February 20, 2026, and …'iso' gives 2026-02-20. Chain filters left to right: {{assessor | d:'N/A' | upper}}.

Page breaks

{{pageBreak}} forces a new page. To page-break after every finding except the last:

{{#findings}}
{{_num}}. {{title}}
{{description}}
{{#if _last}}{{else}}{{pageBreak}}{{/if}}
{{/findings}}

Finding IDs

changeID prefixes and zero-pads finding numbers to three digits:

{{_num | changeID:'FINDING'}}   →  FINDING-001
{{_num | changeID:'F'}} → F-001

Bookmarks & cross-references

Link a summary table to each finding's details. Use {{bookmark}} inside the loop to mark where a finding begins (it creates finding_1, finding_2, and so on, or pass a prefix with {{bookmark:'finding_'}}), and {{ref:field}} to create a hyperlink to it ({{ref:'See details'}} for static text):

## Summary
| ID | Finding | Severity |
|----|---------|----------|
{{#findings}}
| {{_num | changeID:'FINDING'}} | {{ref:title}} | {{severity}} |
{{/findings}}

{{pageBreak}}

## Detailed Findings
{{#findings}}
{{bookmark}}
### {{_num | changeID:'FINDING'}} - {{title}}
{{description}}
{{#if _last}}{{else}}{{pageBreak}}{{/if}}
{{/findings}}

Clicking a finding in the summary jumps Word to its details.

Images & rich text

Images pasted or uploaded in the editor are embedded automatically, with no placeholder needed (PNG, JPEG, GIF, WebP). They scale to fit (max 6 inches), keep the width you set, and respect left, center, or right alignment. All editor formatting (bold, headings, lists, tables, code, blockquotes, links, text color, highlight) converts to native Word styling wherever a rich-text field like {{description}} or {{sections.scope}} is used.

Empty fields

A placeholder for an empty field renders as nothing: blank space, no error, and no leftover {{…}}. Placeholder styling comes from your Word formatting, so make {{title}} 24pt bold and the title comes out 24pt bold. Placeholders work in headers and footers too.

Worked example

[ Cover ]
{{title}}
Prepared for: {{client}}
Date: {{assessment_date | date:'long'}} Version: {{report_version}}

[ Executive Summary ]
{{sections.executive_summary}}

[ Findings Summary ]
Total: {{total_findings}} Critical: {{critical_count}} | High: {{high_count}} | Medium: {{medium_count}} | Low: {{low_count}} | Info: {{info_count}}

[ Findings ]
{{#findings}}
{{_num | changeID:'FINDING'}}. {{title}}
Severity: {{severity}} CVSS: {{cvss_score}}
Description: {{description}}
Impact: {{impact}}
Remediation: {{remediation}}
{{#if _last}}{{else}}{{pageBreak}}{{/if}}
{{/findings}}

[ Recommendations ]
{{sections.recommendations}}
Get the exact variables for a report

In the report editor, Export → Copy Template Syntax copies every variable available for that report, including your custom metadata and finding fields, to your clipboard.