This site uses one functional cookie to keep feature rollouts consistent for you. Nothing is set until you choose. See the privacy notice.
Dev notes
A Firefox extension that rides along inside the ESPN fantasy draft room: it watches picks happen, tracks keepers, knows my league's exact scoring, and answers the three questions that matter on the clock — who to take, what survives to my next pick, and where the tier cliff is. Benchmarked at top-3 finishes in 81 of 100 simulated drafts. Lives in its own repo, not this one.
Every draft tool I tried lives in another tab showing someone else's rankings under someone else's scoring. Draft Lab is the opposite: it knows this league — full PPR, the OP superflex slot, distance-tiered kicking, the D/ST points-allowed brackets — and it sits inside the actual ESPN draft room, reading picks as they happen. While everyone else eyeballs an ADP cheat sheet, it answers the only three questions that matter on the clock: who should I take, what will still be here next turn, and where is the cliff.


Three layers, deliberately separated. At the bottom is a pure draft engine — snake order, keeper slotting, roster legality, scoring, recommendations — plain functions over plain data, no DOM, no storage, fully unit-testable. Above it, extension storage acts as the message bus: the content script in the draft room and the board tab both read and write one shared record, so neither depends on the other being open. At the top are two thin render surfaces — a shadow-DOM overlay injected into ESPN (so their CSS can't touch it and vice versa) and a full-page board.
The scoring pipeline starts from raw stat projections, not points: fetch ESPN's projection feed (or a CSV), then apply this league's rules locally. That ordering matters — the same stat line is worth different points under different rules, and it means a new data source re-scores automatically. Recommendations then score each candidate as marginalLineupValue + 0.6 × urgency, where marginal value comes from greedily filling the actual slot structure (QB/2RB/2WR/TE/FLEX/OP/D-ST/K) with and without the candidate, and urgency is the drop-off to the best same-position player likely to survive to your next pick — survival being a product of per-team softmax pick probabilities over each intervening opponent's needs and personality. Tier cliffs (your overrides included) amplify urgency; your personal caps hard-filter positions you refuse to draft more of.


One button plays the whole draft out: opponents draft by their configured personalities, your picks come from the recommendation engine, keepers resolve where they're pinned. When it ends, every team gets a letter grade curved against the room by starting- lineup points — which is how the engine indicted itself: an early version gave my team the league's best total points and its tenth-best starters, the exact fingerprint of drafting bench depth. The Strategy tab is the distilled output of ~2,500 of these simulations: what actually works when RBs fly early, why chasing a QB run is the worst tested move, and the one moment reaching is correct.


Vanilla ES modules loaded straight from the extension directory: no bundler, no framework, no dependencies at all. An extension I reload twenty times during a draft is the wrong place for a build step. The split that matters is engine versus surface — snake order, keeper slotting, positional maximums, VOR, survival odds and tier math are pure functions in one module, exercised by fifteen node --test cases including a full 180-pick simulated draft and a 30-seed legality stress run. The UI (a board tab and a shadow-DOM overlay injected into the draft room) just renders what the engine says.
Every assumption about ESPN's draft room broke at least once. The pick sidebar virtualizes, so history disappears from the DOM. Row text has no whitespace between cells unless you read innerText instead of textContent. The Players table changes its button text depending on whose turn it is. The approach that survived: treat the rendered text as the interface, anchor patterns on stable tokens (the on-the-clock banner, the QUEUE/DRAFT buttons, the "R1, P1" pick format), and cross- check against ESPN's unofficial league API where cookies allow. Every scraper failure taught the same lesson — make the failure visible. The overlay grew a version badge, a detection log, and a debug line showing candidate-row counts, because "it's not working" with a screenshot of self-reported diagnostics is a one-round fix instead of four.
The unit tests pin the league scoring line by line, but the bugs that mattered came from one integration test that plays an entire draft: keepers being draftable by other teams before their slot resolved, and AI rosters finishing without a kicker because the ADP window never surfaced one. Neither shows up in any single-function test — they only exist across 180 sequential decisions. That test earned its keep more than the other fourteen combined.
Update — August 26, 2026
Running the extension against back-to-back practice drafts grew it fast. The overlay now carries a tier system — players auto-tiered from projection gaps over the full pool so tier identity survives the draft, editable per player from a Tiers tab (overrides persist across drafts), rendered as a ruled grid of left/total per position with my own fill against the roster caps. Recommendations blend my ESPN "My Rankings", harvested straight off the page when that sort is active, and a tier outlook replaced the useless "most teams still need an RB" trend with expected survivors per tier by my next pick.
The bugs were better than the features. ESPN's Players list and Pick History rows collapse to nearly identical text, so the pick parser ingested ranking rows as picks — rank numbers became pick numbers and the QUEUE button text overwrote real team names. The guard is embarrassingly simple: a "team" called QUEUE is a button, not a franchise. Separately, keepers stopped being respected after switching projection sources, because keeper assignments stored player ids and every source uses a different id scheme — the reservation pointed at nobody, so the AI happily drafted a keeper three rounds early. Keepers now carry names and re-resolve against whatever pool is loaded.
One honest limit surfaced too: an auto-keeper mode that makes League Manager picks in ESPN's own UI works when it gets the clock, but it cannot stop ESPN's autopick from sniping a keeper-designated player early — the reservation only exists in the extension. The reliable lock is ESPN's native Keepers tab; the overlay now says so, loudly, when a keeper gets taken before their slot.
The grades feature then indicted the AI itself. A full simulation gave my team the league's highest TOTAL points and its tenth-best STARTING lineup — the fingerprint of drafting by raw value: bench depth at stacked positions instead of starters. The engine now scores every candidate by marginal lineup value (what they add to the lineup I can actually field), "my" picks in simulations come from the recommendation engine rather than the generic AI, personal caps let me refuse a third QB or second TE outright, and my tier board — overrides included — feeds an urgency bump when a pick is the last of its tier likely to survive to my next turn. Across a twelve-draft benchmark the recommendation-driven team now averages second or third in starter points instead of bottom-third.
The same harness then answered a question no draft tool I know of touches: which two players should I actually keep? Feeding in last year's real draft history (keeper cost = round drafted, undrafted = last round) and my end-of-season roster, it simulated every viable keeper pair — 55 combinations, 30 drafts each, against opponents keeping their own best values. The first run confidently told me to swap a keeper for a receiver my bundled projections still priced as a top-2 WR — he'd actually finished WR21. Re-priced at 75% current projection / 25% last-year finish, the answer inverted — and with live ESPN projections in the blend, my existing pair ranked first outright, top-3 in every simulated draft. The lesson is the oldest one in modelling — the optimizer is only as good as its prices, and a confident ranking off one projection source is a bug that looks like a feature.
Worth saying plainly: none of this accounts for injuries, camp news, depth-chart changes, or anything else that isn't already baked into the projections it runs on. The keeper sweep briefly ranked a QB coming off IR as the best keep in the league, because no number in the system knows what a knee looks like. The projections are the model's entire world — which is exactly why swapping them (ESPN fetch, CSV, blends) is a first-class operation in the extension instead of an afterthought.
The blend then shipped INTO the extension rather than living in offline scripts: one fetch now pulls current projections and last season's actuals, scores both under the league's rules (validated to the decimal against the league's own season totals on four of six spot checks), and prices every player at 75/25 — except players who missed their season's final three games, who stay on pure projection so an injury-shortened year can't drag a healthy player's value. The last calibration layer came from the league itself: profiling all eleven opponents from the real 2025 draft recap (the QB hoarders, the zero-QB-until-round-15 gambler, the team that takes a D/ST in round seven), assigning each a drafting personality, and re-running the simulations against that specific room — with the actual keeper landscape, the engine won all sixty. Not a promise, since real humans out-weird any tendency parameter, but the policy the panel recommends live is literally the one that produced that record.