Skip to content

Sports odds comparison browser extension

Browser extension (Chrome, Firefox, Safari) comparing sports betting odds directly on sportsbook pages. I worked on adapters for 40+ sportsbooks and shipped fixes across the other product apps.

Project details

#Context

At OddsGuard I worked on a browser extension for comparing sports betting odds. The main area was the adapter layer - modules that recognised odds in the HTML of specific sportsbook pages and attached a badge with a better price from another sportsbook next to them. I also shipped fixes across the other product apps: the backend API, the odds-polling service, and the marketing site.

I joined OddsGuard after working on a regulated online multi-brand gambling platform - the sports betting market context (regulators, brands, players, how odds and bets work) was already familiar, and an odds-comparison product was a natural next step.

Alongside day-to-day tickets, I led larger initiatives myself - from idea, through prototype, to production. Details in the “What I delivered” section below.

The extension compared sports betting odds in real time. When a user browsed a sportsbook, the extension detected the price directly on the page and showed a badge with a better one from another operator next to it. Supported browsers: Chrome, Firefox, Safari (including Safari on iPhone) - all on the Manifest V3 standard. The extension itself covered more than 40 sportsbooks across the UK, US, European, and offshore markets.

The extension was one of the apps inside a larger product living in a single code repository (monorepo built on pnpm and Turborepo), alongside the backend, the data services, and the other client applications.

Statystyki

My activity in the first three months

Tickets closed
82
Tickets closed assigned to me and completed, April - July 2026
New features shipped to production
40
New features shipped to production excluding pure bug fixes and cleanup work
Adapters written from scratch
2
Adapters written from scratch Oddsshark, VegasInsider - competitor aggregators
Supported browsers
3
Supported browsers Chrome, Firefox, Safari (Manifest V3 standard)

#What I delivered

  • Adapters written from scratch: Oddsshark and VegasInsider - two adapters for sites that aren’t sportsbooks themselves, but competitor aggregators. The challenge: those sites showed several price alternatives per row, so a single adapter had to be able to clone the comparison badge as many times as there were options in that row.
  • Shared layer under those adapters: I extracted a shared mixin (a reusable code fragment) for the competitor-aggregator adapters - one change now propagated to every adapter of that kind at once. Similarly, for sportsbooks sharing the same underlying odds-display engine, I extracted shared platform files supporting the whole group.
  • World Cup 2026 coverage: I shipped comparison badges on WC pages across several sportsbooks - theScore Bet, DraftKings, Oddschecker, William Hill - plus a single change in shared code that landed “who wins the tournament” outright badges across a whole group of sportsbooks at once. Each operator had a different tournament-page structure (a separate match list, a separate bracket, a separate “futures” tab), so each one needed its own recognition logic in the adapter.
  • Keeping the extension running across three browsers at once: the same source code built into three separate bundles. Each browser had its own release process and its own pre-release tests.
  • Safari on iOS and macOS through Xcode: the extension also ran as a native Apple app inside an Xcode project with a Swift shell. I added Apple Privacy Manifests (files describing what data the app collects) for all four apps built from the project - required by App Store.
  • Work across the other product apps (about 40 changes beyond the extension itself): a competitor-comparison section on the marketing site (URLs shaped like /vs/competitor-name), AI chatbot integration in the backend API, and work in the odds-polling service.
  • Tests and monitoring: unit tests pinning the selectors (code fragments identifying elements on the sportsbook page) so that a silent change on the sportsbook side was caught in automated tests before release, not by the end user.
  • Strict TypeScript, zero any: a project-wide standard enforced across the whole codebase, gated in CI.

#Challenges

Adapters had to survive silent HTML changes at the sportsbook

Sportsbook pages changed their HTML without warning - a different element id, a renamed CSS class, a rebuilt column structure, a new odds-table layout. An adapter that recognised the odds correctly today would show zero badges tomorrow. The trick was making selectors (code fragments pointing at where to find the price in the page) specific enough not to catch false positives, but loose enough to survive small rebuilds. On top of that: tests pinning the selector contracts - so a silent id change on the operator side was caught in automated tests before release, not by end users - and a clear distinction between “this is an adapter bug” and “this is a data gap in the odds-polling service” - two different ticket types, two different areas of the codebase.

Manifest V3 across three browsers meant three different implementations in practice

Chrome, Firefox, and Safari all declared Manifest V3 support (the standard describing how extensions should behave in the browser), but every browser differed in the details: they handled background workers differently, they resolved cross-domain request permissions differently, they held sessions differently across navigations, they passed messages between extension parts differently. Safari on iOS was its own story - a bespoke packaging process, the iPhone simulator had to be wiped before every install test (because persisted user settings masked the first-run flow), and copying the bundle into the Xcode project after every change in the main branch was daily release hygiene. I treated each browser as a separate target and reconciled the differences in a single shared layer - instead of writing one version and hoping it would behave identically everywhere.

In-play betting: the page code changes constantly, not only on load

In in-play betting views the odds updated in real time - prices moved, participants (players or teams) dropped off, the event ended in the middle of the user’s session. Adapters written for pre-match views (a one-shot scan after page load) weren’t enough. I worked on solutions that watched page-code changes on the fly - only activated when the user landed on a specific in-play URL pattern on several sportsbooks. Key idea: the overlay disappeared together with the participant, not left hanging on a stale price.

Telling adapter bugs apart from data gaps and geo-restrictions

When a user reported “I don’t see odds comparison on this match”, it could mean three completely different things: (a) the adapter parsed this specific sportsbook view incorrectly, (b) the odds-polling service didn’t have those prices from the upstream provider, (c) the user was in a region where the operator didn’t offer that market at all. I put together a diagnostic procedure - first checked the geo-restriction dimension against the production data feed, then the polling-service dimension for missing prices, and only then inspected the adapter in browser devtools. Concluding “no adapter bug here” is a legitimate outcome, as long as it comes with concrete evidence.

#Takeaways

  • Manifest V3 across three browsers was, in practice, three different implementations under one name - the safest bet was to treat every browser as a separate target and reconcile the differences in a single shared layer, instead of writing one version and hoping it would behave identically everywhere.
  • DOM-driven adapters needed production observability - without production telemetry and tests pinning selector contracts, there was no way to tell when the sportsbook page had changed its HTML. Fixes triggered by a user report were always late.
  • Shared patterns (reusable mixins, platform files for sportsbooks sharing an engine) paid off the most - when the twentieth adapter started to look like the twenty-first, extracting a shared mixin was what let me recover velocity. The shared mixin for competitor-aggregator adapters came out of exactly this observation, after the first two of that kind.
  • Telling failure modes apart matters on any product that renders third-party data it doesn’t own: a report of “nothing shows up here” can point at the adapter, at the data layer, or at a geo-restriction. Without a set diagnostic procedure, it’s easy to fix the wrong thing - and ship a “fix” that changes nothing.

Let's work together

I'm a senior frontend engineer focused on building fast, accessible web experiences with clean, type-safe TypeScript. I work mainly with React and Next.js, and I care about SSR/SSG, Core Web Vitals, and maintainable component systems. I value clear communication and shipping practical solutions - if that sounds like what you need, let's talk.

Get in touch →