A MagicMirror² module that cycles through MLB, NHL, NFL, NBA, World Cup soccer, and Olympic Ice Hockey scoreboards. Scores are fetched automatically from public APIs with sensible fallbacks.
- Features
- Requirements
- Installation
- Quick Start
- Configuration
- Playoff Bracket Screens
- Assets & Styling
- Data Sources
- Troubleshooting
- License
- Eight-league scoreboards: MLB (R/H/E linescore), WBC, NHL (goals & shots), NFL (quarter-by-quarter totals plus bye list), NBA (quarter/OT breakdown), World Cup soccer (half/extra-time clocks, stoppage time, and shootout indicators), Men's Olympic Hockey, and Women's Olympic Hockey.
- MLB/NHL/NBA Playoff Bracket screens: A full postseason bracket (Wild Card/Division Series/LCS/World Series for MLB; First Round through the Finals for NHL/NBA) plus a scrollable current-round series list with live status, next-game times, and series scores. See Playoff Bracket Screens.
- Automatic league rotation: Show a single league, a custom sequence, or all supported leagues with timed page flips.
- Flexible layout: Control columns, rows, or total games per page per league and scale everything with a single
layoutScalevalue. - Favorite team highlighting: Per-league highlight lists add a subtle accent to matching teams on scoreboards.
- Times Square-inspired font option: Apply the included font to scoreboard content while keeping the default MagicMirror header font.
- Width cap for MagicMirror regions: Keep headers and content aligned inside
middle_centeror other constrained regions.
- MagicMirror² v2.20.0 or newer.
- Node.js 18+ on the MagicMirror host (uses the built-in
fetch). - Optional: Team logo PNGs and the
TimesSquare-m105.ttffont (see Assets & Styling).
cd ~/MagicMirror/modules
git clone https://github.com/yourname/MMM-Scores.git
cd MMM-Scores
# No npm install required; the helper uses Node 18's global fetch.Place any custom logos or font files as described below, then add the module to your config/config.js.
Run the offline unit tests for shared league/date logic:
npm testUse this script to verify that every external API endpoint used by the helper is reachable from your host:
npm run test:apiThe command checks MLB, NHL (all fallback feeds), NFL, NBA, World Cup soccer, and both Olympic hockey ESPN endpoints, then exits non-zero if any connection fails.
Use this Olympic-focused diagnostics script to check provider reachability and print normalized men's/women's Olympic games for a target date:
npm run test:olympic -- 2026-02-11Validate bundled font/logo assets without hitting network APIs:
npm run check:assetsAdd this entry to config/config.js:
{
module: "MMM-Scores",
position: "middle_center",
config: {
league: "all", // "mlb", "nhl", "nfl", "nba", "worldcup", "olympic_mhockey", "olympic_whockey", array, or "all"
updateIntervalScores: 60 * 1000, // helper refresh frequency
rotateIntervalScores: 15 * 1000, // front-end page flip interval
layoutScale: 0.95, // scale everything uniformly
highlightedTeams_mlb: ["CUBS"],
highlightedTeams_worldcup: ["USA"],
highlightedTeams_olympic_mhockey: ["USA"],
highlightedTeams_oly_whockey: ["CAN"], // short alias also supported
maxWidth: "720px"
}
}By default the module cycles through every supported league. Supply a string, array, or comma-separated list to league/leagues to control the order.
Every option may be declared globally, as an object keyed by league ({ mlb: value, nhl: value, ... }), or with a per-league suffix (gamesPerColumn_nhl). When both exist, per-league values win.
| Option | Type | Default | Description |
|---|---|---|---|
league / leagues |
string | string[] |
"mlb" |
League(s) to display. Accepts "mlb", "wbc", "nhl", "nfl", "nba", "worldcup", "olympic_mhockey", "olympic_whockey", "mlb_playoffs", "nhl_playoffs", "nba_playoffs", or "all". Arrays define the rotation order. The three *_playoffs screens are not included in "all" — add them explicitly (see Playoff Bracket Screens). |
updateIntervalScores |
number |
60000 |
Milliseconds between helper fetches. Minimum enforced interval is 10 seconds. |
rotateIntervalScores |
number |
15000 |
Milliseconds between scoreboard page rotations. |
timeZone |
string |
"America/Chicago" |
Time zone used to decide the scoreboard date. Before the configured daily update cutoff (09:30 local for most leagues, 03:00 for Olympic hockey), scoreboards show previous-day final scores and then rotate to a current-day schedule screen; after the cutoff they show the current day's scoreboard. |
providerCacheMs |
number |
20000 |
Per-provider/per-date Olympic provider cache TTL in milliseconds (minimum 15000). |
requestTimeoutMs |
number |
15000 |
Maximum time in milliseconds for each helper HTTP request before it is aborted. |
lastGoodCacheMs |
number |
21600000 |
How long a last successful response can be reused if a provider fails; stale payloads are flagged for the UI. |
seasonalFiltering |
boolean |
true |
Enables automatic hiding for known seasonal windows such as the 2026 NHL Olympic break and Olympic scoreboard retirement. |
hideNhlDuringOlympics |
boolean |
true |
Hide NHL while the configured NHL break window is active. |
hideNhlFrom / hideNhlUntil |
string |
"2026-02-06" / "2026-02-24" |
Inclusive ISO dates for the NHL Olympic-break visibility window. |
hideOlympicsAfterEnd |
boolean |
true |
Hide Olympic hockey scoreboards after the configured Olympic end date. |
hideOlympicsFrom |
string |
"2026-02-24" |
ISO date when Olympic hockey scoreboards are hidden unless seasonal filtering is disabled. |
showProviderStatus |
boolean |
false |
Shows a compact source/updated/stale-data line above the scoreboards; stale fallback data is always indicated. |
scoreboardColumns |
number |
auto | Columns per page. Defaults to 2 for MLB (capped at 2) and 4 for NHL/NFL/NBA/World Cup/Olympic hockey. |
gamesPerColumn (scoreboardRows) |
number |
auto | Games stacked in each column (4 for all leagues unless overridden). |
gamesPerPage |
number |
derived | Override the total games per page; rows adjust automatically per league. |
layoutScale |
number |
1 |
Scales the entire module (clamped between 0.6 and 1.4). |
highlightedTeams_mlb |
string | string[] |
[] |
Team abbreviations to highlight. Also available as _nhl, _nfl, _nba, _worldcup, _olympic_mhockey/_oly_mhockey, _olympic_whockey/_oly_whockey. |
showTitle |
boolean |
true |
Toggles the module header (MLB Scoreboard, etc.). |
useTimesSquareFont |
boolean |
true |
Applies the Times Square font to scoreboard cards. |
maxWidth |
string | number |
"800px" |
Caps the module width and header alignment. Numbers are treated as pixels. |
- Per-league overrides: Append the league suffix (
_nhl,_nfl,_nba,_mlb,_worldcup,_olympic_mhockey,_olympic_whockey) toscoreboardColumns,gamesPerColumn, orgamesPerPageto change a single league's layout. - Object form: For
layoutScaleor highlight lists, you can pass an object withdefaultand per-league keys.
The module keeps an internal rotation list derived from league/leagues. It fetches games for every configured league on each helper poll and flips the front-end page every rotateIntervalScores milliseconds. Helper fetches run independently so one slow provider does not block the rest of the rotation.
All helper HTTP requests use requestTimeoutMs and validated HTTP status handling. When a provider fails, the helper reuses the most recent successful payload for that league until lastGoodCacheMs expires and marks the data as stale. Set showProviderStatus: true to show source/update metadata even when data is fresh; stale fallback data is shown automatically.
Seasonal filtering is configurable. For example, keep NHL and Olympic boards visible regardless of the built-in 2026 windows:
config: {
league: "all",
seasonalFiltering: false
}Or customize the dates:
config: {
hideNhlFrom: "2026-02-06",
hideNhlUntil: "2026-02-24",
hideOlympicsFrom: "2026-02-25"
}Highlight any number of teams per league using the appropriate _mlb, _nhl, _nfl, _nba, _worldcup, _olympic_mhockey (or _oly_mhockey), or _olympic_whockey (or _oly_whockey) suffix. Highlights apply to scoreboards.
Olympic hockey country mapping uses IOC-style 3-letter codes (CAN, USA, FIN, SWE, GER, SUI, CZE, SVK, LAT, DEN, FRA, ITA, JPN).
Three additional screens show a full postseason bracket instead of a day's scoreboard: mlb_playoffs, nhl_playoffs, and nba_playoffs. Like the scoreboards, each one is titled in the module header ("MLB Playoffs", etc., hidden with showTitle: false), and draws:
- A seven-column bracket (
WC · DS · LCS · WS · LCS · DS · WCfor MLB;R1 · R2 · CF · SCF/Finals · CF · R2 · R1for NHL/NBA), with the AL/West on the left and the NL/East on the right. Decided series dim the loser, live series turn both scores yellow, and undecided future rounds show the matchup already known from seeding with no scores ("TBD" when a team isn't known yet). - A single-column list of every series in the current round (the earliest round with an unfinished series, or the last round once everything is decided), each with a status line ("Game 2 · Tonight 7 PM", "Royals win 2-0", "Series tied 1-1", etc).
- On very narrow displays (under 200px wide) the bracket is dropped automatically and only the series list shows.
Add one or more to your rotation like any other league:
{
module: "MMM-Scores",
position: "middle_center",
config: {
league: ["mlb_playoffs", "nhl_playoffs", "nba_playoffs"],
rotateIntervalScores: 20 * 1000,
maxWidth: "800px"
}
}Notes:
- Before a league's postseason bracket exists, the screen shows a projected bracket built from regular-season standings/seeding (heading reads "Projected Wild Card Series" / "Projected First Round"), so the screen is never blank during the stretch run.
- Each playoff feed is cached in the helper for 120 seconds, or 30 seconds while any series has a live game, independent of
updateIntervalScores. - If a league has no postseason data at all (offseason with no cached standings yet), the screen shows a centered "No postseason data" message.
- The playoff screen is 3.5 NFL scoreboard cells wide (595px at the default scale) and scales with
layoutScalethe same way scoreboard cards do. A fixed-lengthmaxWidthsmaller than that still caps it.
MMM-Scores/
├─ MMM-Scores.js
├─ MMM-Scores.css
├─ playoff-bracket.css
├─ playoff-bracket-shared.js
├─ playoff-data-mlb.js
├─ playoff-data-nhl.js
├─ playoff-data-nba.js
├─ node_helper.js
├─ fonts/
│ └─ TimesSquare-m105.ttf
└─ images/
├─ mlb/
│ └─ ATL.png (etc.)
├─ nhl/
│ └─ BOS.png (etc.)
├─ nfl/
│ └─ kc.png (lowercase filenames)
├─ nba/
│ └─ ATL.png (etc.)
└─ oly/
└─ USA.png (Olympic/World Cup country flags, uppercase country code)
- Logos: Place transparent PNG logos named with the abbreviations used in-game data (
CUBS.png,NYR.png,kc.png,CHI.png, etc.). The module falls back to text when a logo is missing. Olympic men's/women's hockey and World Cup soccer read fromimages/oly/<CODE>.png(for exampleCAN.png,USA.png,SWE.png). - Font: Drop
fonts/TimesSquare-m105.ttfintofonts/. The CSS registers it with@font-face. - Styling tweaks: Override CSS variables in
MMM-Scores.cssor globally (e.g.,css/custom.css). Useful variables include--scoreboard-card-width-base,--scoreboard-team-font-base,--scoreboard-value-font-base,--scoreboard-gap-base, and--matrix-gap-base. - Asset diagnostics: Run
npm run check:assetsafter adding logos or fonts. The command verifies required folders/files and warns about case-colliding PNG names that can behave differently across filesystems.
Example:
:root {
--scoreboard-team-font-base: 30px;
--scoreboard-value-font-base: 34px;
--matrix-gap-base: 10px;
}Scoreboard data comes from league-specific feeds with fallbacks where needed.
- MLB scores:
https://statsapi.mlb.com/api/v1/schedule/games?sportId=1&hydrate=linescore(date based ontimeZone), filtered to MLB club-vs-club games only. International/WBC matchups are kept off the MLB scoreboard and only appear whenwbcis explicitly configured. - NHL scores: Prefers
statsapi.web.nhl.comendpoints with automatic fallbacks to the public scoreboard and REST feeds; the date adjusts for early-morning previous-day fetches. - NBA scores:
https://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboardfor the selected date. - World Cup soccer scores:
https://site.api.espn.com/apis/site/v2/sports/soccer/fifa.world/scoreboardfor the selected date; live cards show1H,2H,ET, match minutes, stoppage time such as45'+2', and shootout superscripts when provided by the feed. - NFL scores: Weekly schedules from
https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?dates=<YYYYMMDD>aggregated from Wednesday through Monday night; includes bye-week teams. - Men's Olympic hockey scores: Primary
https://site.api.espn.com/apis/site/v2/sports/hockey/mens-olympics/scoreboard?dates=<YYYYMMDD>with resilient provider-chain hooks (olympics.com, IIHF, TheSportsDB, Wikipedia/Wikidata finals) and last-good-data fallback. - Women's Olympic hockey scores: Primary
https://site.api.espn.com/apis/site/v2/sports/hockey/womens-olympics/scoreboard?dates=<YYYYMMDD>with the same provider-chain/fallback architecture. - MLB playoff bracket:
statsapi.mlb.comschedule/postseason-series endpoints for the bracket, plus the regular-season standings endpoint for wild-card-era seeding (falls back to a seed-projected bracket before the postseason schedule exists). - NHL playoff bracket:
api-web.nhle.com'splayoff-bracketandschedule/nowendpoints, with a standings-based first-round projection and a last-season bracket fallback in the offseason. - NBA playoff bracket: the NBA's live-data CDN bracket JSON (
cdn.nba.com, with an S3 mirror fallback), which also keeps last season's completed bracket visible in the offseason.
- Header font changes unexpectedly: Remove broad overrides like
.module.MMM-Scores * { font-family: 'Times Square' !important; }so the MagicMirror header keeps its default font. - Font not loading: Confirm
fonts/TimesSquare-m105.ttfexists and is readable. CSS references it withurl('/sitelet?url=https%3A%2F%2Fgithub.com%2Fmrjrask%2Ffonts%2FTimesSquare-m105.ttf'). - Logos missing: Ensure filenames exactly match the abbreviations used in game data (case-sensitive per league). Missing files fall back to text labels.
- "Cannot find module 'node-fetch'": Upgrade to Node.js 18+; the helper relies on the built-in
fetch. - CSS 404s for
/css/custom.css: Only referencecss/custom.cssif the file exists to avoid MIME errors. - Playoff screen always shows "Projected": That's expected once regular-season standings exist but the postseason schedule hasn't been published yet (or, for NHL/NBA, once the previous postseason has fully wrapped and next year's bracket isn't up yet). It switches to the real bracket automatically once the provider publishes it.
- Playoff screen shows "No postseason data": The helper couldn't reach any of that league's data sources (live bracket, standings-based projection, or previous-season fallback). Check
npm run test:api-style connectivity to the hosts listed in Data Sources.
MIT License. See LICENSE for full text.