Universe

Create project in app

Laskutys is a template for making invoices satisfying (mostly) the VAT invoice requirements by the Finnish Tax Administration. Among other things, this template features:

  • Finnish/English/Swedish translations
  • VAT calculations by VAT base
  • Optional bank barcode (fi. Pankkiviivakoodi)
  • Optional EPC QR Code (European Payment Council’s Quick Response Code)
  • Invoice number generation from date
  • RF creditor reference (ISO 11649) generation from invoice number
  • Customizable colors

[!NOTE] VAT-calculation might have rounding errors due to division. The errors are at most a few cents.

Usage

See API documentation for all arguments.

#import "@preview/laskutys:1.0.0": invoice

#let data = yaml("data.yaml")

#invoice(
  /// Optional, defaults to today
  date: datetime(year: 2025, month: 09, day: 30),
  /// Optional logo, displayed as is
  logo: image("logo.svg", height: 4em),
  iban: "FI2112345600000785",
  bic: "NDEAFIHH",
  seller: (
    name: "Yritys Oy",
    business-id: "1234567-8",
    address: [Talousosasto\ PL 12\ 00100 Helsinki],
  ),
  /// Recipient can also have business-id
  recipient: (
    name: "Kuluttaja Nimi",
    address: [Kotikatu 1\ 00100 Helsinki],
  ),
  /// fi: Finnish
  /// en: English (default)
  /// sv: Swedish
  lang: "fi",
  footnotes: [Company Oy, Phone: +358 123 4567, Email: sales.person\@company.com],
  data,
)

YAML data of items:

- description: Ruoka
  quantity: 10
  # Unit price including VAT
  unit-price: "2"
  vat-rate: "0.14"

- description: AA paristo
  quantity: 2
  unit-price: "1.99"
  vat-rate: "0.255"

- description: Sanomalehti
  quantity: 3
  unit-price: "9.99"
  vat-rate: "0.10"

- description: "!Phone"
  quantity: 1
  unit-price: "1000"

  # \_ is non breaking space
- description: "Sijoituskulta, AVL\_43\_a\_§"
  quantity: 3
  unit-price: "10"
  vat-rate: "0"

Output of the above code: Example generated invoice

[!IMPORTANT] Pass unit-price and vat-rate as string, so that they can be converted to decimal without errors. This avoids rounding errors due to imprecision of floating-point numbers.

The data can also be defined directly in Typst as an array:

#let data = (
  (
    description: "Apple",
    quantity: 10,
    unit-price: "2",
    vat-rate: "0.14",
  ),
  (
    description: "Battery AA",
    quantity: 2,
    unit-price: "2",
    vat-rate: "0.255",
  ),
  (
    description: "Item with default VAT",
    quantity: 3,
    unit-price: "10",
  ),
),

You can also use other loader functions if they can produce an array in the same format.

Documentation

Examples

Customize colors

#import "@preview/laskutys:1.0.0": DEFAULT-COLORS, invoice

#let data = yaml("data.yaml")

#invoice(
  ...
  colors: (
    ..DEFAULT-COLORS
    active: blue,
    bg-passive: teal.lighten(85%),
    passive: teal,
  ),
  data,
)

Changing colors

[!TIP] The DEFAULT-COLORS is needed if you don’t want to override all colors. You can also override any other preset similarly.

See Typst documentation for more colors and API documentation for configurable colors. There are also some presets available, see Color presets.

Change language

Pass ISO 639 language code as lang. Supported languages are

  • en: English (default)
  • fi: Finnish
  • sv: Swedish
#import "@preview/laskutys:1.0.0": invoice

#let data = yaml("data.yaml")

#invoice(
  ...
  lang: "fi",
  data,
)

Read configuration from file

Using spread syntax, configurations can also be read from a file. For instance, from a YAML file.

#import "@preview/laskutys:1.0.0": *

#let data = yaml("data.yaml")
#let config = yaml("config.yaml")

#invoice(
  ..config,
  data,
)

YAML config file

iban: FI2112345600000785
bic: OKOYFIHH

seller:
  name: Company Oy
  business-id: 1234567-8
  address: "Street 123\n01234 City"

recipient:
  name: Recipient Name
  address: "Street 123\n01234 City"

footnotes: "Company Oy, Phone: +358 123 4567, Email: sales.person@company.com"

[!NOTE] date and logo cannot be read from non-Typst file since Typst cannot convert them. Also, use quotes " for string containing newline \n or something that is converted incorrectly.

Automated invoice generation

Typst CLI can pass inputs to Typst file using

typst compile file.typ --input key1=val1`

The key value pairs can be accessed in Typst file using sys.inputs as a dictionary.

#import "@preview/laskutys:1.0.0": *

#let data = yaml("data.yaml")
#let config = sys.inputs

#invoice(
  ..config,
  data,
)

Since all values are strings, some preprocessing is required. For example, the values can be JSON strings which can be parsed using json.

Hide QR code or bank barcode

Set qrcode or barcode to false:

...

#invoice(
  ...
  qrcode: false,
  barcode: false,
)
  • Bank barcode supports only Finnish IBAN, so disable it if non-Finnish IBAN is used.
  • EPC QR code supports non-Finnish IBAN [^epc_qr]

License

The project is licensed under the MIT-0 license. Licenses of libraries used in this project are listed in /licenses.

[^epc_qr]: European Payments Council, Quick Response Code: Guidelines to Enable Data Capture for the Initiation of a SEPA Credit Transfer, https://www.europeanpaymentscouncil.eu/document-library/guidance-documents/quick-response-code-guidelines-enable-data-capture-initiation