Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Custom templates

reptr ships with a built-in HTML template. You can override it — or add new formats — by placing template files in the templates/ directory of your engagement.

How template resolution works

When rendering output, reptr looks for a template in this order:

  1. templates/<format>.html (or .jinja) in the engagement root
  2. The embedded default template

If a custom template is found it is used in place of the default. reptr watch automatically rebuilds when files in templates/ change.

Template engine

Templates use MiniJinja — a Rust port of Jinja2. Most Jinja2 syntax works:

  • {{ variable }} — output a value
  • {% for ... %} / {% endfor %} — loops
  • {% if ... %} / {% endif %} — conditionals
  • {{ value | filter }} — filters

Context variables

The following variables are available in every template.

engagement

The top-level engagement object.

VariableTypeDescription
engagement.slugstringEngagement slug from reptr.toml
engagement.namestringFull engagement name
engagement.datestringEngagement start date
engagement.report_datestringReport issue date
engagement.versionstringReport version

client

VariableTypeDescription
client.namestringClient organisation name
client.contact_namestringPrimary contact name
client.contact_emailstringPrimary contact email
client.logostring or nullPath to logo image (base64-encoded in HTML output)

findings

A list of finding objects, sorted by severity then ID.

VariableTypeDescription
finding.idstringFinding ID (e.g. F-001)
finding.titlestringFinding title
finding.severitystringcritical, high, medium, low, or info
finding.statusstringopen, resolved, accepted, or false_positive
finding.affected_assetslist of stringsAffected hosts or URLs
finding.tagslist of stringsFinding tags
finding.cvssstring or nullCVSS score (e.g. "9.8")
finding.cvss_vectorstring or nullCVSS 3.x vector string
finding.cwestring or nullCWE identifier
finding.owaspstring or nullOWASP Top 10 category
finding.body_htmlstringRendered HTML from the Markdown body

summary

Pre-computed counts for convenience.

VariableTypeDescription
summary.totalintegerTotal number of findings
summary.openintegerOpen findings
summary.resolvedintegerResolved findings
summary.criticalintegerCritical findings (all statuses)
summary.highintegerHigh findings
summary.mediumintegerMedium findings
summary.lowintegerLow findings
summary.infointegerInfo findings

Example: minimal HTML template

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{ engagement.name }} — Security Assessment</title>
</head>
<body>
  <h1>{{ engagement.name }}</h1>
  <p>Client: {{ client.name }} · Date: {{ engagement.report_date }}</p>
  <p>{{ summary.total }} findings ({{ summary.open }} open)</p>

  {% for finding in findings %}
  <section id="{{ finding.id }}">
    <h2>[{{ finding.severity | upper }}] {{ finding.id }}: {{ finding.title }}</h2>
    <p>Status: {{ finding.status }}</p>
    {{ finding.body_html }}
  </section>
  {% endfor %}
</body>
</html>

Save this as templates/report.html in your engagement root, then run reptr build.

Example: severity badge filter

MiniJinja lets you define macros for repeated patterns:

{% macro severity_badge(s) %}
  <span class="badge badge-{{ s }}">{{ s | upper }}</span>
{% endmacro %}

{% for finding in findings %}
  {{ severity_badge(finding.severity) }} {{ finding.title }}
{% endfor %}

Tips

  • Access the built-in template source by running reptr build --dump-template (outputs the embedded HTML to stdout — useful as a starting point).
  • Keep images in assets/ and reference them with relative paths. reptr inlines them as base64 in HTML output so the file is self-contained.
  • The body_html field already contains rendered, sanitised HTML. Do not double-escape it — use {{ finding.body_html | safe }} if your Jinja environment escapes by default.