DEV Community

Jude Hilgendorf
Jude Hilgendorf

Posted on

I Built a Privacy-First Chrome Extension That Saves Your Forms Locally — Zero Network Requests

I Built a Privacy-First Chrome Extension That Saves Your Forms Locally

You're 800 words deep into a job application. Tab crashes. Refresh. Gone.

We've all been there. Most "form saver" extensions phone home to some SaaS with a freemium tier and a privacy policy nobody reads. I wanted the opposite: something that runs entirely on my machine, never makes a network request, and just works.

So I built FormVault — an open-source Chrome extension that auto-saves form data to local storage, with a one-click restore toast when you come back to the page.

GitHub: https://github.com/TiltedLunar123/FormVault

What it does

  • Auto-saves any form field you type into, debounced 3 seconds after your last keystroke
  • Pops a non-intrusive toast when you revisit a page with saved data
  • Skips passwords, credit card fields, SSNs — never saved, period
  • Banking sites blocked by default, with a user-configurable domain blocklist
  • All UI injected via Shadow DOM so it never collides with host page styles
  • 100% local storage. Zero fetch calls. Zero analytics. Zero telemetry. Verifiable in the source.

It's Manifest V3, MIT-licensed, and the entire codebase is small enough to audit in an afternoon.

Why I built it

I'm a cybersecurity student. I spend a lot of my time studying how user data leaks through "convenience" features. Every time I install a browser extension, I read its permissions and skim its code. Most form auto-savers either sync to a cloud (which means your half-finished forms — including stuff that should have been redacted — sit on someone else's server), or they have hidden analytics calls.

I wanted the boring, paranoid version: storage stays on your machine, code is small, nothing exfiltrates. Building it also forced me to learn a few things about the Chrome extension platform that I'd been hand-waving over.

The interesting technical bit: making auto-restore work with React

Here's where it got fun. Saving form values is easy — you addEventListener('input', ...) on every field, debounce, write to chrome.storage.local, done. Restoring is where most extensions fail silently on modern apps.

Set input.value = 'foo' on a React-controlled input and... nothing visible happens. The DOM updates, but React's internal state still has the old value, and the next render snaps it back. Same story with Vue, Svelte, and most reactive frameworks.

The fix is to call the native value setter directly and then dispatch the events React's synthetic event system listens for:

function setReactCompatibleValue(el, value) {
  // Use the native setter from the prototype, not the framework's overridden one
  const proto = el instanceof HTMLTextAreaElement
    ? HTMLTextAreaElement.prototype
    : HTMLInputElement.prototype;
  const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;

  if (nativeSetter) {
    nativeSetter.call(el, value);
  } else {
    el.value = value;
  }

  // React listens to these — bubbling matters
  el.dispatchEvent(new Event('input', { bubbles: true }));
  el.dispatchEvent(new Event('change', { bubbles: true }));
  el.dispatchEvent(new Event('blur', { bubbles: true }));
}
Enter fullscreen mode Exit fullscreen mode

Why this works: React patches the value setter on input elements to track changes through its synthetic event system. When you write el.value = ..., you hit React's patched setter, which marks the value as "set by React" and ignores the subsequent input event as a no-op. By grabbing the native setter from HTMLInputElement.prototype and calling it directly, you bypass the patched version, so when you then fire a synthetic input event with bubbles: true, React picks it up and updates state correctly.

The same technique works for Vue, Preact, and basically any framework that uses synthetic events.

The other thing: identifying fields without owning them

The other rabbit hole was figuring out how to find the same field again on a later page load. SPAs rerender. Element IDs sometimes survive, sometimes don't. CSS selectors that work today may break tomorrow.

FormVault stores three identifiers per field and falls back through them at restore time:

  1. Unique selector#id if there's an ID, otherwise tag[name="..."] if names are unique, otherwise a nth-of-type path up to the body.
  2. name attribute — used as a secondary lookup if the selector misses.
  3. XPath — last-resort positional path for sites with no useful identifiers.

Three layers of redundancy means restore works on the messy real-world web instead of just on demos.

Privacy guarantees you can actually verify

One of the things I find frustrating about "privacy-focused" tools is that you have to take their word for it. FormVault is small enough that you don't.

The repo is one content script, one service worker, one popup, and a tiny storage helper. Search the codebase for fetch, XMLHttpRequest, WebSocket, or any analytics SDK. You won't find them. The manifest doesn't request host_permissions for any external domain. Network tab will be silent forever.

For sensitive field detection, I check four signals before saving anything:

  • Input type (passwords and hidden are auto-skipped)
  • autocomplete attribute (new-password, cc-number, cc-csc, etc.)
  • Regex against id, name, aria-label, placeholder for password|ssn|cc-num|cvv|routing-number|account-number|pin-code and similar
  • Domain blocklist (banking sites are blocked out of the box)

False negatives are still possible on weirdly-named fields, but the multi-signal approach catches most real-world cases.

Try it

It's a developer-mode install for now (Chrome Web Store fee is a hurdle for a side project):

git clone https://github.com/TiltedLunar123/FormVault.git
Enter fullscreen mode Exit fullscreen mode

Then chrome://extensions/ → Developer mode on → Load unpacked → pick the folder.

If you find a form that doesn't restore properly, open an issue with the URL and I'll take a look. Bonus points if you can break the sensitive field detection — that's the one thing I want to keep tightening.

If you'd like to support the project, a star on the repo helps with discoverability. And if you have ideas — iframe support, Firefox port, export/import — the roadmap is open.

GitHub: https://github.com/TiltedLunar123/FormVault

Top comments (0)