Skip to main content
All articles

Invoices in Pure Swift

Ben Van AkenCo-Founder & CTO4 min read
Invoices in Pure Swift

A Swift PDF invoice is a rendering problem that is largely solved and a judgement problem that is not. The totals, the VAT line, the document you are willing to send a customer: a person checks those. The PDF itself should be boring. Swiftly-PDFKit is the boring half, published as a library rather than kept inside one app.

It lives at github.com/Swiftly-Developed/Swiftly-PDFKit, under the MIT licence the repository states. The figures we recorded on 10 October 2026 showed tag v0.3.0, dated 18 August 2026, and two stars. The README on the default branch, read on 16 October 2026, still tells you to depend on version 0.2.0. Pin the tag you mean. The snippets below are that README's documented API, not a promise that every line matches the 0.3.0 tag byte for byte.

A document model, then a layout

The invoice engine takes one InvoiceDocument: a header, a supplier, a client, the lines, and totals that are derived from the lines unless you override them. Five layouts draw that same model. Classic is a two-column header, a line-items table, totals, and a payment area. Classic with sidebar adds a full-height accent bar. Minimal drops the borders. Stacked puts a title banner on top and stacks supplier and client. Summary first puts the totals and the payment on page one and starts the lines on page two.

The README's own sample, including the names and the prices, looks like this. Those numbers are the library's example. They are not a rate we charge.

Swift
let invoice = InvoiceDocument(
    header: InvoiceHeader(
        invoiceNumber: "INV-2026-001",
        issueDate: "2026-03-01",
        dueDate: "2026-03-15",
        currency: "EUR"
    ),
    supplier: InvoiceSupplier(
        name: "Acme Corp"
    ),
    client: InvoiceClient(
        name: "Client BV"
    ),
    lines: [
        InvoiceLine(
            description: "Consulting",
            quantity: 8,
            unit: "hrs",
            unitPrice: 150,
            vatRate: 21
        ),
    ]
)

let pdf = PDF(
    layout: .classic,
    invoice: invoice,
    theme: .standard
)

Three themes ship with it: standard, gold and corporate. You can replace the accent, the fonts and the logo position without writing a new layout. The same document model feeds four other types, each with a small supplement for the fields an invoice does not have: a quotation, a sales order, a delivery note and a shipment document. QR codes, including a SEPA payment code, are generated in pure Swift. Fifteen demos cover the combinations, and swift run GenerateDemos writes them out.

Two rendering paths, on purpose

On Apple platforms the library draws with CoreGraphics. On Linux it goes through HTML and the wkhtmltopdf binary. That is a different code path, and the README says so. If you are generating PDFs on a Linux server, "pure Swift" describes the document model and the QR encoder, not the last step that turns HTML into a file. There is a SwiftUI preview helper, PDFPreviewView, for the Xcode canvas. It is a separate product in the package, SwiftlyPDFKitUI.

Beside the invoice types, the DSL is the usual result-builder set: pages, text, tables, columns, images, rules, footers. Page sizes in the README include A4, US Letter and US Legal. None of that is specific to money. The invoice engine is the part we reach for when the document has a supplier, a client and a total.

What this is not

Swiftly Workspace, our ERP, has a native invoice screen on the Mac: line items, quantities, unit prices, a discount, tax and totals. That is a product. This is a library. This article does not say Workspace calls Swiftly-PDFKit. We have not published that wiring, so it is not claimed here. If you want the ERP, the Workspace article is the one. If you want a PDF from a Swift value, the repository is the one.

Swiftly Workspace ERP

Swiftly-PDFKit on GitHub

Where an agent fits

An agent can fill an InvoiceLine list from a messy brief, and it can get the layout wrong in ways a snapshot would catch. It should not be the thing that decides the PDF is ready to send. The useful split, the same one we use on native iOS work, is that the model drafts and a person reviews. Here the review is about the numbers and the document type, not about a pixel. Belgian e-reporting and Peppol are a different problem from drawing the page. Those deadlines live on the consulting site. This library does not implement them, and this article will not pretend a layout engine is a tax engine.

If you vendor it, pin a tag, keep the sample prices out of production, and treat the Linux path as the path the README describes. The library is early in its tags, v0.3.0 as of August 2026, and the README's install line is behind the tag. That gap is part of using it, not a footnote to skip.

Comments

Loading comments…

Got a build in mind?

We ship native iOS, Android, and server-side Swift for teams that care about craft. Book a call and let's scope it.