DEV Community

Cover image for I built an invoice generator with no backend — the whole app is one HTML file
Ahmed Memon
Ahmed Memon

Posted on

I built an invoice generator with no backend — the whole app is one HTML file

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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=...
Enter fullscreen mode Exit fullscreen mode

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)