Overview
Cijfera is an accounting app for Dutch sole traders. The core is deliberately narrow: invoicing, expenses, VAT overview and KOR. No bank connection, no Digipoort, no required subscription. A free account is enough to start.
The problem
Dutch sole traders often pay €15–20 a month for software they mainly use to invoice and keep a VAT overview. Spreadsheets are free, but numbering, PDFs, KOR and quarterly figures stay manual. Most tools were not built around the Dutch return.
My solution
Cijfera covers that core, in Dutch, with no subscription. Invoices that can go out the door. A VAT overview that matches the return, not a generic 'tax percent'. KOR as a first-class path, not an afterthought.
Product
- Invoices and quotes
- Draft, outstanding, paid. Quote to invoice, credit notes, PDF with KvK and VAT ID, reminders.
- Expenses
- Categories, outstanding items, optional receipt import via the OpenAI API.
- VAT and KOR
- Rates 21/9/0, quarterly overview, KOR, reverse-charged VAT and EU/export regimes as they appear on the return.
- Account and access
- Sessions via Better Auth, optional passkeys (WebAuthn) and 2FA.
Architecture
A Next.js application with a relational database. The browser talks to the app; the app writes to PostgreSQL, sends mail via Resend, stores attachments in object storage and calls the OpenAI API for optional receipt import. Auth sits in Better Auth, not a homegrown session layer.
- Next.js · TypeScript
- PostgreSQL · Drizzle ORM
- Better Auth · passkeys · 2FA
- React-PDF · Resend · object storage · OpenAI
Tech stack
- Next.js
- TypeScript
- PostgreSQL
- Drizzle ORM
- Better Auth
- OpenAI API
AI integration
Receipts and purchase invoices can optionally be read via the OpenAI API. It is a helper with quotas, not a replacement for the ledger. The user stays responsible for what lands in the books.
Database
Invoices, line items, payments and VAT boxes belong in a relational model with constraints, not a document store. Drizzle keeps schema and queries in TypeScript. Invoice numbers are unique and sequential per administration — that lives in PostgreSQL (sequences/constraints), not the UI.
Design decisions
PostgreSQL + Drizzle
Bookkeeping is relations and invariants. Drizzle keeps schema and queries in the same language as the rest of the app.
Dutch VAT in the data model
No generic 'tax percent'. Separate regimes and treatments (KOR, 1c, reverse charge) so the overview matches the return.
No bank connection on purpose
Cijfera stays light: invoices, expenses, VAT. No Digipoort, no required subscription. The core stays maintainable solo and free.
PDF and mail as product features
Invoices have to go out. PDF via React-PDF, attachments via object storage, mail via Resend — not a manual export as the only path.
What I built myself
Everything that is live, I built and shipped myself. No team, no agency layer.
- Product decisions and scope
- Data model and migrations
- Auth, passkeys and 2FA
- UI from overview to invoice
- PDF generation and mail
- AI import of receipts
- Marketing site
Challenges
Dutch VAT is not a percentage field. Cijfera splits revenue and input VAT into boxes that appear on the return: 21, 9, 0, 1c, private use, domestic reverse charge, KOR. Drafts do not count; outstanding and paid do. Deadlines hang off the previous quarter, not a US tax engine.
Invoice numbers must be unique and sequential per administration. That lives in PostgreSQL, not the UI. Auth is Better Auth: sessions, optional passkeys and 2FA — because accounting is about access, not a magic-link demo.
AI import is a helper with quotas. PDF, object storage and mail belong to the product: an invoice that cannot go out is not an invoice. Bank connection and Digipoort were left out on purpose so the core stays maintainable solo.
Result / current status
Cijfera is live at cijfera.nl: free account, Dutch KvK, invoices and VAT overview with no trial period. No user counts on this page — the proof is the working product.
Product visuals
Sample data, not live figures from users.
cijfera.nl
Sample datacijfera.nl
Sample data