Invoices

Kimai allows creating invoices from timesheet data in several formats.

Invoice number format

You can mix arbitrary characters and the allowed replacer from the list below. All examples represent the date 2019-07-09 (2019, July 9th):

  • {date} - shortcut for ymd. Example: 190709 (default format before Kimai 1.9)
  • {cname} - the customer name
  • {cnumber} - the customer number
  • {c} - counter for invoices of all times, starting at 1
  • {cy} - counter for invoices this year, starting at 1
  • {cm} - counter for invoices this month, starting at 1
  • {cd} - counter for invoices this day, starting at 1
  • {Y} - full numeric representation of a year (4 digits). Example: 2019
  • {y} - two digit representation of a year. Example: 19
  • {M} - numeric representation of a month, with leading zeros. Example: 07
  • {m} - numeric representation of a month, without leading zeros. Example: 7
  • {D} - day of the month, 2 digits with leading zeros. Example: 09
  • {d} - day of the month without leading zeros. Example: 9
  • {cc} - per customer invoice counter for all times, starting at 1
  • {ccy} - per customer invoice counter for this year, starting at 1
  • {ccm} - per customer invoice counter for this month, starting at 1
  • {ccd} - per customer invoice counter for this day, starting at 1
  • {cu} - per user (the one creating the invoice) invoice counter for all times, starting at 1
  • {cuy} - per user (the one creating the invoice) invoice counter for this year, starting at 1
  • {cum} - per user (the one creating the invoice) invoice counter for this month, starting at 1
  • {cud} - per user (the one creating the invoice) invoice counter for this day, starting at 1
  • {ustaff} - the user (the one creating the invoice) staff number, empty if not configured
  • {uid} - the internal user (the one creating the invoice) ID

Each replacer (x) can be combined with a length formatter, which will prepend (X) leading zeros, eg. {x,X}. Example: to get a three digit long string with year counter use {cy,3}, which results in 001 for the first invoice of the year.

To change the format, look out for the icon in the invoice screen, or change it from the System configurations.

Incrementing the invoice counter

It is possible to increment the counter by adding a number to the result. For evaluated counters, Kimai takes the amount of found invoices and adds 1, but you can replace +1 with an addition like +3. This works for the following replacer: {c} and {cy} and {cm} and {cd} and {cc} and {ccy} and {ccm} and {ccd}.

Simply use the plus (+) after the replacer, eg. {cy+72}. This also works in combination with the length formatter, eg.: {cy+72,3}

Decrementing the invoice counter

You can decrement the counter subtracting a number from the result. For evaluated counters, Kimai takes the amount of found invoices and adds +1, but you can replace +1 with a subtraction like -12. This works for the following replacer: {c} and {cy} and {cm} and {cd} and {cc} and {ccy} and {ccm} and {ccd}.

Simply use the minus (-) after the replacer, eg. {cy-72}. This also works in combination with the length formatter, eg.: {cy-72,3}

Examples

Assume you already wrote 72 invoices this year (before you started to use Kimai), and your counter is an incrementing number per year, which is prefixed with the four digit year:

  • {Y}/{cy+73,3} would result in 2020/073 for your first invoice

Assume that you want to change your invoice numbering and reset it to zero, you already wrote 72 invoices this year, and your counter is an incrementing number per year that should be prefixed with the four digit year:

  • {Y}/{cy-72,3} would result in 2020/001 for your first invoice

Invoice state

Invoices can have multiple states:

  • New - the invoice was just created
  • Waiting for payment - the invoice was sent to the customer
  • Invoice paid - the invoice was paid (you have to choose a payment date)
  • Canceled - the invoice was accidentally created

Deleting invoices

Invoices should not be deleted, because this can cause all kinds of weird problems in Kimai (depending on your invoice number format).

You should instead “cancel” invalid invoices. Many users fear legal issues if they don’t have consecutive invoice numbers, please clarify that with your accountant - this is often not a legal requirement. Instead it is a completely regular workflow, that invoices get canceled (the used invoice number will not be released).

If you insist on deleting invoices, do NOT delete them if you already created invoices afterwards. This will cause troubles with your invoice counter, as Kimai is calculating invoice numbers from the amount of entries in the database. By deleting invoices you lower the amount of invoices in the database, and the next invoice number might be one that you already used.

Invoice deletion is disabled by default, because of all known problems with deletion. You can activate it enabling the permissions called delete_invoice.

Export state

Invoices and exports share the export state, which is used to mark timesheet records as processed. These records cannot be edited any longer by regular users and are excluded by default from further invoices and exports.

You need to tick the “mark as export” checkbox before saving the invoice, to automatically set the export state on all filtered timesheet records.

The “mark as export” checkbox is only available for users with the edit_export_other_timesheet permission.

For further information read the timesheet documentation.

Billable items only

Only billable items will be included in invoices.

By default, every timesheet records is billable, but users can set the billable flag on individual timesheets.

Expense items have a configurable billable flag per item and only the ones marked as billable (refundable) will be included.


Invoice document

The invoice system currently supports the following formats:

  • PDF
    • through the use of Twig templates
    • filename must end with .pdf.twig
    • Pro: no need for additional software
    • Contra: you have to understand HTML, Twig and the MPDF library
    • the recommended invoice document format
  • HTML
    • through the use of Twig templates
    • filename must end with .html.twig
    • Pro: no need for additional software, print or convert to PDF from your browser (if supported)
    • Contra: you have to understand HTML and Twig
  • DOCX
    • OOXML - Open Office XML Text
    • Microsoft Word 2007-2013 XML
    • filename must end with .docx
    • Pro: simple customization and possibility to edit the invoice later on
    • Contra: Non-free software required
  • ODS
    • Open Document Spreadsheet or OASIS, is the OpenOffice.org XML file format for spreadsheets supported by OpenOffice, LibreOffice, StarCalc, Microsoft and others
    • file extension: filename must end with .ods
    • Pro: open format, good for exporting and creating enhanced reports with an office software package
    • Contra: not ideal format for invoices (harder to customize)
  • XLSX
    • Microsoft Excel™ 2007 shipped with a new file format, namely Microsoft Office Open XML SpreadsheetML, and Excel 2010 extended this still further with new features.
    • file extension: filename must end with .xlsx
    • Pro: good for creating enhanced reports with an office software package
    • Contra: Non-free software required, not ideal format for invoices
  • CSV
    • Comma-separated file with UTF-8 encoding and double-quotes around each field
    • filename must end with .csv
    • Pro: good for exporting and creating enhanced reports with an office software package
    • Contra: only row based information possible (meta information can’t be used properly), UTF-8 is not properly supported when using Excel (see this issue)

Create your own invoice document

Invoice documents are searched in two locations:

  • var/invoices/ - does not exist by default, please create it when you add a new document
  • templates/invoice/renderer/ - don’t change files in here, will be overwritten with the next update

Be aware of the following rules:

  • Documents are addressed by their filename without extension (e.g. kimai.html.twig results in kimai)
  • You can use every document name only once: so having kimai.html.twig and kimai.docx will lead to unpredictable results (the first file to be found takes precedence)
  • Kimai looks for templates in var/invoices/ first, so you can overwrite default templates
  • You should store your templates in var/invoices/, as this directory is not shipped with Kimai and not touched during updates
  • You can configure different search directories through the config key kimai.invoice.documents if you want to add additional template source directories

After you changed an invoice template, you have to clear the cache to see the results.

You can have a look at https://github.com/Keleo/kimai2-invoice-templates to get some code inspirations.

Twig templates

Generally speaking, you have two main variables in your template which you should use:

  • entries which is an array of arrays, with the first level representing the invoice items and the second level being Timesheet entry variables (see below)
  • invoice which is an array of variables (see Template variables below), just with a different syntax for access, e.g.: {{ invoice['invoice.due_date'] }} instead of ${invoice.due_date}
  • don’t rely on the model variable (which is an instance of App\Model\InvoiceModel) as it’s accessor methods can change without warning

Please see the default templates at GitHub to find out which variables can be used.

Use this debug template to find out which variables are available:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{{ invoice['invoice.number'] }}-{{ invoice['customer.company']|default(invoice['customer.name'])|u.snake }}</title>
    <style type="text/css">
        {{ encore_entry_css_source('invoice-pdf')|raw }}
    </style>
</head>
<body>
<div class="wrapper">
    <h3>The following list of variables is available for this invoice:</h3>
    <p>Please note, that the available variables will change depending on your search. E.g. the {{ '{{ project }}' }} variables are only available if you selected a project in your search filter.</p>
    <table class="items">
        <tr>
            <th>Variable</th>
            <th>Value</th>
        </tr>
        {% for name, value in invoice %}
            <tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
                <td>{{ "{{ invoice['" ~ name ~ "'] }}" }}</td>
                <td>{{ invoice[name] }}</td>
            </tr>
        {% endfor %}
    </table>
    <h3>The following list is available for all timesheet entries, which can be rendered like this:</h3>
    <pre>
    {{ '{%' }} for entry in entries {{ '%}' }}
    &lt;ul&gt;
        {{ '{%' }} for name, value in entry {{ '%}' }}
            &lt;li&gt;{{ '{{ name }}' }}: {{ '{{ value }}' }}&lt;/li&gt;
        {{ '{%' }} endfor {{ '%}' }}
    &lt;/ul&gt;
    {{ '{%' }} endfor {{ '%}' }}
    </pre>
    <p>The table below contains only the first timesheet record:</p>
    <table class="items">
        <tr>
            <th>Variable</th>
            <th>Value</th>
        </tr>
        {% for entry in entries|slice(1) %}
            {% for name, value in entry %}
                <tr class="{{ cycle(['odd', 'even'], loop.index0) }}">
                    <td>{{ "{{ entry['" ~ name ~ "'] }}" }}</td>
                    <td>{{ value }}</td>
                </tr>
            {% endfor %}
        {% endfor %}
    </table>
</div>
</body>
</html>

Want to include an image in your template? Use the asset tag for referencing relative URLs (this example points to the directory public/images/my-logo.png):

<img src="{{ asset('images/my-logo.png') }}">

But the safest way is to host your images on your own domain:

<img src="https://www.example.com/images/my-logo.png">

Want to include a file in your template? Use the twig include feature with the @invoice namespace . The following example references the file bar.html.twig in var/invoices/foo/:

{% include '@invoice/foo/bar.html.twig' %}

Custom fields

Iterating above all entries (line items) in the invoice with {% for id, entry in model.calculator.entries %} allows access to your custom fields.

Want to use a timesheet custom field in your template?

{% set meta = entry.getMetaFieldValue('foo') %}
{% if meta is not null %}
    Foo: {{ meta }}
{% endif %}

Please be aware:

  • you can access timesheet custom fields only if you use the “sum calculation” standard, which creates one invoice line item per timesheet.
  • if you group timesheets e.g. by project, you loose access to their custom fields.
  • entries could be of type “expense” or other types (depending on your used plugins), you can test that with {% if entry.type == 'timesheet' '%}

Want to use a customer custom field in your template?

{% set meta = entry.customer.getMetaFieldValue('foo') %}
{% if meta is not null %}
    Foo: {{ meta }}
{% endif %}

Want to use a project custom field in your template?

{% set meta = entry.project.getMetaFieldValue('foo') %}
{% if meta is not null %}
    Foo: {{ meta }}
{% endif %}

Want to use a user preference in your template?

{% set meta = entry.user.getMetaFieldValue('foo') %}
{% if meta is not null %}
    Foo: {{ meta }}
{% endif %}

PDF templates

PDF invoice templates are basically the same as Twig templates. But the resulting HTML is processed by the MPdf library, which will convert the HTML & CSS to PDF. There are some additional contents, that will define e.g. page header and footer.

Please read the MPdf documentation and check the default PDF templates.

You can customize the following values from within your PDF templates:


{%- set customer = query.customers|length == 1 ? query.customers.0 : null -%}
{%- set filename = 'ACME_' ~ (customer is not null ? customer.name|replace({' ': '-'}) ~ '_' : '') ~ query.begin|date_format('Y-m') -%}
{%- set option = pdfContext.setOption('filename', filename) -%}
{%- set option = pdfContext.setOption('format', 'A4-L') -%}

Custom fonts

If you want to use a custom font in your PDF, you can configure it like that:

{%- set fontData = pdfContext.setOption('fonts', {
'demo': {
    'R': 'Demo-Regular.ttf',
    'B': 'Demo-Bold.ttf',
    'I': 'Demo-Italic.ttf',
    'BI': 'Demo-BoldItalic.ttf'
}
}) -%}
<style>
body { 
    font-family: 'demo', sans-serif;
}
</style> 

The font files must be stored in the directory var/data/fonts/ within the Kimai directory, in this case it would be:

  • var/data/fonts/Demo-Regular.ttf
  • var/data/fonts/Demo-Bold.ttf
  • var/data/fonts/Demo-Italic.ttf
  • var/data/fonts/Demo-BoldItalic.ttf

Page size

Want to display the PDF in a different size, e.g. because your customer expects US-Letter and not the standard size DIN-A4? Xou can add a CSS rule to your twig template:

<style>
@page { 
    sheet-size: LETTER-L;
}
</style> 

Available sizes are:

  • A4, A3, Letter, Legal, Executive, Folio, Demy, Royal, A, B, Ledger, Tabloid
  • A4-L, A3-L, Letter-L, Legal-L, Executive-L, Folio-L … for landscape

More information available in the MDPF documentation and the full list of available sizes can be found here (check format).

Docx templates

Docx templates are processed by PHPWord and its TemplateProcessor.

Important: You have to add one of the variables - either ${entry.description} or ${entry.row} - in one table row, otherwise the records will not be rendered (but only the global values)!

The row containing this variable will be cloned for every included (timesheet) record.

If you do not use ${entry.description} then a fallback for ${entry.row} is used and will be removed in the rendering process, it will not show up in the generated invoice.

See below in Template variables to find out which variables you can use in your template.

Find out more about PHPWord templates here.

Spreadsheets (ODS, XLSX and CSV)

Spreadsheet templates are powered by PhpSpreadsheet.

Important: within the first 100 rows you MUST-HAVE the template row for timesheet entries, which means there must be a value starting with ${entry. in one of the first 10 columns, otherwise no timesheet records will be rendered!

Check the default templates if that doesn’t make sense to you ;-)

This row will then be cloned for every timesheet entry.

See below in Template variables to find out which variables you can use.

Template variables

Be aware, that the following list of variables is working for the “document” based formats (ODS, XLSX, CSV, DOCX).

Twig rendering is different, you have to access the variables with `` instead of ${xxx.yyy}.

Global variables

Key Description
${invoice.due_date} The due date for the invoice payment
${invoice.date} The creation date of this invoice
${invoice.number} The generated invoice number
${invoice.currency} The invoice currency
${invoice.currency_symbol} The invoice currency as symbol (if available)
${invoice.total_time} The total working time (entries with a fixed rate are always calculated with 1)
${invoice.duration_decimal} The total working time as decimal value
${invoice.language} The invoices language as two character code
${invoice.total} The invoices total (including tax) with currency
${invoice.total_nc} The invoices total (including tax) without currency
${invoice.total_plain} The invoices total (including tax) as unformatted value
${invoice.subtotal} The invoices subtotal (excluding tax) with currency
${invoice.subtotal_nc} The invoices subtotal (excluding tax) without currency
${invoice.subtotal_plain} The invoices subtotal (excluding tax) as unformatted value
${invoice.currency} The invoices currency as string (like EUR or USD)
${invoice.vat} The VAT in percent for this invoice
${invoice.tax} The tax of the invoice amount with currency
${invoice.tax_nc} The tax of the invoice amount without currency
${invoice.tax_plain} The tax of the invoice amount as unformatted value
${template.name} The invoice name, as configured in your template
${template.company} The company name, as configured in your template
${template.address} The invoicing address, as configured in your template
${template.title} The invoice title, as configured in your template
${template.payment_terms} Your payment terms, might be multiple lines
${template.due_days} The amount of days for the payment, starting with the day of creating the invoice
${template.vat_id} The Vat ID for this invoice
${template.contact} Extended contact information, might be multiple lines
${template.payment_details} Extended payment details like bank accounts, might be multiple lines
${query.begin} The query begin as formatted short date
${query.end} The query end as formatted short date
${query.month} The month for this query (begin date) DEPRECATED
${query.month_number} The numerical value for the month (with leading zero) DEPRECATED
${query.day} The day for the queries begin as numerical value with leading zero DEPRECATED
${query.year} The year for this query (begin date) DEPRECATED
${query.begin_month} The month for the queries begin date
${query.begin_month_number} The numerical value for the month of the queries begin date with leading zero
${query.begin_day} The day for the queries begin as numerical value with leading zero
${query.begin_year} The year for the queries begin date
${query.end_month} The month for the queries end date
${query.end_month_number} The numerical value for the month of the queries end date with leading zero
${query.end_day} The day for the queries end as numerical value with leading zero
${query.end_year} The year for the queries end date
${user.name} The current users name
${user.email} The current users email
${user.alias} The current users alias
${user.title} The current users title
${user.meta.X} The current users preference named X

Timesheet entry variables

For each timesheet record you can use these variables:

Key Description Example
${entry.row} An empty string, used as template row for docx  
${entry.description} The entries description foo bar
${entry.amount} The format duration/amount for this entry 02:47 h
${entry.rate} The rate for one unit of the entry (normally one hour) with currency 1.100,01 EUR
${entry.rate_nc} The rate for one unit of the entry without currency 1100,01
${entry.rate_plain} The rate for one unit of the entry as unformatted value 1100.01
${entry.total} The total rate for this entry with currency 1.278,33 EUR
${entry.total_nc} The total rate for this entry without currency 1.278,33
${entry.total_plain} The total rate as unformatted value 1278.33
${entry.currency} The currency for this record as string (like EUR or USD) EUR
${entry.duration} The duration in seconds 10020
${entry.duration_decimal} The duration in decimal format (with localized separator) 2.78
${entry.duration_minutes} The duration in minutes with no decimals 167
${entry.begin} The begin date (format depends on the users language) 27.10.2018
${entry.begin_time} The formatted time for the begin of this entry 14:57
${entry.begin_timestamp} The timestamp for the begin of this entry 1542016273
${entry.end} The begin date (format depends on the users language) 27.10.2018
${entry.end_time} The formatted time for the end of this entry 17:44
${entry.end_timestamp} The timestamp for the end of this entry 1542016273
${entry.date} The start date when this record was created 27.10.2018
${entry.week} The start week number when this record was created 39
${entry.weekyear} The corresponding year to the week number 2018
${entry.user_id} The user ID 1
${entry.user_name} The username susan_super
${entry.user_alias} The user alias Susan Miller
${entry.activity} Activity name Post production
${entry.activity_id} Activity ID 124
${entry.project} Project name Nemesis
${entry.project_id} Project ID 10
${entry.customer} Customer name Acme Studios
${entry.customer_id} Customer ID 3
${entry.meta.X} The meta field named X (if visible)  
${entry.type} The type of this entry (plugins can add custom types) timesheet
${entry.category} The category of this entry (plugins can add custom types) work

Customer variables

Variables for the customer who is receiving the invoice:

Key Description (highlighted words are the names in the UI)
${customer.id} The internal customer ID (do not display that on your invoices)
${customer.name} The customer name
${customer.comment} The description of this customer
${customer.address} The customer address
${customer.contact} Usually the name of the customer contact person
${customer.company} The company name
${customer.vat} The customer Vat ID
${customer.number} The customer account number
${customer.country} The country of the customer location
${customer.homepage} A URL to the customer homepage
${customer.phone} The customers phone number
${customer.mobile} The customers mobile number
${customer.email} The customers email address
${customer.fax} The customers fax number
${customer.meta.x} The customer meta field named X. The internal name X needs to be used in lowercase letters, eg. FooBar will be available as ${customer.meta.foobar}. Only available if the field is visible.
${customer.budget_open} The open monetary budget for this customer at the end date of your filter query (formatted with currency) (since 1.16.7)
${customer.budget_open_plain} The open monetary budget for this customer at the end date of your filter query (plain float value) (since 1.16.7)
${customer.time_budget_open} The open time budget for this customer at the end date of your filter query (formatted) (since 1.16.7)
${customer.time_budget_open_plain} The open time budget for this customer at the end date of your filter query (integer value = seconds) (since 1.16.7)

Project variables

ONLY if a project was selected in the invoice filter (search form) the following variables exist as well:

Key Description (highlighted words are the names in the UI)
${project.id} The internal project ID (do not display that on your invoices)
${project.name} The project name
${project.comment} The description of this project
${project.order_number} The project order number
${project.start_date} The Project start date-time
${project.end_date} The Project end date-time
${project.order_date} Projects order date-time
${project.budget_money} Projects budget including currency
${project.budget_money_nc} The projects budget without currency
${project.budget_money_plain} The projects budget as unformatted value
${project.budget_time} The projects time-budget as seconds
${project.budget_time_decimal} The projects time-budget in decimal format (with localized separator)
${project.budget_time_minutes} The projects time-budget in minutes with no decimals
${project.meta.x} The project meta field named X. The internal name X needs to be used in lowercase letters, eg. FooBar will be available as ${project.meta.foobar}. Only available if the field is visible.
${project.budget_open} The open monetary budget for this project at the end date of your filter query (formatted with currency) (since 1.16.7)
${project.budget_open_plain} The open monetary budget for this project at the end date of your filter query (plain float value) (since 1.16.7)
${project.time_budget_open} The open time budget for this project at the end date of your filter query (formatted) (since 1.16.7)
${project.time_budget_open_plain} The open time budget for this project at the end date of your filter query (integer value = seconds) (since 1.16.7)

If you selected more than one project in the search, you will have further variables (same list then above) called ${project.1.name}, ${project.2.name} and so on. The order is not guaranteed, so it is not recommended relying on those variables.

Activity variables

ONLY if an activity was selected in the invoice filter (search form) the following variables exist:

Key Description (highlighted words are the names in the UI)
${activity.id} The internal activity ID (do not display that on your invoices)
${activity.name} The activity name
${activity.comment} The description of this activity
${activity.meta.x} The activity meta field named X. The internal name X needs to be used in lowercase letters, eg. FooBar will be available as ${activity.meta.foobar}. Only available if the field is visible.
${activity.budget_open} The open monetary budget for this activity at the end date of your filter query (formatted with currency) (since 1.16.7)
${activity.budget_open_plain} The open monetary budget for this activity at the end date of your filter query (plain float value) (since 1.16.7)
${activity.time_budget_open} The open time budget for this activity at the end date of your filter query (formatted) (since 1.16.7)
${activity.time_budget_open_plain} The open time budget for this activity at the end date of your filter query (integer value = seconds) (since 1.16.7)

If you selected more than one activity in the search, you will have further variables (same list then above) called ${activity.1.name}, ${activity.2.name} and so on. The order is not guaranteed, so it is not recommended relying on those variables.

Uploading invoice documents

You can upload invoice documents via the UI at /en/invoice/document_upload.

Due to security restrictions currently only the upload of the following formats is allowed: DOCX, ODS, XLSX.

There is a known bug in LibreOffice which exports DOCX files with a wrong mime-type. These files will not be accepted by Kimai with the error This file type is not allowed (read this issue for more information). The workaround is to change the document with another word processor: Apple pages, Google Drive and Microsoft 365 Online Office will export the DOCX files with the correct mimetype.

Filter and search in the invoice archive

The search supports filtering by the fields:

  • creation date
  • customer
  • state

Besides these filters, you can query for a free search term, which will be searched in the fields:

  • comment
  • customer name
  • customer company

Additionally, you can filter for custom fields by using a search phrase like location:homeoffice. This would find all entries with the custom field location matching the term homeoffice.

The search terms will be found within the full value, so searching for office would find:

  • I love working in my office
  • Office
  • This office is beautiful
  • Our offices are very noisy

Attention: checkboxes have the values 0 (not checked) and 1 (checked).

You can mix the search term and use multiple meta-field queries:

  • location:homeoffice hello - find all entries matching the search term hello with the custom field location matching the term homeoffice
  • location:homeoffice contract:fulltime - find all entries with the custom field combination: location matching homeoffice and contract matching fulltime
  • expired:0 finds all items whose expired checkbox is off

There are also special operators, which can be used in conjunction with custom fields (since Kimai 1.19.1):

  • The   empty string (e.g. location:) will find all entries whose value in the location field is either empty or not existing
  • The ~ search term (e.g. location:~) will find all entries that are missing the custom field (created before the field was created)
  • The * search term (e.g. location:*) will find all entries that have any value in the location field (basically the opposite of ~)

Create invoices with cronjobs

See command line tools to find out how to create invoices from the command line.