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

  1. Navigate to Configuration → Settings in Aviate.

  2. 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:

  1. 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.

  2. Customize style details such as text color, table heading background/text color, and table border color.

  3. Select the desired language from the translations drop-down and modify the translations as required.

  4. Use the live invoice preview panel to confirm your changes render as expected.

  5. Click Save Template to save the changes.

  6. 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:

  1. Upload the invoice template

  2. Upload the translation for all the supported locales

  3. Upload the catalog translation for all the locales (if required)

  4. Optionally, upload branding information via the API

Step 1 - Upload Invoice Template

  1. Create a file $SOME_PATH_PREFIX/HtmlInvoiceTemplate.mustache as 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>
  2. Upload the file either via API call or Kaui.

    1. To upload via API, execute the following cURL command:

          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
    2. To upload via Kaui, go to InvoiceTemplate tab on the admin tenant page:

      custom invoice template

Step 2- Upload Invoice Translations

  1. 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
  2. Upload the file either via API call or Kaui.

    1. To upload via API, execute the following cURL command:

      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
    2. To upload via Kaui go to the InvoiceTranslation tab on the admin tenant page:

      custom invoice translation

      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.

  1. Create a catalog translation for the desired locale (for example fr_FR) as follows:

    gold-monthly = plan Or mensuel
  2. Upload the file via API call or Kaui.

    1. To upload via API, execute the following cURL command:

      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
    2. Alternatively, you can upload this file via Kaui by going to your admin tenant page (CatalogTranslation) tab:

      catalog translation

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

COMPANY_INFO

Organization-level (global default) company information

LOGO_INFO

Organization-level (global default) logo

BRAND_INFO

Organization-level (global default) style/brand information. Only accepts the tableHeadingBgColor field.

INVOICE_TEMPLATE_COMPANY_INFO

Invoice-template-level company information (overrides COMPANY_INFO)

INVOICE_TEMPLATE_LOGO_INFO

Invoice-template-level logo (overrides LOGO_INFO)

INVOICE_TEMPLATE_BRAND_INFO

Invoice-template-level style/brand information (overrides BRAND_INFO). Accepts all 4 fields: textColor, tableBorderColor, tableHeadingBgColor, tableHeadingTextColor.

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.

  1. Company information is uploaded to the COMPANY_INFO key (or INVOICE_TEMPLATE_COMPANY_INFO for a template-level override) as follows:

    1. Define the JSON payload:

      {"companyName":"CloudSprout","companyAddress":"East Street","companyCityProvincePostalCode":"New York","companyCountry":"United States","companyUrl":"cloudsprout.com"}
    2. Upload the payload via the following cURL command:

      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
  2. Style/brand information is uploaded to the BRAND_INFO key (or INVOICE_TEMPLATE_BRAND_INFO for a template-level override) as follows:

    1. Define the JSON payload:

      {"textColor":"red","tableBorderColor":"blue","tableHeadingBgColor":"yellow","tableHeadingTextColor":"green"}
    2. Upload the payload via the following cURL command:

      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_INFO
      Note
      Note: BRAND_INFO only accepts the tableHeadingBgColor field — the other style fields (textColor, tableBorderColor, tableHeadingTextColor) are only available at the invoice-template level, via INVOICE_TEMPLATE_BRAND_INFO.
  3. The logo is uploaded to the LOGO_INFO key (or INVOICE_TEMPLATE_LOGO_INFO for a template-level override) as a base64-encoded data URI:

    1. Define the JSON payload:

      {"logo":"data:image/png;base64,/9j/4AAQSk..."}
    2. Upload the payload via the following cURL command:

      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

invoiceNumber

The invoice number

invoiceItems

The list of invoice items on this invoice (see Invoice Item Fields below). Iterate with {{#invoice.invoiceItems}}…​{{/invoice.invoiceItems}}.

trackingIds

Tracking IDs associated with the invoice

chargedAmount

Total charged amount on the invoice

originalChargedAmount

Charged amount before any adjustments

balance

Remaining balance on the invoice

formattedChargedAmount

Charged amount, formatted per the account locale and invoice currency

formattedPaidAmount

Paid amount, formatted per the account locale and invoice currency

formattedBalance

Balance, formatted per the account locale and invoice currency

processedCurrency

Currency the payment was processed in, if different from the invoice currency; null otherwise

processedPaymentRate

Conversion rate used, if the payment was processed in a different currency; null otherwise

migrationInvoice

Whether this is a migration invoice

invoiceDate

Invoice date

targetDate

Target date used to generate the invoice

currency

Invoice currency

paidAmount

Amount paid against this invoice

formattedInvoiceDate

Invoice date, formatted per the account locale

id

Invoice ID

createdDate

Invoice creation timestamp

updatedDate

Invoice last-updated timestamp

status

Invoice status

parentInvoice

Whether this is a parent invoice

parentAccountId

Parent account ID, for child invoices

parentInvoiceId

Parent invoice ID, for child invoices

groupId

Group ID associated with the invoice, if any

creditedAmount

Amount credited against this invoice

refundedAmount

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

amount

Item amount

currency

Item currency

formattedAmount

Item amount, formatted per the account locale and invoice currency

invoiceItemType

Item type (e.g. recurring, fixed, usage, tax, credit)

description

Item description (translated, if a matching resource bundle entry exists)

startDate

Item service period start date

endDate

Item service period end date

formattedStartDate

Start date, formatted per the account locale

formattedEndDate

End date, formatted per the account locale

invoiceId

ID of the invoice this item belongs to

accountId

Account ID

childAccountId

Child account ID, for items billed to a child account

bundleId

Bundle ID associated with the item

subscriptionId

Subscription ID associated with the item

productName

Product name (translated)

prettyProductName

Human-readable product name (translated)

planName

Plan name (translated)

prettyPlanName

Human-readable plan name (translated)

phaseName

Phase name (translated)

prettyPhaseName

Human-readable phase name (translated)

usageName

Usage unit name (translated)

prettyUsageName

Human-readable usage unit name (translated)

id

Item ID

createdDate

Item creation timestamp

updatedDate

Item last-updated timestamp

quantity

Item quantity

itemDetails

Free-form item details, if set

catalogEffectiveDate

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

email

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:

  1. Execute the following cURL command:

    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"
  2. Alternatively, you can generate the HTML invoice via the Invoices tab in Kaui:

    view html invoice

    This displays the invoice in a new browser tab, which you can then inspect to confirm your changes.

    html invoice

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.