PitchKit
Data

Data providers

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>

Metrica's grid has Wyscout's orientation at a hundredth of the scale: 0..1 on both axes, origin top-left, y downward. <Pitch type="metrica"> plots it raw:

<Pitch type="metrica">
  <Scatter data={frame.players} x={(p) => p.x} y={(p) => p.y} />
</Pitch>

Impect's grid is SkillCorner's shape standardised to 105×68: metres from the centre spot, y to the left. <Pitch type="impect"> plots it raw, in either of Impect's two coordinate frames.

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; 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.
  • Metrica Sports — sample-data carries no licence file. Its README asks that anything public made from the data acknowledges Metrica Sports as the source.
  • Impect — open-data is not openly licensed. Its terms of use allow non-commercial analytical and research use only, forbid redistributing or altering the data, and require IMPECT to be credited, with their logo, in anything you publish.

On this page