Competition Statistics

A competition's season at a glance: a progress header, upcoming fixtures and results grouped by stage and round, player rankings, and season-wide statistics. Expanding a finished or live match shows its match stats and a goal timeline. Expanding an upcoming one shows both teams' form, a season comparison and the previous meeting this season. Odds, when enabled, sit on the match row itself.

All data comes from the Sports API's competition-by-id endpoint, the same one Standings reads. Names, crests, headshots and stage names come from the Search API in batches of 100 ids, so a league with 400+ players and teams costs a handful of parallel requests.

Import

import { CompetitionStatistics } from "fansunited-frontend-components";
import { CompetitionStatisticsProps } from "fansunited-frontend-core";

Required props

PropTypeDescription
entityIdstringCompetition ID, e.g. "fb:c:3".
sdkFansUnitedSDKModelSDK instance.
languageLanguageTypeDisplay language. Entity names are localized from the Search API's translations.

Optional props

PropTypeDescription
themeOptionsCustomThemeOptionsSee Theming.
seasonIdstringSeason override, e.g. "fb:c:3:2026/27". Defaults to the competition's current season.
stageIdstringScope matches and team statistics to one stage. Defaults to every stage.
sectionsCompetitionStatisticsSection[]Which sections render, in the order given: "header", "matches", "players", "season". Defaults to all four in that order. A repeated key renders once.
playerRankingsLayout"auto" | "grid" | "tabs"How the player rankings are laid out. "auto" (default) shows a grid of cards and switches to tabs when the container is narrower than 600px.
playerRankingsRowsnumberRows per player ranking before "Show all". Defaults to 5; clamped to 1–10.
showOddsbooleanShow odds on each upcoming and live match row. Defaults to false. See Odds.
oddsOddsConfigmode ("default" / "flash"), flashInterval, pollingInterval, operators, affiliateId, affiliateIds. The same config as CompetitorCard's odds.
callbacksCompetitionStatisticsCallbacksonAddToBetslip(selection), see Odds; onBroadcastClick(matchId), see TV broadcasts. See also Callbacks.
liveRefreshbooleanPoll live matches so their score, minute and stats stay current. Defaults to true.
refreshIntervalnumberLive poll interval in ms. Defaults to 30000.
matchCTACompetitionStatisticsLinkCTAWhere the "highest-scoring match" and "biggest win" cards link to.
teamCTACompetitionStatisticsLinkCTAWhere the team callouts (most wins, best attack, …) link to.
playerCTACompetitionStatisticsLinkCTAWhere a player in the rankings links to.
labelsCompetitionStatisticsLabelsOverrides for any user-facing string. See Labels.

Sections

SectionContents
headerCompetition crest and name, season, season dates, the next round, and how many matches have been played.
matchesUpcoming / Results tabs, a stage picker (multi-stage competitions only), a round picker, and matches grouped by day. Rows expand.
playersTop scorers, assists, goals + assists, clean sheets, most minutes and most cards. Up to 10 players each; tied ranks read 2=.
seasonPlayed / goals / home-win / both-teams-scored tiles, result split, goal timing by 15-minute period, goal lines (2+ / 3+ / 4+ goals), averages per match, total cards, team callouts and the two record matches.

Player rankings are hidden when the season has no player statistics yet.

Matches

Stages. In a competition with more than one stage — a regular season followed by championship and relegation groups, or a play-off — a row of stage pills sits above the round picker, styled like the Standings stage tabs, and the round picker lists only the selected stage's rounds. Stage names come from the Search API. The Upcoming tab opens on the stage of the next match, the Results tab on the stage of the latest one. A stage with a single round, such as a final, shows no round picker. Single-stage leagues show neither.

The competition payload tags few matches with their stage, and a single matchday can hold matches from several groups at once, so the widget assigns each match itself: to the stage whose standings list both teams and whose dates cover the kick-off, otherwise to the nearest stage by date.

Not played. A postponed, cancelled, interrupted or abandoned match is listed on the Results tab, in its own round and day, with the reason in place of the score. It doesn't expand.

Live matches

A live match's row has a red accent, a pulsing dot and the match minute, with the half-time score under the current one once it is known. Expanding it shows the running match stats and goal timeline. While any match is live, the widget polls that match's own event endpoint every refreshInterval, not the competition endpoint. A match that has kicked off but is still marked scheduled in the competition payload is picked up too. Nothing is polled when no match is live.

Odds

With showOdds, each upcoming match row shows 1X2 odds from one operator — the same strip, settings and behaviour as CompetitorCard. A live match shows in-play prices; pre-match prices are never shown on a match in progress. One request covers every match in the round on screen. When the odds API has no complete market for a match, its row shows none. In a narrow container (under 600px) the odds move to a second line under the teams.

callbacks.onAddToBetslip receives { matchId, market, pick, odd } when a visitor clicks a price. The widget owns no betslip, so pass this to push the selection into yours — see Betslip. The operator's own link still opens either way.

TV broadcasts

The widget holds no broadcast data. Pass callbacks.onBroadcastClick and every upcoming match shows a TV icon next to its kick-off time. A click calls the handler with the match id, and the row doesn't expand. Without the callback, no icon renders. Which channel shows the match, and what the click opens (a modal, your TV guide, a channel page), is up to you.

<CompetitionStatistics
  entityId="fb:c:3"
  sdk={sdk}
  language="en"
  callbacks={{
    onBroadcastClick: (matchId) => openTvSchedule(matchId),
  }}
/>

Link CTAs

interface CompetitionStatisticsLinkCTA {
  onClick?: (id: string) => void;
  url?: string | null;
  target?: "_blank" | "_self" | null;
}

url is a template: {id} is replaced with the clicked entity's URL-encoded id. {matchId}, {competitorId} and {athleteId} work as aliases on the matching CTA. With onClick the handler receives the id and the link's default navigation is prevented. Without either, the cards stay inert: no pointer cursor and no hover state.

<CompetitionStatistics
  entityId="fb:c:3"
  sdk={sdk}
  language="en"
  teamCTA={{ url: "/teams/{id}" }}
  playerCTA={{ onClick: (athleteId) => router.push(`/players/${athleteId}`) }}
/>

Labels

Every string falls back to the locale bundle (competitionStatistics.<key>), so override only what you want to reword. Keys carrying {{…}} are interpolated, so keep the placeholder. Count-dependent keys (e.g. matchesCount, unitWins) pick their plural form from the count.

<CompetitionStatistics
  entityId="fb:c:3"
  sdk={sdk}
  language="en"
  labels={{ leadersTitle: "Top performers", round: "Matchday {{round}}" }}
/>

Examples

Full widget

import { CompetitionStatistics } from "fansunited-frontend-components";

<CompetitionStatistics entityId="fb:c:3" sdk={sdk} language="en" />;

Fixtures only

<CompetitionStatistics
  entityId="fb:c:3"
  sdk={sdk}
  language="en"
  sections={["header", "matches"]}
/>

Season statistics first

<CompetitionStatistics
  entityId="fb:c:3"
  sdk={sdk}
  language="en"
  sections={["season", "players", "matches"]}
/>

With odds wired to the Betslip

import { CompetitionStatistics, betslipApi } from "fansunited-frontend-components";

<CompetitionStatistics
  entityId="fb:c:3"
  sdk={sdk}
  language="en"
  showOdds
  odds={{ operators: ["betano-bg"], affiliateId: "aff-123" }}
  callbacks={{
    onAddToBetslip: (selection) =>
      betslipApi.setSelection(`${selection.matchId}:${selection.market}:${selection.pick}`),
  }}
/>;

Did this page help you?