Every invoicing tool I tried wanted an account, a subscription, and a copy of my client list on its servers — then charged me monthly to put my own logo on my own invoice.
So I built the opposite. Billfold is a complete invoice generator that runs entirely in your browser. No account, no backend, no build step. The whole app is a single index.html file you could email to yourself. It's MIT-licensed and the source is right here: github.com/quantum-hacker0/billfold.
Here are the three parts that were actually fun to build.
1. "No server" isn't a privacy policy — it's the architecture
The usual pitch is "we take your privacy seriously." That's a promise you have to trust. I wanted it to be a fact you can verify:
Open DevTools → Network, create an invoice, and count the requests. It's zero.
There's nothing to upload because there's nowhere to upload it. Data lives in localStorage. The app is HTML/CSS/JS inlined into one file — no framework, no bundler, no node_modules. Download it once and it works offline forever.
2. Sharing an invoice without a database — put it in the URL hash
This was the interesting constraint. How do you send someone a view-only invoice when you have no server to store it on?
The trick: encode the whole document into the URL hash fragment. The fragment (everything after #) is the one part of a URL that browsers never send to the server — it stays client-side.
function shareLink(state) {
const json = JSON.stringify(state);
const encoded = btoa(unescape(encodeURIComponent(json)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); // base64url
return location.origin + location.pathname + '#v=' + encoded;
}
The recipient's browser reads the fragment, decodes it, and renders the invoice locally. The data rides inside the link and never touches a host — not even mine. PDF export, by the way, is just window.print() with a print stylesheet.
3. Invoices as URLs — with an npm package
Because the app reads its state from query params, a prefilled invoice is just a URL:
https://billfold.io/app/?cur=USD&to=Acme+Co&item=Design+work|10|120
That turned out to be genuinely useful — you can generate a ready-to-send invoice per customer straight from your own app or a spreadsheet. So I published a tiny zero-dependency helper to build those links safely (encoding, the | field rule, negative lines for things like tax deductions):
npm install billfold-invoice-link
const { buildInvoiceLink } = require('billfold-invoice-link');
const url = buildInvoiceLink({
currency: 'USD',
business: 'Jordan Rivera Design',
client: 'Acme Co',
items: [
{ description: 'Landing page', quantity: 1, rate: 1800 },
{ description: 'Revisions', quantity: 3, rate: 120 },
],
});
// → https://billfold.io/app/?cur=USD&biz=Jordan+Rivera+Design&to=Acme+Co&item=...
It's typed and tested: npmjs.com/package/billfold-invoice-link.
Bonus: license keys that verify offline
The paid tier is a one-time unlock, and I didn't want an activation call either. Keys are signed server-side with an ECDSA P-256 private key; the app verifies them with the embedded public key via the Web Crypto API. So even activating a license makes no network request, and a key can't be forged without the private key.
The honest tradeoff
Local-first isn't free. The thing you give up is cross-device sync — your data is on the device you made it on. For an invoice tool I think that's the right trade (you export a PDF and send it anyway), but it's a real limitation.
The only backend I'd ever add is optional end-to-end-encrypted sync — and it would only ever see ciphertext, never your numbers. The "no server can read your data" property is the whole point; I'm not going to quietly break it.
If a zero-server, single-file approach is your kind of thing, the source is one readable file: github.com/quantum-hacker0/billfold (a ⭐ helps others find it), and the hosted version is at billfold.io.
Happy to get into any of the local-first tradeoffs in the comments.
Top comments (0)