Customize Templates
The Template IDE is the editing environment for designing invoice and account statement templates. This guide covers the three-panel interface, how to work with variables, write Jinja2 template code, style with CSS, and test your changes with the live preview.
Overview: The Three-Panel Interface
The Template IDE is divided into three areas:
- Variables Sidebar (left): Browse available data fields organized by category. Click a variable to insert it at the cursor position in the code editor.
- Code Editor (center): Write and edit HTML and CSS using CodeMirror with syntax highlighting. Content is organized into tabs: Header, Invoice Content, Footer, Account Statement Content, CSS, and Translations.
- Live PDF Preview (right): See a rendered PDF preview. The preview uses sample data from the variable definitions.
Open any template and click Edit to launch the IDE. The sidebar can be collapsed to give more space to the editor, and the divider between editor and preview can be dragged to resize the panels.
The Variables Sidebar
The left panel shows all data fields available to your template. Variables are defined in the template's JSON metadata and organized by prefix.
Variable Categories
Common variable prefixes include:
- invoice:
invoice.invoice_number,invoice.invoice_date,invoice.benefit_period_start, etc. - customer:
customer.name,customer.customer_number,customer.vat_id,customer.address.*(with sub-fields likestreet,city,zip_code,country) - company:
company.name,company.address,company.tax_id,company.vat_id, etc. - bank_account:
bank_account.bank_name,bank_account.iban,bank_account.bic - payment_terms:
payment_terms.name,payment_terms.days,payment_terms.date,payment_terms.text - positions[]:
positions[].title,positions[].quantity,positions[].unit_price,positions[].total,positions[].unit, etc. - statement:
statement.start_date,statement.end_date,statement.start_balance,statement.end_balance,statement.transactions
Inserting Variables
To insert a variable into your template:
- Place your cursor in the code editor where you want the variable
- Click a variable name in the sidebar
- The variable is inserted at the cursor position using Jinja2 syntax
For example, clicking company.name inserts {{ company.name }}.
Searching Variables
Use the search field at the top of the sidebar to filter variables. Type "date" to find all date-related fields, or "total" to find fields related to totals.
The Code Editor
The center panel is a CodeMirror-based editor with syntax highlighting for HTML and CSS.
Template Sections (Tabs)
Your template code is organized into tabs:
- Header: Content rendered at the top of every page (logo, company name, invoice title)
- Invoice Content: The main body of an invoice (line items table, totals, customer details)
- Footer: Content rendered at the bottom of every page (bank details, payment terms, legal notices)
- Account Statement Content: Alternative body content used when generating account statements instead of invoices
- CSS: All styling that applies to the entire template
- Translations: JSON-based translation keys for multi-language text (see Template Translations)
When a PDF is generated, docs101 assembles a complete HTML document by combining the CSS (in a <style> tag), Header (in a .header div), Footer (in a .footer div), and either Invoice Content or Account Statement Content (in a .content div). This assembled HTML is sent to the rendering service along with the data model.
Basic HTML Structure
A typical invoice content section:
<div class="invoice-header">
<h1>{{ company.name }}</h1>
<p>{{ company.address }}</p>
</div>
<table class="invoice-items">
<thead>
<tr>
<th>{{ t('col_description') }}</th>
<th>{{ t('col_quantity') }}</th>
<th>{{ t('col_unit_price') }}</th>
<th>{{ t('col_total') }}</th>
</tr>
</thead>
<tbody>
{% for position in positions %}
<tr>
<td>{{ position.title }}</td>
<td>{{ position.quantity }}</td>
<td>{{ position.unit_price }}</td>
<td>{{ position.total }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Jinja2 Syntax
Templates use Jinja2 for all dynamic content. The rendering service (DocsRabbit) processes the Jinja2 syntax and replaces it with actual data.
Variables
Display data values with double curly braces:
{{ company.name }}
{{ invoice.invoice_number }}
{{ customer.name }}
{{ invoice.invoice_date }}
Filters
Format variables using Jinja2 filters with the pipe (|) operator:
{{ customer.name|upper }}
{{ invoice.invoice_date }}
Dates in docs101 templates are pre-formatted by the backend according to the company's date format setting before being passed to the template. You do not need to apply date filters in your template code — just output the variable directly.
Conditionals
Show or hide sections based on data values:
{% if customer.vat_id %}
<p>VAT ID: {{ customer.vat_id }}</p>
{% endif %}
{% if IS_REVERSE_CHARGE %}
<p class="notice">{{ t('reverse_charge_notice') }}</p>
{% endif %}
{% if IS_INVOICE_DOCUMENT %}
<!-- Invoice-specific content -->
{% endif %}
{% if IS_ACCOUNT_STATEMENT_DOCUMENT %}
<!-- Statement-specific content -->
{% endif %}
Loops
Iterate over lists of items — most commonly invoice positions:
{% for position in positions %}
<tr>
<td>{{ position.title }}</td>
<td>{{ position.quantity }}</td>
<td>{{ position.unit }}</td>
<td>{{ position.unit_price }}</td>
<td>{{ position.total }}</td>
</tr>
{% endfor %}
Access loop metadata for special styling:
{% for position in positions %}
<tr class="{% if loop.first %}first-row{% endif %} {% if loop.last %}last-row{% endif %}">
<td>#{{ loop.index }}</td>
<td>{{ position.title }}</td>
</tr>
{% endfor %}
The Translation Function
Use {{ t('key_name') }} to output translated text. The correct language is selected automatically based on the customer's language preference:
<h1>{{ t('invoice_title') }}</h1>
<th>{{ t('col_description') }}</th>
See Template Translations for full details.
CSS Styling
Switch to the CSS tab to control all visual aspects of the template.
Common CSS Tasks
Set the Base Font
body {
font-family: Arial, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #333;
}
Style the Header
.invoice-header {
background-color: #003366;
color: white;
padding: 20px;
border-radius: 4px;
}
.invoice-header h1 {
margin: 0;
font-size: 24pt;
}
Style the Line Items Table
.invoice-items {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.invoice-items th {
background-color: #f0f0f0;
padding: 10px;
text-align: left;
font-weight: bold;
border-bottom: 2px solid #333;
}
.invoice-items td {
padding: 8px 10px;
border-bottom: 1px solid #ddd;
}
.invoice-items tr:nth-child(even) {
background-color: #f9f9f9;
}
Position the Company Logo
.company-logo {
max-width: 150px;
margin-bottom: 20px;
}
<img src="{{ company.logo_url }}" class="company-logo" alt="Company Logo">
CSS Best Practices
- Use relative units (%, em) for widths where possible for better PDF rendering
- Define a consistent color palette at the top of your CSS using comments
- Use CSS classes rather than inline styles for maintainability
- Test in multiple PDF readers — some may not support advanced CSS features
- Keep CSS organized by section (header, table, totals, footer)
Live Preview
The right panel renders a PDF preview using the same rendering service that generates your final invoices.
Using the Preview
- Edit your template code in the center panel
- Save your changes to trigger a preview update
- The preview panel shows the rendered PDF
- Use the zoom controls to adjust the preview size
- Scroll to see all pages if the template produces multi-page output
The preview uses the example values defined in the template's variable metadata. The rendering service generates sample positions to test multi-page layouts.
Common Preview Issues
If the preview does not look right:
- Variable not showing: Check the variable name matches what is defined in the variables metadata (e.g.,
company.namenotcompany.fullname) - Table misaligned: Verify CSS class names match between HTML and CSS
- Font too small or large: Adjust
font-sizein the CSS tab - Colors not applying: Look for conflicting or overriding CSS rules
Saving and Versioning
Save Your Changes
Click Save to persist your changes. Each save increments the template's version number automatically and updates the last_updated timestamp.
The following fields are saved: name, content (invoice body), account statement content, header, footer, CSS, and translations.
The version number increments with each save, giving you a record of how many revisions have been made. You can safely experiment knowing your changes are tracked.
Template Options
Templates support boolean options that control conditional behavior in the rendered output. These are configured in the template's options metadata and are automatically injected into the template model.
For example, a template may define an option like SHOW_BANK_DETAILS that controls whether the bank details section is displayed:
{% if SHOW_BANK_DETAILS %}
<div class="bank-details">
<p>{{ bank_account.bank_name }}</p>
<p>IBAN: {{ bank_account.iban }}</p>
</div>
{% endif %}
Additionally, some options are auto-detected:
IS_REVERSE_CHARGE: Automatically set totrueif any position has VAT category code "AE"IS_SMALL_BUSINESS_EXEMPT: Automatically set totrueif any position has VAT category code "O"IS_INVOICE_DOCUMENT/IS_ACCOUNT_STATEMENT_DOCUMENT: Set based on the document type being generated
Common Customizations
Example: Conditional Payment Status Badge
{% if invoice.is_paid %}
<span class="status-badge paid">PAID</span>
{% elif invoice.is_overdue %}
<span class="status-badge overdue">OVERDUE</span>
{% else %}
<span class="status-badge pending">PENDING</span>
{% endif %}
.status-badge {
display: inline-block;
padding: 5px 15px;
border-radius: 20px;
font-weight: bold;
font-size: 10pt;
}
.status-badge.paid { background-color: #4CAF50; color: white; }
.status-badge.overdue { background-color: #f44336; color: white; }
.status-badge.pending { background-color: #ff9800; color: white; }
Example: Two-Column Layout
.two-column {
display: flex;
gap: 20px;
}
.two-column > div {
flex: 1;
}
<div class="two-column">
<div>
<h4>{{ t('bill_to') }}</h4>
<p>{{ customer.name }}</p>
</div>
<div>
<h4>{{ t('invoice_details') }}</h4>
<p>{{ invoice.invoice_number }}</p>
</div>
</div>
Example: Handling Missing Data
{% if customer.vat_id %}
<p>VAT ID: {{ customer.vat_id }}</p>
{% endif %}
{% if payment_terms.text %}
<div class="payment-terms">
<strong>{{ t('payment_terms_label') }}:</strong>
{{ payment_terms.text }}
</div>
{% endif %}
Testing Your Customizations
- Save your template in the IDE
- Review the PDF preview in the right panel
- Create a test invoice and select your template
- Download the generated PDF and review it
- Print the PDF to check formatting on paper
- Test with different languages if translations are configured
To add multi-language support, continue to Template Translations.