events()
returns Event[] Every recognised match event in a canonical shape.
Wyscout's public dataset is tag-driven — events carry lists of numeric tags you have to interpret to get meaning. The adapter does the semantic work for you: interceptions are materialised from tag combinations, set-piece shots are dispatched from sub-event IDs, and duels collapse into the right canonical kind.
fromWyscout.shots(matchData, matchInfo) ShotEvent[] <ShotMap />, your own React view, a server-side
report, or an analysis script.
Wyscout public data carries a tags array on every event with numeric codes.
Interceptions, set-piece shots, clearance intents — you don't get them as their own event
types. The adapter materialises them from tag combinations so your chart code sees clean
canonical events.
import { fromWyscout } from "@withqwerty/campos-adapters";
const events = fromWyscout.events(matchData, matchInfo);
const shots = fromWyscout.shots(matchData, matchInfo);
const passes = fromWyscout.passes(matchData, matchInfo);
const lineups = fromWyscout.matchLineups(matchData, { players, teams }); events()
returns Event[] Every recognised match event in a canonical shape.
shots()
returns ShotEvent[] Just the shots, ready to plot.
Wyscout notes: Some placement inferred from tags; no xG in the public path.
passes()
returns PassEvent[] Pass trajectories with start, end, and result.
Wyscout notes: Result taxonomy still needs adapter-side review for PassSonar parity.
matchLineups()
returns MatchLineups Home and away team sheets with starters and bench.
Wyscout notes: Starters, bench, labels, and substitution metadata — no formation label, captain, shirt numbers, or coordinates yet.
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.
Interception detection (tag 1401) layers onto passes, duels, clearances, and touch events. Ground attacking duels with the right sub-event ID become take-ons rather than plain duels. Set-piece shots are dispatched from sub-event IDs even when the top-level event ID matches something else. This is the real work the Wyscout adapter does.
[
{
"kind": "pass",
"id": "2500040:241083854",
"matchId": "2500040",
"teamId": "1619",
"playerId": "383",
"playerName": null,
"minute": 0,
"addedMinute": null,
"second": 1,
"period": 1,
"x": 52,
"y": 48,
"provider": "wyscout",
"providerEventId": "241083854",
"sourceMeta": {
"eventId": 8,
"eventName": "Pass",
"subEventId": 85,
"subEventName": "Simple pass",
"tags": [
1801
]
},
"endX": 27,
"endY": 50,
"length": 25.08,
"angle": 3.0618,
"recipient": null,
"passResult": "complete",
"passType": "ground",
"isAssist": false
},
{
"kind": "shot",
"id": "2500040:241084564",
"matchId": "2500040",
"teamId": "1609",
"playerId": "49876",
"playerName": null,
"minute": 34,
"addedMinute": null,
"second": 45,
"period": 1,
"x": 74,
"y": 61,
"provider": "wyscout",
"providerEventId": "241084564",
"sourceMeta": {
"eventId": 3,
"eventName": "Free Kick",
"subEventId": 33,
"subEventName": "Free kick shot",
"tags": [
401,
1101,
201,
1215,
1802
]
},
"endX": 0,
"endY": 100,
"xg": null,
"xgot": null,
"outcome": "off-target",
"bodyPart": "left-foot",
"isOwnGoal": false,
"isPenalty": false,
"context": "direct-free-kick",
"goalMouthY": 40,
"goalMouthZ": 3.5
}
] The current Wyscout path is the public research dataset plus optional player and team lookups. It's a useful source for many pitch charts, but it's deliberately not framed as full official-API parity.
No per-shot xG in the public path. Pass result semantics still need adapter-side
normalisation review before PassSonar will claim full parity. Shot placement
is inferred from tags in several cases rather than observed end locations.
matchLineups() reads match.teamsData.formation with optional
player and team lookups. Delivers starters, bench, substitution metadata, and coarse
role codes. Formation labels, captain, shirt numbers, and coordinates are not in the
public source.
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.
SPADL canonical action taxonomy as a reference when we decided which Wyscout tag combinations collapse into which canonical event.
Pitch-standardisation conventions we cross-checked our canonical Campos coordinate frame against.
{
"id": 241084564,
"eventId": 3,
"subEventName": "Free kick shot",
"tags": [
{
"id": 401
},
{
"id": 1101
},
{
"id": 201
},
{
"id": 1215
},
{
"id": 1802
}
],
"playerId": 49876,
"positions": [
{
"y": 39,
"x": 74
},
{
"y": 0,
"x": 0
}
],
"matchId": 2500040,
"eventName": "Free Kick",
"teamId": 1609,
"matchPeriod": "1H",
"eventSec": 2085.050844,
"subEventId": 33
} {
"id": 241083854,
"eventId": 8,
"subEventName": "Simple pass",
"tags": [
{
"id": 1801
}
],
"playerId": 383,
"positions": [
{
"y": 52,
"x": 52
},
{
"y": 50,
"x": 27
}
],
"matchId": 2500040,
"eventName": "Pass",
"teamId": 1619,
"matchPeriod": "1H",
"eventSec": 1.9439610000000016,
"subEventId": 85
} {
"matchId": "2500040"
} [
{
"kind": "shot",
"id": "2500040:241084564",
"matchId": "2500040",
"teamId": "1609",
"playerId": "49876",
"playerName": null,
"minute": 34,
"addedMinute": null,
"second": 45,
"period": 1,
"x": 74,
"y": 61,
"provider": "wyscout",
"providerEventId": "241084564",
"sourceMeta": {
"eventId": 3,
"eventName": "Free Kick",
"subEventId": 33,
"subEventName": "Free kick shot",
"tags": [
401,
1101,
201,
1215,
1802
]
},
"endX": 0,
"endY": 100,
"xg": null,
"xgot": null,
"outcome": "off-target",
"bodyPart": "left-foot",
"isOwnGoal": false,
"isPenalty": false,
"context": "direct-free-kick",
"goalMouthY": 40,
"goalMouthZ": 3.5
}
]