What I Learned Building 46 Browser-Based Tools for One WordPress Site

Imdad Khan Author
10 min read
Share
What I Learned Building 46 Browser-Based Tools for One WordPress Site

Every one of the 46 tools on this site runs entirely in your browser. Nothing you put into them is uploaded, because there is nowhere for it to go — no server processing, no queue, no temporary storage, no “we delete files after an hour” promise you have to take on faith.

That was a deliberate choice and it has been consistently more difficult than the alternative. This is what it actually costs to build tools that way, including the bugs that only exist because of it, and why I would still do it again.

Tools46, all client-side
Files uploadedZero
Hardest to buildPDF generation
Most surprising bugWordPress itself

Why client-side, when server-side is easier

The honest answer is that “we don’t store your files” is a claim, and “your files never leave your computer” is an architecture. Only one of those can be verified by the person using it.

If you compress an image on this site, the work happens in your browser using a canvas. Open your network tab and you will see no upload. That is not a privacy policy you have to trust, it is an observable fact, and for anything involving an invoice, a contract, a passport photo or a client’s document, that difference is the entire point.

There is a practical benefit too. No uploads means no file size limits, no queue when several people arrive at once, and no hosting bill that scales with usage. A tool that runs on the visitor’s machine costs the same whether ten people or ten thousand use it.

What it costs: you inherit every limitation of the browser. No ImageMagick, no Ghostscript, no server-side libraries. Anything you cannot do in JavaScript, you cannot do at all — and some things that are trivial server-side turn into a genuine engineering problem.

The PDF problem

Converting images to PDF sounds simple until you try it without a library.

A PDF is a structured binary document — objects, a cross-reference table, byte offsets that must point exactly where they claim to. Getting it wrong produces a file that either fails to open or opens blank, with no useful error either way.

The part that saved me is a detail of the format worth knowing: a PDF can embed a JPEG’s bytes verbatim using a DCTDecode filter. You do not decode and re-encode the image, you write the original bytes into the file and tell the PDF what they are. That means no quality loss and no image processing at all — the hard part becomes bookkeeping rather than graphics.

Text was harder. To place text you need to know how wide it will be, and to know that you need font metrics. I ended up embedding the width tables for Helvetica — one of the fourteen fonts every PDF reader is guaranteed to have — so the invoice generator can measure a string before it draws it, wrap it, and right-align a column of numbers so the decimal points line up.

None of that is difficult in the sense of being clever. It is difficult in the sense that there is no library doing it for you and every byte has to be right.

The bug that was not my code

This one cost me an evening and it is the most useful thing here if you build tools inside WordPress.

A tool that had worked perfectly in testing broke the moment it went into a page. The JavaScript threw a syntax error that made no sense against the code I had written.

The cause was wpautop — the WordPress function that turns blank lines in your content into paragraph tags. It does not know or care that it is looking at a script. It split my JavaScript on a blank line and inserted </p><p> into the middle of it.

The fix is not to fight it. Take the JavaScript out of the post content entirely and print it from a hook that runs after content filtering, guarded to the page that needs it:

add_action( 'wp_footer', function () {
    if ( ! is_page( 'invoice-generator' ) ) {
        return;
    }
    ?>
    <script>/* your code, untouched by wpautop */</script>
    <?php
} );

There is a related trap. If you post content through the REST API, WordPress’s content sanitiser rewrites && into an HTML entity. Your logical AND becomes &#038;&#038; and your script dies. Same solution — keep code out of content.

The bug that was the browser

The PDF to JPG tool appeared to hang. Load a PDF, watch the progress indicator, and nothing would happen — sometimes for minutes, sometimes forever.

It was not hanging. The rendering library schedules its work with requestAnimationFrame, and browsers suspend that entirely in background tabs to save power. Every time I switched away to check something, the conversion stopped. Every time I switched back, it silently resumed.

I only worked it out by replacing the animation frame callback with a timer and watching the same file convert while the tab was hidden. Nothing was broken — the browser was behaving exactly as designed, and my mental model was wrong.

If you build anything long-running in a browser, assume the user will switch tabs, and test with the tab hidden.

What client-side tools gain and give up A comparison showing client-side tools gain verifiable privacy, no file size limits and flat hosting costs, while giving up server libraries, heavy processing and older browser support. The actual trade What you gain Privacy the visitor can verify No upload size limits No queue under load Hosting cost stays flat Works offline once loaded What you give up Every server-side library Heavy or long processing Predictable performance Older browser support Anything needing a secret key
The left column is why the site exists. The right column is why building it took longer than expected.

Money is not a floating point number

The invoice generator taught me the most, because money is deceptively hostile to computers.

The first rule is that you never hold currency in a floating point number. 0.1 + 0.2 is famously not 0.3, and an invoice that is a penny out is an invoice someone will query. Everything is held as an integer number of minor units — pence, cents — and only formatted for display at the very end.

The second rule is that rounding has to be deliberate. If you calculate tax on each line and again on the total, the two will occasionally disagree by a penny. I put the difference on the largest line, so the parts always sum exactly to the whole. That is a decision, not a default, and every invoicing system has to make it one way or another.

The problem I did not anticipate was parsing what people type. Is 1.234 one thousand two hundred and thirty four, or one and a bit? It depends entirely on where the person lives. The rule I settled on:

  • Both a dot and a comma present — whichever appears last is the decimal separator
  • Only dots, exactly one — decimal point
  • Only dots, several — thousands separators
  • Only a comma, with exactly three digits after it — thousands separator
  • Only a comma otherwise — decimal separator

That last case is the interesting one. 1,234 is almost certainly one thousand two hundred and thirty four. 1,23 is almost certainly one and twenty three. Three digits is the tell, and getting it wrong silently multiplies someone’s invoice by a thousand.

What I would tell someone starting

Test with the tab hidden. Browsers throttle background tabs aggressively and it will look like your code is broken when it is not.

Keep JavaScript out of post content. WordPress will reformat it. Print it from a footer hook scoped to the page.

Self-host your libraries. If your promise is that nothing leaves the visitor’s browser, loading a script from someone else’s CDN quietly breaks it — that request carries the visitor’s IP and referring page. It is a real cost in bandwidth and a real gain in honesty.

Integers for money, always.

Check what your page actually loads. I once wrote that a tool contacted no third party, and then found the theme was loading fonts from an external domain on the same page. The tool was clean; the page around it was not. Claims about privacy have to cover the whole page.

Worth the trouble

  • Privacy the visitor can confirm themselves
  • No upload limits, no queues, no per-use cost
  • Tools keep working if the site is slow
  • Nothing to breach, because nothing is stored
  • Genuinely differentiating from every upload-based competitor

What it costs

  • No server libraries — you write more yourself
  • Browser limits become your limits
  • Background tabs suspend long-running work
  • WordPress actively reformats your code
  • Large files depend on the visitor’s machine

All 46 tools are listed on the free tools page if you want to see what they do. The weight all that client-side code added to each page, and what I did about it, is in cutting 39 KB of HTML from every page.

Frequently asked questions

How can I verify nothing is uploaded?
Open your browser’s developer tools, go to the Network tab, and use any tool on this site. You will see the page load and then no further requests carrying your file. That check works on any site making this claim, and it is worth running on the ones that only say it in a privacy policy.
Why is a big PDF slow?
Because the work is happening on your machine rather than a server. A large file on an older laptop will take longer than the same file on a fast desktop. That variability is the direct cost of not uploading anything.
Why did my conversion stop when I switched tabs?
Browsers suspend animation frame callbacks in background tabs to save battery, and many rendering libraries schedule their work that way. It resumes when you return. Nothing is lost, but leave the tab in front for large files.
Can everything be done client-side?
No. Anything needing a secret API key, very heavy processing, or coordination between users has to happen on a server. The tools here are deliberately the category that does not — conversion, calculation, formatting and inspection.
Why build a PDF by hand instead of using a library?
Mostly weight and control. Embedding JPEG bytes directly with a DCTDecode filter avoids re-encoding entirely, so images keep their original quality, and the output stays small. It is more work once and less overhead on every page load after.
What breaks most often?
WordPress reformatting JavaScript that lives in post content. It has caught me more than once and the symptom — a syntax error in code that is definitely valid — sends you looking in entirely the wrong place.

The short version

Building tools that run entirely in the browser is harder than putting them on a server, and the difficulty is not evenly distributed — most tools are straightforward and a few, like generating a PDF from scratch, are genuinely involved. What you get back is a privacy claim the visitor can verify rather than believe, no limits tied to your hosting, and costs that do not grow with traffic. After 46 of them, I would make the same choice again.

How this article was made

Everything described here comes from building and maintaining the 46 tools on this site during 2026 — the PDF generation approach, the wpautop and REST sanitiser problems, the background-tab rendering behaviour and the currency parsing rules are all from that work and are running in production here. I have not benchmarked these tools against server-side equivalents, and for very large files a server-side converter will be faster. The trade described here is a deliberate one, not a claim that client-side is better in every respect.

Was this article helpful?
Scroll to Top