Kill Bill invoices can be rendered as HTML using the render HTML invoice API. This can be useful, for instance, to show the invoices directly to your customer on the billing section of your website. This document explains how to configure custom invoice templates.
Invoice Template Configuration Overview
Kill Bill uses the mustache engine for generating HTML invoices.
There are three things you can customize when generating an HTML invoice:
-
A template: This constitutes the body of the template. It includes static text as well as variables which are replaced at runtime by the mustache engine.
-
Translation files: This includes one or more files that specify translations corresponding to a locale. Invoice translations supply the text strings used in the invoice template itself — labels like "Invoice Date" or "Balance". You can read the Kill Bill Internationalization document to know more about resource bundles.
-
Branding information: Company details, appearance/style, and logo, which can be set as a tenant-wide default and optionally overridden per template.
Kill Bill ships with a default template, translations, and branding out of the box — see below for how to customize any of them.
Managing Templates, Translations & Branding via Aviate
While templates, translations, and branding information can all be uploaded directly via the Kill Bill API or Kaui, Aviate — the premium UI/plugin layer on top of Kill Bill — is the recommended way to manage all three. Aviate provides a guided experience with a live invoice preview, inheritance indicators, and inline validation.
|
Note
|
Before you can configure templates, translations, or branding via Aviate, the Kill Bill instance must first be added as a deployment. See Aviate Deployment Management for details. |
Organization-level branding
-
Navigate to Configuration → Settings in Aviate.
-
Set the tenant-wide defaults for company information and appearance (logo, primary brand color). These apply to all templates (invoices, emails, and quotes) by default, and can be overridden per template.
Invoice-template-level template, branding, and translations
Under Configuration → Invoices, configure the invoice template as follows:
-
Override company information and appearance for this template only, if required. Fields are pre-filled from the organization-level settings, with an indicator showing which are inherited versus overridden.
-
Customize style details such as text color, table heading background/text color, and table border color.
-
Select the desired language from the translations drop-down and modify the translations as required.
-
Use the live invoice preview panel to confirm your changes render as expected.
-
Click Save Template to save the changes.
-
Advanced users can also edit the underlying mustache template directly via Advanced View, instead of the structured fields in steps 1–3. This is not recommended unless you are familiar with the mustache template engine.
Managing Invoice Template, Translations & Branding via API/Kaui
If you are not using Aviate, or need to script/automate these uploads, you can manage the template, translations, and branding directly through the Kill Bill API or Kaui, as described below.
At a high-level, you need to execute the steps given below:
-
Upload the invoice template
-
Upload the translation for all the supported locales
-
Upload the catalog translation for all the locales (if required)
-
Optionally, upload branding information via the API
Step 1 - Upload Invoice Template
-
Create a file
$SOME_PATH_PREFIX/HtmlInvoiceTemplate.mustacheas follows:<!doctype html> <html> <head> <meta charset="utf-8"> <title>{{text.invoiceTitle}}</title> <style> /*! * Base layout adapted from sparksuite/invoice.css */ /* =========================== CSS VARIABLES (editable) =========================== */ :root { --text-color: {{brand.textColor}}; --table-heading-bg-color: {{brand.tableHeadingBgColor}}; --table-heading-text-color: {{brand.tableHeadingTextColor}}; --container-border-color: #dddddd; --table-border-color: {{brand.tableBorderColor}}; --logo-max-width: 220px; } body { margin: 0; padding: 24px 8px; background: #f5f7fa; } /* =========================== INVOICE BASE STYLES =========================== */ .invoice-box { max-width: 640px; margin: auto; padding: 30px; background: #ffffff; border: 1px solid var(--container-border-color); box-shadow: none; font-size: 15px; line-height: 24px; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: var(--text-color); } .invoice-box table { width: 100%; line-height: inherit; text-align: left; border-collapse: collapse; border-spacing: 0; } .invoice-box table td { padding: 6px 8px; vertical-align: top; } .invoice-box table tr td:nth-child(3) { text-align: right; } .invoice-box table tr.top table td.title { padding-right: 20px; } .invoice-box table tr.top table td.title img { display: block; max-width: var(--logo-max-width); height: auto; } .invoice-box table tr.top table td:last-child { font-size: 14px; line-height: 20px; text-align: right; padding-top: 2px; } .invoice-box table tr.heading td { background: var(--table-heading-bg-color); color: var(--table-heading-text-color); border-top: 1px solid var(--table-border-color); border-bottom: 1px solid var(--table-border-color); font-weight: 700; padding: 10px 12px; } .invoice-box table tr.heading td:first-child { border-left: 1px solid var(--table-border-color); } .invoice-box table tr.heading td:last-child { border-right: 1px solid var(--table-border-color); } .invoice-box table tr.item td { border-bottom: 1px solid var(--table-border-color); padding: 10px 12px; margin: 0; } .invoice-box table tr.item td:first-child { border-left: 1px solid var(--table-border-color); } .invoice-box table tr.item td:last-child { border-right: 1px solid var(--table-border-color); } .invoice-box table tr.item.last td { border-bottom: 1px solid var(--table-border-color); } .invoice-box table tr.total td:nth-child(3) { padding-top: 6px; margin: 0; } #notes, #tnc { font-size: 12px; line-height: 18px; color: #666666; margin-top: 0; margin-bottom: 0; } #notes { font-weight: 700; padding-top: 14px; } #tnc { padding-top: 6px; } @media only screen and (max-width: 600px) { .invoice-box table tr.top table td { width: 100%; display: block; text-align: center; } .invoice-box table tr.information table td { width: 100%; display: block; text-align: center; } } </style> </head> <body> <div class="invoice-box"> <!-- {{#text.properties}}customKey{{/text.properties}} --> <table cellpadding="0" cellspacing="0"> <tr class="top"> <td colspan="3"> <table> <tr> <td class="title"> <img src="{{{logo.logo}}}" style="width:100%; max-width:300px;"> </td> <td></td> <td> {{text.invoiceTitle}} INV#{{invoice.invoiceNumber}}<br> {{text.invoiceDate}}{{invoice.formattedInvoiceDate}} </td> </tr> </table> </td> </tr> <tr class="information"> <td colspan="3"> <table> <tr> <td> {{company.companyName}}<br> {{company.companyAddress}}<br> {{company.companyCityProvincePostalCode}}<br> {{company.companyCountry}} </td> <td></td> <td> {{#account.name}}{{account.name}}<br>{{/account.name}} {{#account.companyName}}{{account.companyName}}<br>{{/account.companyName}} {{#account.address1}}{{account.address1}}<br>{{/account.address1}} {{#account.city}}{{account.city}}{{#account.stateOrProvince}}, {{account.stateOrProvince}}{{/account.stateOrProvince}} {{account.postalCode}}<br>{{/account.city}} {{account.country}} </td> </tr> </table> </td> </tr> <tr class="heading"> <td>{{text.invoiceItemServicePeriod}}</td> <td>{{text.invoiceItemDescription}}</td> <td>{{text.invoiceItemAmount}}</td> </tr> {{#invoice.invoiceItems}} <tr class="item last"> <td>{{formattedStartDate}}{{#formattedEndDate}} - {{formattedEndDate}}{{/formattedEndDate}}</td> <td>{{prettyPlanName}}</td> <td>{{formattedAmount}}</td> </tr> {{/invoice.invoiceItems}} <tr class="total"> <td></td> <td></td> <td>{{text.invoiceAmount}}{{invoice.formattedChargedAmount}}</td> </tr> <tr class="total"> <td></td> <td></td> <td>{{text.invoiceAmountPaid}}{{invoice.formattedPaidAmount}}</td> </tr> <tr class="total"> <td></td> <td></td> <td>{{text.invoiceBalance}}{{invoice.formattedBalance}}</td> </tr> </table> </div> </body> </html> -
Upload the file either via API call or Kaui.
-
To upload via API, execute the following
cURLcommand:curl -v \ -u admin:password \ -H "X-Killbill-ApiKey: bob" \ -H "X-Killbill-ApiSecret: lazar" \ -H 'X-Killbill-CreatedBy: admin' \ -H "Content-Type: text/html" \ -X POST \ --data-binary @$SOME_PATH_PREFIX/HtmlInvoiceTemplate.mustache \ http://127.0.0.1:8080/1.0/kb/invoices/template -
To upload via Kaui, go to
InvoiceTemplatetab on the admin tenant page:
-
Step 2- Upload Invoice Translations
-
Create a properties file for the desired locale (for example
fr_FR) as follows:invoiceEmailSubject=Nouvelle Facture invoiceTitle=FACTURE invoiceDate=Date: invoiceNumber=Facture # invoiceAmount=Montant à payer invoiceAmountPaid=Montant payé invoiceBalance=Nouveau montant accountOwnerName=Chauffeur invoiceItemBundleName=Armes invoiceItemDescription=Description invoiceItemServicePeriod=Période de facturation invoiceItemAmount=Montant processedPaymentCurrency=(*) Le payment à été payé en processedPaymentRate=Le taux de conversion est -
Upload the file either via API call or Kaui.
-
To upload via API, execute the following
cURLcommand:curl -v \ -u admin:password \ -H "X-Killbill-ApiKey: bob" \ -H "X-Killbill-ApiSecret: lazar" \ -H 'X-Killbill-CreatedBy: admin' \ -H "Content-Type: text/plain" \ -X POST \ --data-binary @$SOME_PATH_PREFIX/InvoiceTranslation_fr_FR.properties \ http://127.0.0.1:8080/1.0/kb/invoices/translation/fr_FR -
To upload via Kaui go to the
InvoiceTranslationtab on the admin tenant page:
Additional resource files can be uploaded for different locales as required.
-
Step 3 - Upload Catalog Translations
Catalog translations are different from invoice translations covered above. While invoice translations supply the text strings used in the invoice template itself, catalog translations supply the localized names of catalog entities, such as plan names (e.g. Gold plan translated as Plan Or in French), which are then substituted into the rendered invoice.
Catalog translations are optional. If you don’t upload one for a given locale, the invoice falls back to the plan names as defined in the catalog.
-
Create a catalog translation for the desired locale (for example
fr_FR) as follows:gold-monthly = plan Or mensuel -
Upload the file via API call or Kaui.
-
To upload via API, execute the following
cURLcommand:curl -v \ -u admin:password \ -H "X-Killbill-ApiKey: bob" \ -H "X-Killbill-ApiSecret: lazar" \ -H 'X-Killbill-CreatedBy: admin' \ -H "Content-Type: text/plain" \ -X POST \ --data-binary @$SOME_PATH_PREFIX/CatalogTranslation_fr_FR.properties \ http://127.0.0.1:8080/1.0/kb/invoices/catalogTranslation/fr_FR -
Alternatively, you can upload this file via Kaui by going to your admin tenant page (
CatalogTranslation) tab:
-
Step 4 - Upload Branding Information
Branding fields (company info, style, logo) can be set directly using the per-tenant user key/value endpoint.
Refer to the following table for the keys:
| Key | Description |
|---|---|
|
Organization-level (global default) company information |
|
Organization-level (global default) logo |
|
Organization-level (global default) style/brand information. Only accepts the |
|
Invoice-template-level company information (overrides |
|
Invoice-template-level logo (overrides |
|
Invoice-template-level style/brand information (overrides |
Each type of information is sent as a JSON payload. To override any of the three at the invoice-template level instead of organization-wide, use the corresponding INVOICE_TEMPLATE_* key instead.
-
Company information is uploaded to the
COMPANY_INFOkey (orINVOICE_TEMPLATE_COMPANY_INFOfor a template-level override) as follows:-
Define the JSON payload:
{"companyName":"CloudSprout","companyAddress":"East Street","companyCityProvincePostalCode":"New York","companyCountry":"United States","companyUrl":"cloudsprout.com"} -
Upload the payload via the following
cURLcommand:curl -v \ -X POST \ -u admin:password \ -H "X-Killbill-ApiKey: bob" \ -H "X-Killbill-ApiSecret: lazar" \ -H "Content-Type: application/json" \ -H 'X-Killbill-CreatedBy: admin' \ -d '{"companyName":"CloudSprout","companyAddress":"East Street","companyCityProvincePostalCode":"New York","companyCountry":"United States","companyUrl":"cloudsprout.com"}' \ http://127.0.0.1:8080/1.0/kb/tenants/userKeyValue/COMPANY_INFO
-
-
Style/brand information is uploaded to the
BRAND_INFOkey (orINVOICE_TEMPLATE_BRAND_INFOfor a template-level override) as follows:-
Define the JSON payload:
{"textColor":"red","tableBorderColor":"blue","tableHeadingBgColor":"yellow","tableHeadingTextColor":"green"} -
Upload the payload via the following
cURLcommand:curl -v \ -X POST \ -u admin:password \ -H "X-Killbill-ApiKey: bob" \ -H "X-Killbill-ApiSecret: lazar" \ -H "Content-Type: application/json" \ -H 'X-Killbill-CreatedBy: admin' \ -d '{"textColor":"red","tableBorderColor":"blue","tableHeadingBgColor":"yellow","tableHeadingTextColor":"green"}' \ http://127.0.0.1:8080/1.0/kb/tenants/userKeyValue/INVOICE_TEMPLATE_BRAND_INFONoteNote: BRAND_INFOonly accepts thetableHeadingBgColorfield — the other style fields (textColor,tableBorderColor,tableHeadingTextColor) are only available at the invoice-template level, viaINVOICE_TEMPLATE_BRAND_INFO.
-
-
The logo is uploaded to the
LOGO_INFOkey (orINVOICE_TEMPLATE_LOGO_INFOfor a template-level override) as a base64-encoded data URI:-
Define the JSON payload:
{"logo":"data:image/png;base64,/9j/4AAQSk..."} -
Upload the payload via the following
cURLcommand:curl -v \ -X POST \ -u admin:password \ -H "X-Killbill-ApiKey: bob" \ -H "X-Killbill-ApiSecret: lazar" \ -H "Content-Type: application/json" \ -H 'X-Killbill-CreatedBy: admin' \ -d '{"logo":"data:image/png;base64,/9j/4AAQSk..."}' \ http://127.0.0.1:8080/1.0/kb/tenants/userKeyValue/LOGO_INFO
-
Invoice Template Fields Reference
In addition to the company, logo, brand, and text (translation) fields covered above, you can use some additional fields in the template. This section lists those fields.
Invoice Fields
These fields are accessed under the invoice. namespace, e.g. {{invoice.chargedAmount}}.
For example, to use the chargedAmount field in the template, you can use the following snippet:
Invoice Amount Charged: #{{invoice.chargedAmount}}
The table below lists all the available invoice fields.
| Field | Description |
|---|---|
|
The invoice number |
|
The list of invoice items on this invoice (see Invoice Item Fields below). Iterate with |
|
Tracking IDs associated with the invoice |
|
Total charged amount on the invoice |
|
Charged amount before any adjustments |
|
Remaining balance on the invoice |
|
Charged amount, formatted per the account locale and invoice currency |
|
Paid amount, formatted per the account locale and invoice currency |
|
Balance, formatted per the account locale and invoice currency |
|
Currency the payment was processed in, if different from the invoice currency; |
|
Conversion rate used, if the payment was processed in a different currency; |
|
Whether this is a migration invoice |
|
Invoice date |
|
Target date used to generate the invoice |
|
Invoice currency |
|
Amount paid against this invoice |
|
Invoice date, formatted per the account locale |
|
Invoice ID |
|
Invoice creation timestamp |
|
Invoice last-updated timestamp |
|
Invoice status |
|
Whether this is a parent invoice |
|
Parent account ID, for child invoices |
|
Parent invoice ID, for child invoices |
|
Group ID associated with the invoice, if any |
|
Amount credited against this invoice |
|
Amount refunded against this invoice |
Invoice Item Fields
These fields are accessible within an {{#invoice.invoiceItems}}…{{/invoice.invoiceItems}} block. For example to use the invoice item prettyPlanName and formattedAmount fields, you can use the following snippet:
{{#invoice.invoiceItems}}
{{prettyPlanName}}: {{formattedAmount}}
{{/invoice.invoiceItems}}
The table below lists all the available invoice item fields.
| Field | Description |
|---|---|
|
Item amount |
|
Item currency |
|
Item amount, formatted per the account locale and invoice currency |
|
Item type (e.g. recurring, fixed, usage, tax, credit) |
|
Item description (translated, if a matching resource bundle entry exists) |
|
Item service period start date |
|
Item service period end date |
|
Start date, formatted per the account locale |
|
End date, formatted per the account locale |
|
ID of the invoice this item belongs to |
|
Account ID |
|
Child account ID, for items billed to a child account |
|
Bundle ID associated with the item |
|
Subscription ID associated with the item |
|
Product name (translated) |
|
Human-readable product name (translated) |
|
Plan name (translated) |
|
Human-readable plan name (translated) |
|
Phase name (translated) |
|
Human-readable phase name (translated) |
|
Usage unit name (translated) |
|
Human-readable usage unit name (translated) |
|
Item ID |
|
Item creation timestamp |
|
Item last-updated timestamp |
|
Item quantity |
|
Free-form item details, if set |
|
Catalog effective date used to price this item |
Account Fields
These fields are accessed under the account. namespace, e.g. {{account.name}}, and come directly from the account object (rather than a formatter wrapper) — so, unlike the invoice and invoice item fields above, there are no formatted*/pretty* convenience variants.
| Field | Description |
|---|---|
externalKey |
The account’s external key |
name |
The account name (first and last name combined, where applicable) |
firstNameLength |
The length of the first name that can be extracted from name |
The primary account email |
|
billCycleDayLocal |
The bill cycle day for the account, interpreted in the account’s timezone |
currency |
The account’s currency |
paymentMethodId |
The ID of the account’s current default payment method |
referenceTime |
The reference time for the account |
timeZone |
The account’s timezone |
locale |
The account’s locale |
address1 |
Address line 1 |
address2 |
Address line 2 |
companyName |
The company name for the account |
city |
City |
stateOrProvince |
State or province |
postalCode |
Postal code |
country |
Country |
phone |
Phone number |
migrated |
Whether the account was migrated into the system |
parentAccountId |
The ID of the parent account, if any |
paymentDelegatedToParent |
Whether the child account’s payments are delegated to the parent account |
Testing Your Changes
No matter how you configure your template, translations, or branding, you’ll want to confirm the changes render as expected before using them in production.
If you configured things via Aviate, use the live invoice preview panel in the template editor — it reflects your changes as you make them, without needing to generate a separate invoice.
If you configured things via the API or Kaui, generate an HTML invoice to verify the result:
-
Execute the following
cURLcommand:curl -v \ -u admin:password \ -H 'X-Killbill-ApiKey: bob' \ -H 'X-Killbill-ApiSecret: lazar' \ -H "Content-Type: application/json" \ -H 'X-Killbill-CreatedBy: admin' \ "http://127.0.0.1:8080/1.0/kb/invoices/1785b3d5-24b3-4d17-94ce-310aeb74bc63/html" -
Alternatively, you can generate the HTML invoice via the
Invoicestab in Kaui:
This displays the invoice in a new browser tab, which you can then inspect to confirm your changes.
Customizing Invoice Data
Sometimes, you may need to customize the data in the invoice (add additional fields, custom logic for existing fields, etc.). For this, you will need to create a custom invoice formatter plugin. We have a sample plugin here.