Back to Blog
Green soccer stadium field viewed from the stands under bright lights
Tutorial
September 7, 2026
10 min read

How to Build a Sports Fixture Calendar for a Club Website

By SimpleCalendarJS Team

SimpleCalendarJS~18 KB gzipped · Zero dependencies · Any framework

Every sports club — from Sunday league football to competitive rugby, cricket, and hockey — needs a fixture calendar on their website. Members check upcoming matches, parents plan around away games, and opponents confirm kick-off times. The default path is a WordPress plugin like SportsPress or a spreadsheet embedded in an iframe. Both work until they don't: the plugin locks you into WordPress, and the spreadsheet looks amateur. A custom fixture calendar built with JavaScript gives you full control, works on any hosting, and ships far less code than you'd expect.

Why most club websites get fixtures wrong

Club websites typically handle fixtures in one of three ways, each with trade-offs:

WordPress + SportsPress is the most common. SportsPress is a capable plugin with league tables, player profiles, and fixture management built in. But it requires WordPress (with its ~1 MB of frontend JavaScript and CSS), regular updates, PHP hosting, and a database. For a club that just needs a fixture calendar on a static site or a modern framework like Next.js or Astro, it's overkill.

Embedded Google Sheets or iframes appear on smaller club sites. A volunteer pastes match dates into a spreadsheet and embeds it. The result: no mobile responsiveness, no filtering by team, no visual distinction between home and away games, and an aesthetic that screams "we didn't try."

Enterprise calendar components like FullCalendar or DHTMLX are built for complex scheduling — resource timelines, drag-and-drop, multi-calendar sync. They work, but the free tiers ship 45–200 KB of JavaScript for features a fixture calendar never uses.

ApproachBundle SizeHosting RequirementCustomisationCost
SportsPress (WordPress)~1 MB (WordPress total)PHP + MySQLTheme-dependentFree plugin, $5–30/mo hosting
Embedded Google Sheet~0 KB (iframe)AnyNoneFree
FullCalendar~45–200 KBAnyHigh (complex API)Free core / $480/dev/yr premium
SimpleCalendarJS~14 KBAnyFull (vanilla JS)Free personal / $49/yr or $199 lifetime commercial

The sweet spot for most clubs: a lightweight calendar library that renders fixtures in a clean month or week view, colour-codes home and away games, and lets you plug in any data source — a JSON file, a REST API, or a sports data provider.

Building the fixture calendar

Step 1: Set up the calendar

npm install simple-calendar-js
import SimpleCalendarJs from 'simple-calendar-js'; import 'simple-calendar-js/dist/simple-calendar-js.min.css'; const fixtureCalendar = new SimpleCalendarJs('#fixture-calendar', { defaultView: 'month', locale: 'en-GB', enabledViews: ['month', 'week'], fetchEvents: async (start, end) => { const res = await fetch( `/api/fixtures?from=${start.toISOString()}&to=${end.toISOString()}` ); const fixtures = await res.json(); return fixtures.map((match) => ({ id: match.id, title: formatFixtureTitle(match), start: new Date(match.kickoff), end: new Date(new Date(match.kickoff).getTime() + 90 * 60 * 1000), color: match.isHome ? '#2563eb' : '#7c3aed', })); }, onEventClick: (event) => { showMatchDetails(event); }, }); function formatFixtureTitle(match) { if (match.result) { return `${match.homeTeam} ${match.homeScore}${match.awayScore} ${match.awayTeam}`; } const time = new Date(match.kickoff).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', }); return `${match.homeTeam} vs ${match.awayTeam}${time}`; }

This gives you a month-view fixture calendar at ~14 KB gzipped. Home matches appear in blue, away matches in purple. Upcoming fixtures show "Team A vs Team B — 15:00"; completed matches show "Team A 2–1 Team B". Clicking any fixture opens its details.

Step 2: Structure the fixture data

Define a clear schema for fixtures. Whether you store them in a JSON file, a database, or fetch from an API, the shape stays the same:

const fixtures = [ { id: 'fix-001', homeTeam: 'Riverside FC', awayTeam: 'Borough United', kickoff: '2026-09-12T15:00:00Z', venue: 'Riverside Park', competition: 'League Division 2', isHome: true, result: null, homeScore: null, awayScore: null, }, { id: 'fix-002', homeTeam: 'Hilltop Rangers', awayTeam: 'Riverside FC', kickoff: '2026-09-19T14:00:00Z', venue: 'Rangers Ground', competition: 'County Cup Round 1', isHome: false, result: 'win', homeScore: 1, awayScore: 3, }, ];

For small clubs with fewer than 50 fixtures per season, a static JSON file committed to your repo works perfectly. No database needed. Update it after each match and redeploy — or fetch from a Google Sheet via a simple API if a non-technical volunteer manages the data.

Step 3: Add result colour-coding

Once a match is played, colour-code it by outcome so visitors can scan the season at a glance:

function getFixtureColor(match) { if (!match.result) { return match.isHome ? '#2563eb' : '#7c3aed'; // blue home, purple away } switch (match.result) { case 'win': return '#16a34a'; // green case 'loss': return '#dc2626'; // red case 'draw': return '#6b7280'; // grey default: return '#2563eb'; } }
#fixture-calendar { --uc-primary: #2563eb; --uc-bg: #ffffff; --uc-border: #e5e7eb; --uc-text: #111827; } @media (prefers-color-scheme: dark) { #fixture-calendar { --uc-primary: #60a5fa; --uc-bg: #1f2937; --uc-border: #374151; --uc-text: #f9fafb; } }

Green for wins, red for losses, grey for draws. The visual pattern tells the story of a season without reading a single word.

Step 4: Filter by team or competition

Most clubs run multiple teams — firsts, reserves, youth squads. Let visitors filter the calendar:

function filterFixtures(fixtures, filters) { return fixtures.filter((match) => { if (filters.team && filters.team !== 'all') { const isTeamMatch = match.homeTeam === filters.team || match.awayTeam === filters.team; if (!isTeamMatch) return false; } if (filters.competition && filters.competition !== 'all') { if (match.competition !== filters.competition) return false; } if (filters.homeAway === 'home' && !match.isHome) return false; if (filters.homeAway === 'away' && match.isHome) return false; return true; }); } document.getElementById('team-filter').addEventListener('change', (e) => { fixtureCalendar.refetchEvents(); });

Wire the filter dropdown to refetchEvents() and pass the selected filters in the fetchEvents callback. The calendar re-renders with only the relevant fixtures — no page reload.

Integrating with sports data APIs

If your club plays in a league covered by a data provider, you can pull fixtures automatically instead of entering them by hand.

async function fetchFromSportsAPI(teamId, season) { const res = await fetch( `https://api.football-data.org/v4/teams/${teamId}/matches?season=${season}`, { headers: { 'X-Auth-Token': process.env.FOOTBALL_DATA_API_KEY } } ); const data = await res.json(); return data.matches.map((match) => ({ id: String(match.id), homeTeam: match.homeTeam.shortName, awayTeam: match.awayTeam.shortName, kickoff: match.utcDate, venue: match.venue, competition: match.competition.name, isHome: match.homeTeam.id === teamId, result: mapResult(match, teamId), homeScore: match.score.fullTime.home, awayScore: match.score.fullTime.away, })); } function mapResult(match, teamId) { const ft = match.score.fullTime; if (ft.home === null) return null; if (ft.home === ft.away) return 'draw'; const homeWin = ft.home > ft.away; const isHome = match.homeTeam.id === teamId; return (homeWin && isHome) || (!homeWin && !isHome) ? 'win' : 'loss'; }

football-data.org offers a free tier with 10 requests/minute — enough for a club site that caches responses. Other providers like API-Football (via RapidAPI) and TheSportsDB cover more leagues but require paid plans for higher rate limits. Always cache API responses server-side to avoid hitting limits on every page view.

Comparison: fixture calendar approaches

FeatureSportsPress (WP)FullCalendarEmbedded SheetSimpleCalendarJS
Works without WordPressNoYesYesYes
Bundle size~1 MB (WP)~45 KB+0 (iframe)~14 KB
Mobile responsiveTheme-dependentYesNoYes
Home/away colour-codingBuilt-inCustomNoCustom
Multi-team filteringBuilt-inCustomNoCustom
API data integrationPluginCustomManualCustom
Result trackingBuilt-inCustomManualCustom
League table generationBuilt-inNoNoNo
Annual cost$0 plugin + hosting$0–$480/dev/yr$0Free personal / $49/yr or $199 lifetime commercial

SportsPress wins if you're already on WordPress and need league tables, player profiles, and fixture management in one package. FullCalendar wins if you need complex scheduling features. SimpleCalendarJS wins when you need a clean, fast fixture display that works on any stack without the overhead.

Generating .ics calendar feeds

Let visitors subscribe to fixtures in their personal calendar app — Google Calendar, Apple Calendar, or Outlook:

function generateICS(fixtures, teamName) { const lines = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', `PRODID:-//${teamName}//Fixtures//EN`, `X-WR-CALNAME:${teamName} Fixtures`, ]; fixtures.forEach((match) => { const start = new Date(match.kickoff) .toISOString() .replace(/[-:]/g, '') .replace(/\.\d{3}/, ''); const end = new Date(new Date(match.kickoff).getTime() + 90 * 60 * 1000) .toISOString() .replace(/[-:]/g, '') .replace(/\.\d{3}/, ''); lines.push( 'BEGIN:VEVENT', `UID:${match.id}@yourclub.com`, `DTSTART:${start}`, `DTEND:${end}`, `SUMMARY:${match.homeTeam} vs ${match.awayTeam}`, `LOCATION:${match.venue}`, `DESCRIPTION:${match.competition}`, 'END:VEVENT' ); }); lines.push('END:VCALENDAR'); return lines.join('\r\n'); }

Serve this at /fixtures.ics and link it from the calendar page. Parents and players subscribe once and every fixture — with venue and kick-off time — appears in their phone calendar automatically.

Summary

  • WordPress plugins like SportsPress work well if you're already on WordPress, but lock you into a CMS and ship ~1 MB of frontend code for features most club sites don't need
  • A static JSON file is enough for small clubs with under 50 fixtures per season — no database required
  • Colour-code fixtures by outcome (green wins, red losses, grey draws) and by home/away (blue home, purple away) so visitors can scan the season instantly
  • SimpleCalendarJS renders the entire fixture calendar at ~14 KB gzipped — versus 45–200 KB for FullCalendar or the full weight of WordPress
  • Add team and competition filters so multi-team clubs can show relevant fixtures without page reloads
  • Generate an .ics feed so members can subscribe to fixtures in Google Calendar, Apple Calendar, or Outlook

Sources & Further Reading

Research & References

Image Credits

All images free to use under the Pexels License.

Frequently Asked Questions

What is the best JavaScript library for a sports fixture calendar?

It depends on your needs. FullCalendar is popular but ships ~45 KB gzipped for the core alone, and premium plugins push that past 200 KB. SportsPress is WordPress-only. For a standalone fixture calendar on any stack — static site, React, Vue, or plain HTML — SimpleCalendarJS at ~14 KB gives you month, week, and day views with full control over how fixtures render.

Can I build a fixture calendar without WordPress?

Yes. WordPress plugins like SportsPress are convenient but lock you into a CMS. A vanilla JavaScript calendar library works on any website — static HTML, Next.js, Astro, Laravel, or a custom backend. You own the markup, the data format, and the hosting.

How do I display match results on a calendar?

Use the calendar's event rendering to show the result alongside the team names. When the match date has passed and a result exists, update the event title from 'Team A vs Team B — 15:00' to 'Team A 2–1 Team B'. Colour-code completed matches (green for wins, red for losses, grey for draws) so visitors can scan results at a glance.

How do I add fixture data from a league API?

Most sports data APIs (football-data.org, API-Football, TheSportsDB) return JSON with match dates, teams, and scores. Fetch the data in your calendar's event loader, map each fixture to an event object with start/end times, title, and metadata, and pass it to the calendar. Cache API responses server-side to avoid hitting rate limits.

Is FullCalendar free for a sports club website?

FullCalendar's core is free and open source (MIT license). But if you want drag-and-drop rescheduling or resource views (e.g. multiple pitches), you need the premium Scheduler plugin starting at $480/developer/year. For a fixture display calendar, the free core works — but it's heavier than necessary for the job.

How much does it cost to add a fixture calendar to a club website?

WordPress plugins like SportsPress are free but require WordPress hosting ($5–$30/month). Enterprise calendar components cost $480–$2,040+/year. Building with SimpleCalendarJS costs nothing for a personal or open-source club site, or $49/year ($199 lifetime) for commercial use — a fraction of the alternatives.

Add a calendar to your app today

Free for personal projects. $49/year or $199 lifetime per commercial project.