PitchKit
Data

Overview

Load real open football data in one call — and when not to bother.

@pitchkit/data-providers takes you from a match id to chart-ready data without a pipeline in between. It's a separate, optional package with no dependency on @pitchkit/core or @pitchkit/react — its only runtime dependency is csv-parse, for SkillCorner's CSV files.

npm install @pitchkit/data-providers
import { fetchMatchEvents, shots, isGoal } from "@pitchkit/data-providers/statsbomb";
import { Scatter, VerticalPitch } from "@pitchkit/react";

const events = await fetchMatchEvents(3943043); // Spain 2–1 England, Euro 2024 final
const spain = shots(events).filter((shot) => shot.team.name === "Spain");

<VerticalPitch type="statsbomb">
  <Scatter
    data={spain}
    x={(s) => s.x}
    y={(s) => s.y}
    fill={(s) => (isGoal(s) ? "orange" : "steelblue")}
  />
</VerticalPitch>;

That's the whole path. No adapter, no field mapping, no coordinate conversion.

You probably don't need this

PitchKit's layers take accessor functions, so they already read whatever shape your data is in — that's the point of the coordinates guide. If you have your own pipeline, point x/y at your own fields and skip this package entirely. Nothing in @pitchkit/react depends on it.

It exists for the cases where fetching real data is the friction: tutorials, prototypes, learning the API, and agent-assisted builds where a complete runnable example beats a schema description.

How the loaders are shaped

Four layers, each usable on its own:

LayerWhat it does
parse*Pure — already-loaded JSON in, typed objects out. No network.
load* / fetch*Fetch and parse in one call. load* takes any URL; fetch* builds the open-data URL for you.
SelectorsNarrow a mixed feed: shots(), passes(), carries(), ofType().
PredicatesCompose with .filter(): isGoal, isComplete, isCorner, isCross, …

Two principles run through all of it, and they're worth knowing before you read the provider pages:

The provider's data stays the provider's. Field names keep their original spelling, values keep their original strings — a StatsBomb outcome is "Off T", not a re-spelled "off-target". You can read StatsBomb's own spec alongside these types with no mapping table in between. The one exception is coordinates: location arrays are also surfaced as x/y/endX/endY, because that's what a PitchKit accessor wants.

Interpretation lives in functions, not fields. There's no invented complete: boolean on a pass — there's an isComplete(pass) you apply. The data stays a faithful record; the reading of it is opt-in and inspectable. It also covers what a per-event-type accessor structurally can't: a corner isn't a StatsBomb event type, it's a kind of pass, so it can only be a predicate.

Providers

SkillCorner also has its own pitch type. Its coordinates are metres from the centre spot, so <Pitch type="skillcorner"> plots them raw — no lifting, no conversion:

<Pitch type="skillcorner" dimensions={{ length: match.pitch_length, width: match.pitch_width }}>
  <Scatter data={frame.player_data} x={(p) => p.x} y={(p) => p.y} />
</Pitch>

Wyscout has its own pitch type too — a 0–100 percentage grid like Opta's, but with a different origin: top-left with y increasing downward, where Opta's is bottom-left with y increasing upward. <Pitch type="wyscout"> plots its coordinates raw:

<Pitch type="wyscout">
  <Scatter data={shots} x={(s) => s.x} y={(s) => s.y} />
</Pitch>

Licensing

This package ships no data — it fetches from whatever URL you give it. The default URLs point at each provider's own open-data repository, and every provider asks to be credited in anything you publish from their data:

  • StatsBomb — open-data is released under StatsBomb's own user agreement rather than an OSI licence. Read the usage terms before you rely on it.
  • SkillCorner — opendata is MIT-licensed, and SkillCorner ask for credit and a mention when you publish work built on it.
  • Wyscout — the Pappalardo et al. dataset is CC BY 4.0, the most permissive of the three; cite the paper in anything you publish. Its official release (figshare) has no per-match JSON file, so events are fetched from a community mirror that splits the same archive per match with no field renamed — the reference files (competitions, teams, players) come from figshare directly.

On this page