← All work

Fundable Chrome Extension

Overlays Fundable's funding card on any company's website, LinkedIn, or Crunchbase page.

Aug 2026Fundable

Chrome Extension (Manifest V3)JavaScript (no build step)Next.jsTypeScriptVercelUpstash RedisPostHognode:test + jsdomVitestGitHub Actions

Problem

Fundable wanted a Harmonic-style browser overlay: land on a company's website, LinkedIn or Crunchbase page, click once, and see who they are and what they have raised without leaving the tab. Two constraints shaped the build: extension code is fully inspectable in every user's browser, so the Fundable API key could never ship in it, and every lookup burns metered API credits, so an unguarded endpoint would be an open spend hole.

What I built

A Manifest V3 Chrome extension in plain JavaScript (no build step, no runtime dependencies) paired with a small Next.js proxy on Vercel that holds the API key. Clicking the toolbar icon injects a transparent overlay iframe into the active tab under activeTab, with no declared content script and no host permission beyond the proxy; the card is a cross-origin extension page, so its DOM and data never touch the host page. A deny-by-default resolver turns the tab URL into a domain, LinkedIn or Crunchbase identifier and refuses banking, health, webmail, auth and adult pages outright; the service worker, the only file allowed on the network, then asks the proxy. The proxy runs a cached three-call ladder (search, company, investors) against the Fundable API and returns trimmed card JSON behind CORS pinned to the extension's ID, a per-IP rate limit and a daily credit ceiling. Usage is counted server-side in PostHog against a random per-install id the extension sends as a header, so no analytics SDK ships in the extension.

Highlights

  • Every fetch lives in one file, background.js. popup.js and inject.js are forbidden from touching the network, and a test greps both for fetch, XHR, sendBeacon, WebSocket and remote src= so the rule cannot quietly rot.
  • A spend brake rather than a kill switch: the proxy atomically reserves the worst-case credit cost before calling upstream, settles to what the ladder actually burned (on failure paths too), and fails closed with a 503 if the counter itself is unreachable, the one place a cache failure is allowed to cost the caller an answer.
  • Two miss sentences that must never be swapped: 'we couldn't find a company on this page' versus 'not available yet, we'll work on adding it'. The coverage promise is shown only after a real lookup returned nothing, never for a deny-listed or non-company page.
  • The resolver reduces hosts to the registrable domain with a per-TLD public-suffix table, matches auth words across path segments, hash routes and OAuth/SAML query parameters, and denies host labels like app., my. and mail. so every company's private side is covered at once instead of blacklisting venture-backed brands.
  • Overlay hardening: the host page cannot drive the frame (postMessage source check), the frame's size request is clamped so a compromised card cannot cover the page, use_dynamic_url stops resource-URL fingerprinting, and card hrefs are dropped unless they are http(s).

Numbers

  • 51 extension tests pass under node --test with jsdom
  • 135 proxy tests pass under Vitest
  • 196 distinct URL fixtures across 24 resolver groups, all passing
  • Zero runtime dependencies in the extension; jsdom is the only dev dependency
  • 40 commits between 2026-08-14 and 2026-08-27

Screenshots

Fundable Chrome Extension: The overlay card mounted top-right of a page by the real inject.js. Rendered from the repo's test fixture, not a live lookup.
The overlay card mounted top-right of a page by the real inject.js. Rendered from the repo's test fixture, not a live lookup.
Fundable Chrome Extension: The card's three endings side by side: a hit, a company Fundable has not indexed yet, and a page that is not a company at all. Two different sentences, by design.
The card's three endings side by side: a hit, a company Fundable has not indexed yet, and a page that is not a company at all. Two different sentences, by design.