Skip to content
Tutorial

Build a league table widget

A step-by-step plan for an embeddable, accessible league table widget fed from a server-side cache.

FootballAPI.net editorial team

Written and reviewed by the organisation

Published 30 Oct 2026Updated 1 Nov 20265 min read

FootballAPI.net — Build a league table widget, illustrated by a floodlit stadium pitch

A league table widget is one of the most requested football features, and one of the best first projects with a football API. It uses a single feed, changes predictably and is easy to verify by eye. Done well, it can sit on a home page, a sidebar or a partner site and stay correct all season.

This tutorial describes the architecture and the rendering decisions rather than one framework's syntax, so you can apply it in React, a server-rendered template or plain HTML. It assumes you have a key and that access to the standings feed has been granted for your account.

Architecture

The widget has three parts. A server job fetches standings for your chosen league identifier and season, validates the response and stores it with a fetch time. A small endpoint on your server returns the stored table as compact JSON or pre-rendered HTML. The widget itself, running in the browser, requests that endpoint and renders the result.

The browser never sees your API key, and every widget view, on every page, reads the same cached data. That keeps request volume flat no matter how popular the widget becomes.

If you embed the widget on other sites, serve the endpoint with appropriate cache headers and cross-origin rules, and make sure the terms allow display on those sites before doing so.

  • Server job fetches and stores
  • Your endpoint serves the cached table
  • The widget renders, never holding the key

Fetching standings

Request the standings feed for the league identifier and season. Use the supplied position for ordering rather than sorting yourself, because competitions use different tie-breakers. Store each row with team identifier, name, played, won, drawn, lost, goals for and against, goal difference and points, plus any zone description.

Schedule the job daily, with an extra run after the last match on match days. Keep the previous table if a refresh fails.

Choosing columns

A compact widget needs only position, team, played, goal difference and points. A full table adds wins, draws, losses and goals. Decide based on where the widget sits: sidebars want compact, league pages want full. Offer both through a size option rather than building two widgets.

Use a tabular-figure font for numbers so columns align. Right-align numeric columns and left-align team names.

Crests

Add crests through FootballAPI.net's media route using each team's identifier. Keep them small, set explicit dimensions, and lazy load in long tables. Provide a text fallback so a failed image leaves a neat gap rather than a broken icon.

Remember that league logos are not offered, so the widget header should use the competition name in text.

Accessibility

Render a real table with a caption naming the competition and season, header cells with scope attributes, and team names as links to team pages. Indicate zones with a text label or an accessible icon, not colour alone. Ensure the widget is navigable by keyboard and that focus styles are visible.

On small screens, keep position, team and points visible and allow horizontal scrolling for the rest, with a visible hint.

  • Caption, header cells and scope
  • Zones not by colour alone
  • Keyboard and screen reader tested

Empty, stale and error states

Before a season starts, the table may be empty. Show a short message that the season has not begun. If the stored table is older than expected, show its fetch time prominently. If your endpoint fails, show a compact error with a retry button rather than a blank box.

Use a skeleton placeholder while loading, sized to the expected table, so the page does not jump when data arrives.

Highlighting a team

Club sites often want their own team highlighted. Accept a team identifier as an option and apply a subtle background to that row, with an accessible label such as your team. Do not rely on name matching, which fails after renames.

Performance

Pre-render the table HTML on the server if possible. It loads instantly and works without JavaScript. Hydrate only the interactive parts, such as a size toggle. Keep the widget's script small, since it may load on every page of the site.

Testing

Test with a full table, an empty pre-season table, a grouped competition with several tables, a team with a points deduction and a slow endpoint. Check on a narrow phone and a wide desktop. Compare the rendered positions with the supplied ones after every match day for the first few weeks.

A worked example

For league 1204, the server job runs each evening and after the final match on Saturdays. It stores twenty rows keyed on team identifier with the fetch time. The endpoint returns pre-rendered HTML for the compact size and JSON for the full size. A club site embeds the compact widget in its sidebar with its own team identifier highlighted, and the league page uses the full version with home and away splits behind a toggle.

When a refresh fails on a Saturday evening, the widget keeps showing the afternoon's table with its fetch time, and the next run corrects it. Nobody sees an empty box, and request volume stays at a handful of calls per day however many pages show the widget.

After a month, the team compares rendered positions with official tables each Monday and finds none out of order, which is exactly the confidence a widget like this needs before partners embed it.

Embedding on partner sites

If partners embed the widget, provide a single script tag or an iframe with a documented size, and version the script so updates do not break their layouts. Confirm the display terms cover partner sites first, and include a small attribution link back to your own site. Keep the endpoint rate-limited so a misbehaving partner page cannot overload your server.

Key takeaways

  • Fetch standings on the server and serve the widget from cache.
  • Trust supplied positions and build an accessible table.
  • Design empty, stale and error states from the start.

Request-budget planner

Estimate calls from one shared server-side poller. Browser visitors should read your cache, not the API.

Calls per day
1,800
Calls per month
54,000
Average rate
0.05 req/s

An estimate only. The average rate applies during active hours, not to bursts: the account safety ceiling is 100 requests per second. Inputs are bounded to 1–86,400 seconds, 0–24 hours, 0–100 endpoints and 0–31 days. Include other workers and detail requests in your forecast. Daily included successful requests reset at midnight UTC without rollover; metered extras cost €0.002 each. Successful API cache hits count too. See the current daily allowances.

Frequently asked questions

Can the widget call the API directly?

No. It should call your own endpoint, which serves cached standings fetched server-side.

How do I show crests?

Use the media route with each team's identifier, with explicit dimensions and a text fallback.

What about grouped competitions?

Render one table per group from the response, each with its own caption.

How often should the table refresh?

Daily, plus once after the last match on match days.

Sources and references

Use these implementation references alongside this guide. Current endpoint parameters and account settings take precedence over illustrative examples.