events()
returns Event[] Every recognised match event in a canonical shape.
StatsBomb notes: Includes carries, pressures, recoveries, take-ons, and out-of-play cards.
StatsBomb coordinates are absolute — no half-flipping, just an origin-at-top-left to deal with. The adapter maps every documented event type (shots, passes, carries, pressures, recoveries, take-ons, out-of-play cards) and stitches the separate lineup feed to the event stream for team sheets.
fromStatsBomb.shots(raw, matchInfo) ShotEvent[] <ShotMap />, your own React view, a server-side
report, or an analysis script.
If you're new to Campos adapters, start here. StatsBomb's coordinate story is the simplest of the four primary providers — no direction flipping between halves — and the event envelope is nested cleanly enough to explain in one screen.
import { fromStatsBomb } from "@withqwerty/campos-adapters";
const events = fromStatsBomb.events(rawEvents, matchInfo);
const shots = fromStatsBomb.shots(rawEvents, matchInfo);
const passes = fromStatsBomb.passes(rawEvents, matchInfo);
const lineups = fromStatsBomb.matchLineups(rawLineups, rawEvents, matchInfo);
const formation = fromStatsBomb.formations(rawLineups, rawEvents, matchInfo, "home"); events()
returns Event[] Every recognised match event in a canonical shape.
StatsBomb notes: Includes carries, pressures, recoveries, take-ons, and out-of-play cards.
shots()
returns ShotEvent[] Just the shots, ready to plot.
passes()
returns PassEvent[] Pass trajectories with start, end, and result.
matchLineups()
returns MatchLineups Home and away team sheets with starters and bench.
formations()
returns FormationTeamData Kickoff tactical shape for one side.
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.
StatsBomb stores events with location: [x, y] where y grows top-to-bottom
from the broadcast camera's perspective. The adapter scales to 0–100, inverts y so the
attacker is facing rightward in the Campos frame, and flattens the nested shot / pass / carry sub-objects into the canonical event shape.
{
"raw": {
"location": [
108,
44
],
"period": 2
},
"normalized": {
"x": 90,
"y": 44.99999999999999,
"period": 2,
"outcome": "saved"
}
} Includes the surfaces most other providers don't expose as first-class — explicit carries (type 43), pressures with a counterpress field (type 17), ball recoveries (type 2), take-ons from dribble events (type 14), and out-of-play bookings via bad behaviour (type 24).
matchLineups() takes both the lineups payload and the event stream because
starters and formation live in Starting XI events (type 35), while the broader squad
lives in the lineup feed. formations() projects a kickoff snapshot from
the same stitched team sheet.
Several providers either omit these or require you to reconstruct them from gaps
between other events. Getting them as first-class Events means
press-map and carry-driven work doesn't need a separate inference layer.
Concrete credits for the projects that did the underlying research or laid the reference tables we read from.
Provider type-ID → canonical kind mapping, coordinate transformers, and direction-by-period normalisation patterns. Our reference for Opta, StatsBomb, Wyscout, Stats Perform, and Sportec.
Reference for how raw StatsBomb event envelopes and lineup payloads are typically consumed.
Pitch-standardisation conventions we cross-checked our canonical Campos coordinate frame against.
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"index": 1842,
"period": 1,
"timestamp": "00:23:14.320",
"minute": 23,
"second": 14,
"type": {
"id": 16,
"name": "Shot"
},
"possession": 47,
"possession_team": {
"id": 217,
"name": "Barcelona"
},
"play_pattern": {
"id": 1,
"name": "Regular Play"
},
"team": {
"id": 217,
"name": "Barcelona"
},
"player": {
"id": 5503,
"name": "Lionel Messi"
},
"position": {
"id": 17,
"name": "Right Wing"
},
"location": [
102.3,
35.6
],
"duration": 0.72,
"shot": {
"statsbomb_xg": 0.078,
"end_location": [
120,
38.2,
1.1
],
"body_part": {
"id": 40,
"name": "Right Foot"
},
"type": {
"id": 87,
"name": "Open Play"
},
"outcome": {
"id": 97,
"name": "Goal"
},
"technique": {
"id": 93,
"name": "Normal"
},
"first_time": false,
"freeze_frame": [
{
"location": [
108.4,
36
],
"player": {
"id": 6785,
"name": "Goalkeeper"
},
"position": {
"id": 1,
"name": "Goalkeeper"
},
"teammate": false
}
]
}
} {
"id": 3773585,
"homeTeam": {
"id": 217,
"name": "Barcelona"
},
"awayTeam": {
"id": 211,
"name": "Real Madrid"
}
} [
{
"kind": "shot",
"id": "3773585:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"matchId": "3773585",
"teamId": "217",
"playerId": "5503",
"playerName": "Lionel Messi",
"minute": 23,
"addedMinute": null,
"second": 14,
"period": 1,
"x": 85.24999999999999,
"y": 55.5,
"xg": 0.078,
"xgot": null,
"outcome": "goal",
"bodyPart": "right-foot",
"isOwnGoal": false,
"isPenalty": false,
"context": "regular-play",
"goalMouthY": 27.5,
"goalMouthZ": 41.2,
"endX": 100,
"endY": 52.25,
"provider": "statsbomb",
"providerEventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"sourceMeta": {
"index": 1842,
"shotType": "Open Play",
"technique": "Normal",
"playPattern": "Regular Play"
}
}
]