StatsBomb adapter

StatsBomb: Cleanest event model to get started with.

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.

Input shape
StatsBomb event arrays, lineup payloads, and a lightweight match-info wrapper.
Status
Primary provider
Raw feed
StatsBomb
Provider-shaped. Their coordinates, their qualifier codes, their outcome strings.
Messy
Campos adapter
fromStatsBomb.shots(raw, matchInfo)
Flips direction, standardises outcomes, filters own-goals and shootouts, resolves player labels.
One call
Typed data
ShotEvent[]
Drop into <ShotMap />, your own React view, a server-side report, or an analysis script.
Ready
Fastest onboarding

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.

Get it running

One import, one call per surface.

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");
What this adapter ships

The calls you can make today.

Shipped
events() returns Event[]

Every recognised match event in a canonical shape.

StatsBomb notes: Includes carries, pressures, recoveries, take-ons, and out-of-play cards.

Shipped
shots() returns ShotEvent[]

Just the shots, ready to plot.

Shipped
passes() returns PassEvent[]

Pass trajectories with start, end, and result.

Shipped
matchLineups() returns MatchLineups

Home and away team sheets with starters and bench.

Shipped
formations() returns FormationTeamData

Kickoff tactical shape for one side.

What you can build with this

Charts that drop straight onto this adapter's output.

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.

The coordinate inversion

Origin top-left becomes attacker-rightward.

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"
  }
}
Current scope

What's honest about this adapter today.

Event coverage

The widest event surface.

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).

Lineups and formations

Honest multi-payload stitching.

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.

Why this matters

Pressures and carries are real events.

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.

Lineage

What this adapter borrows from whom.

Concrete credits for the projects that did the underlying research or laid the reference tables we read from.

kloppy Python

Provider type-ID → canonical kind mapping, coordinate transformers, and direction-by-period normalisation patterns. Our reference for Opta, StatsBomb, Wyscout, Stats Perform, and Sportec.

statsbombpy Python

Reference for how raw StatsBomb event envelopes and lineup payloads are typically consumed.

mplsoccer Python

Pitch-standardisation conventions we cross-checked our canonical Campos coordinate frame against.

Raw example — what the payload looks like before and after
Raw nested event
{
  "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
      }
    ]
  }
}
Match info
{
  "id": 3773585,
  "homeTeam": {
    "id": 217,
    "name": "Barcelona"
  },
  "awayTeam": {
    "id": 211,
    "name": "Real Madrid"
  }
}
Canonical output
[
  {
    "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"
    }
  }
]
Compare providers