shots()
returns ShotEvent[] Just the shots, ready to plot.
Understat notes: Includes xG, body part, and situation.
Understat is a scraped dataset with per-shot xG, body part, and situation — ideal for xG-driven charts when you don't have a paid feed. The adapter handles their top-left origin, maps their result strings to the Campos outcome vocabulary, and drops own-goals the way the shot products expect.
fromUnderstat.shots(shotRows) ShotEvent[] <ShotMap />, your own React view, a server-side
report, or an analysis script.
Understat is the only free public source with per-shot xG that Campos currently has an adapter for. If you don't have an Opta or StatsBomb licence, this is the path for xG-driven charts.
import { fromUnderstat } from "@withqwerty/campos-adapters";
const summary = fromUnderstat.matchSummary(scheduleRow);
const shots = fromUnderstat.shots(shotRows); shots()
returns ShotEvent[] Just the shots, ready to plot.
Understat notes: Includes xG, body part, and situation.
matchSummary()
returns MatchSummary Scoreline, status, and team-level xG.
Understat notes: Scoreline, team xG, competition, kickoff.
Supported cards plot from this adapter with no extra work. Partial cards plot, but read the scope note. Dimmed cards need a surface this provider doesn't ship.
Plot events, shots, passes, and formations directly on the pitch.
Time-series and xG-driven views from shots().
These charts are adapter-independent — they take pre-aggregated inputs.
matchSummary() takes a schedule row and returns a clean
MatchSummary with competition, kickoff, scoreline, and team xG.
shots() takes an array of shot rows and returns ShotEvent[]
with canonical coordinates (Understat stores y top-to-bottom from the broadcast camera
— the adapter inverts to attacker-rightward), outcomes mapped to the Campos vocabulary,
and own-goals filtered out.
ShotMap with xG-weighted markers. XGTimeline for match xG
accumulation. Team xG in match-header cards. Everything that needs per-shot or team-level
xG and doesn't need the wider event stream.
Understat is shot-and-summary only. For pass maps, formations, or team sheets, reach for Opta, StatsBomb, or WhoScored instead.
The adapter takes plain objects — it doesn't care how you got them. Most users pull from soccerdata's Python scraper and serialise to JSON, or hit Understat directly. The row shape is stable; see the raw example below.
Concrete credits for the projects that did the underlying research or laid the reference tables we read from.
Scrape-backed narrow-adapter pattern; the schemas we read for Understat and FBref schedule and shot rows.
{
"game_id": 26761,
"league": "Premier League",
"season": "2024",
"date": "2024-08-17T14:00:00Z",
"home_team": "Liverpool",
"away_team": "Bournemouth",
"home_goals": 2,
"away_goals": 0,
"home_xg": 1.84,
"away_xg": 0.91,
"is_result": true
} {
"shot_id": 548921,
"game_id": 26761,
"team_id": 87,
"team": "Liverpool",
"player_id": 1250,
"player": "Diogo Jota",
"xg": 0.12,
"location_x": 0.82,
"location_y": 0.54,
"minute": 23,
"body_part": "left foot",
"situation": "open play",
"result": "Saved Shot"
} {
"kind": "shot",
"matchId": "26761",
"playerName": "Diogo Jota",
"minute": 23,
"x": 82,
"y": 46,
"xg": 0.12,
"outcome": "saved",
"bodyPart": "left-foot",
"context": "regular-play",
"provider": "understat"
}