FBref adapter

FBref: Scorelines and result cards from schedule rows.

FBref's schedule export is clean enough to drive scoreline headers, result cards, and bump charts. The adapter parses their scoreline strings, detects shootouts and extra time, and handles status edge cases (postponed, cancelled, abandoned, awarded) consistently.

Input shape
A schedule row as a plain JS object.
Status
Narrow (scrape-backed)
Raw feed
FBref
Provider-shaped. Their coordinates, their qualifier codes, their outcome strings.
Messy
Campos adapter
fromFbref.matchSummary(row)
Flips direction, standardises outcomes, filters own-goals and shootouts, resolves player labels.
One call
Typed data
MatchSummary
Drop into <ShotMap />, your own React view, a server-side report, or an analysis script.
Ready
Scoreline parsing, done once

FBref's scoreline strings look simple — "2–0", "1–1 (4–3 pens)", "2–1 (aet)" — but if you parse them yourself every time, the edge cases will bite. The adapter handles shootouts, extra time, postponed, cancelled, abandoned, and awarded matches in one place.

Get it running

One import, one call per surface.

import { fromFbref } from "@withqwerty/campos-adapters";

const summary = fromFbref.matchSummary(scheduleRow);
What this adapter ships

The calls you can make today.

Shipped
matchSummary() returns MatchSummary

Scoreline, status, and team-level xG.

FBref notes: Handles shootouts, extra-time, abandoned/awarded/postponed.

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.

Edge cases it handles

The bits of FBref's schedule that are easy to get wrong.

{
  "shootout": {
    "score": "1–1 (4–3 pens)",
    "status": "finished",
    "resolvedIn": "shootout",
    "shootoutWinner": "home"
  },
  "aet": {
    "score": "2–1 (aet)",
    "status": "finished",
    "resolvedIn": "extra-time",
    "statusLabel": "AET"
  },
  "postponed": {
    "notes": "Match postponed",
    "status": "postponed"
  },
  "abandoned": {
    "notes": "Match abandoned",
    "status": "abandoned"
  },
  "awarded": {
    "notes": "Awarded to home side",
    "status": "awarded"
  }
}
Current scope

What's honest about this adapter today.

What it's great for

Scoreline headers, result cards, fixture lists.

Any UI that consumes a schedule — scoreline strips, league tables with xG differentials, bump charts that need round-level results — is a good fit for matchSummary().

What it doesn't do

No events, no shots, no lineups.

FBref has per-match detail pages, but the current adapter deliberately scopes to the schedule row — that's the surface most FBref pipelines actually stabilise against. Shot-level and lineup surfaces would require scrape-backed secondary endpoints.

Where the row comes from

Usually soccerdata; directly is fine too.

Most users read FBref schedules via soccerdata. The adapter takes the row shape as-is — the field names match soccerdata's output, which matches FBref's DOM.

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.

soccerdata Python

Scrape-backed narrow-adapter pattern; the schemas we read for Understat and FBref schedule and shot rows.

Raw example — what the payload looks like before and after
Raw schedule row
{
  "game_id": "cc5b4244",
  "league": "Premier League",
  "season": "2024-2025",
  "date": "2024-08-17",
  "home_team": "Manchester City",
  "away_team": "Chelsea",
  "home_xg": 2.1,
  "away_xg": 1.3,
  "score": "2–0",
  "attendance": "54 732",
  "venue": "Etihad Stadium",
  "notes": null
}
Canonical MatchSummary
{
  "matchId": "cc5b4244",
  "competitionLabel": "Premier League",
  "seasonLabel": "2024-2025",
  "kickoff": "2024-08-17T00:00:00Z",
  "status": "finished",
  "statusLabel": "FT",
  "venue": "Etihad Stadium",
  "attendance": 54732,
  "home": {
    "teamLabel": "Manchester City",
    "score": 2,
    "xg": 2.1
  },
  "away": {
    "teamLabel": "Chelsea",
    "score": 0,
    "xg": 1.3
  }
}
Compare providers