When you're planning Fantasy Premier League transfers, the fixture list is only useful if you can compare runs in a consistent way. The FPL API includes a difficulty rating for each side of every fixture. With those values, you can rank upcoming schedules, sanity-check a player's match list, or put together a small analysis script - no paid data feed and no API key required.
That said, the raw data still needs some care. The two difficulty fields come from different perspectives, team IDs need mapping to names, and blanks/double gameweeks can mess up any simple "total" view. Also, FPL doesn't publish how it calculates the rating, so it's better to treat the numbers as a tool for comparison - not as a guaranteed prediction model.
This guide covers where the difficulty data comes from, how the ratings relate to team strength fields, and how to build a Python table for each team's next five gameweeks. It also calls out the edge cases that tend to bite people when they turn this into "real" transfer decisions.
Where does FPL fixture difficulty data live?
FPL exposes fixture data through its free API. The fixtures endpoint gives you match details like the gameweek, the home/away team IDs, and a difficulty value for each side. The bootstrap-static endpoint includes the team list, so you can map IDs to short names like ARS or CHE.
No API key is required. But the API doesn't include a CORS header, which means a browser page generally can't call it directly from client-side JavaScript. A Python script (or any server-side process) is the straightforward option. Use a reasonable timeout, and set a User-Agent header; it makes requests easier to manage than "anonymous and unbounded."
The endpoints and fields to use
| Endpoint or data | Useful fields | What they represent |
|---|---|---|
fixtures/?future=1 |
team_h_difficulty, team_a_difficulty |
Difficulty from each fixture side's perspective |
fixtures/?event={gw} |
The same fixture fields | Fixtures for a specific gameweek |
bootstrap-static/ → teams |
id, short_name |
Maps team IDs to readable names |
element-summary/{player_id}/ → fixtures |
difficulty, is_home |
A player's fixtures, from that player's team's perspective |
A fixture record looks something like this (values shown as an example from a live response):
{
"id": 64,
"event": 7,
"kickoff_time": "2026-10-17T11:30:00Z",
"team_h": 9,
"team_a": 6,
"team_h_difficulty": 4,
"team_a_difficulty": 3,
"finished": false,
"started": false
}
The event field is the gameweek number. It can be null when the fixture exists but hasn't been assigned a gameweek yet. That matters if you're building a fixed "next five gameweeks" window: those null fixtures can't be placed reliably, so you should skip them (and ideally report how many you skipped).
Choosing the right endpoint
If you want a whole-league comparison, start with fixtures/?future=1, then filter to the gameweek range you care about. If you're looking at a single player's run, the element-summary endpoint is handy because it already presents the fixture list from that player's team's perspective.
If you use the player endpoint to cross-check your own results, compare the team, venue, gameweek, and difficulty together. It's a good way to catch an accidental home/away mix-up.
What do the FPL difficulty ratings mean?
FPL assigns a difficulty value to each side of a fixture. In the API response, team_h_difficulty is the rating for the home team's fixture, while team_a_difficulty is the rating for the away team's fixture. The scale is generally 1 to 5, where a higher number means a harder fixture. The exact values can shift between seasons.
The field names can be misleading if you read them as "how strong the team named in the field is." Instead, treat them as "how difficult the fixture is for that team in this matchup." For example, team_h_difficulty reflects the away opponent (not the home side's own quality).
The relationship to team strength fields
FPL doesn't document the difficulty formula. In a check of the 2026/27 API data on October 14, 2026, the fixture ratings matched the opposing team's venue-specific overall strength field for every fixture examined:
- The home team's difficulty matched the away opponent's
strength_overall_homevalue. - The away team's difficulty matched the home opponent's
strength_overall_awayvalue.
In that snapshot, Arsenal's strength_overall_home was 4 and strength_overall_away was 5. So when Arsenal was the away side, the home opponent's team_h_difficulty came out as 4; when Arsenal was the home side, the away opponent's team_a_difficulty came out as 5. In other words, despite the naming, the values were describing Arsenal as an opponent at each venue.
That's an observed relationship for that snapshot - not a published spec, and not a guarantee that the mapping will stay identical forever. FPL can update these ratings during a season, so pull the current fixture data when you run your script. In the same snapshot, some other team-strength fields (like strength and various attack/defence subfields) were empty or zero. Don't build your own model on fields that aren't populated in the current data.
What the number does not tell you
The difficulty rating is a single opponent-and-venue value. It doesn't break the fixture difficulty down separately for attackers vs defenders vs goalkeepers. A forward might care more about the opponent's defensive performance, while a defender might care more about the opponent's attack. Use FDR as a first-pass comparison, not as a player-specific forecast.
How do you build a next-five-gameweek table in Python?
The script below pulls the current team list and future fixtures, finds the next gameweek, and then prints each team's upcoming matches from that team's perspective. It ranks teams by average difficulty across fixtures that actually fall inside the five-gameweek window, and it also shows how many matches were included in that average.
Install requests if it isn't already available in your environment, then run the script from your terminal (or any Python-capable environment). Don't run it from a browser page.
import requests
API = "https://fantasy.premierleague.com/api/"
session = requests.Session()
session.headers["User-Agent"] = "fpl-fdr/1.0 (personal script)"
bootstrap = session.get(API + "bootstrap-static/", timeout=30).json()
teams = {team["id"]: team["short_name"] for team in bootstrap["teams"]}
next_event = next(
(event["id"] for event in bootstrap["events"] if event["is_next"]),
None,
)
if next_event is None:
raise RuntimeError("No next gameweek is currently marked in the API.")
horizon = range(next_event, next_event + 5)
fixtures = session.get(API + "fixtures/?future=1", timeout=30).json()
# Each entry is (gameweek, opponent, venue, difficulty), from this team's view.
runs = {team_id: [] for team_id in teams}
for fixture in fixtures:
event = fixture["event"]
if event not in horizon:
continue
home_id = fixture["team_h"]
away_id = fixture["team_a"]
runs[home_id].append(
(event, teams[away_id], "H", fixture["team_h_difficulty"])
)
runs[away_id].append(
(event, teams[home_id], "A", fixture["team_a_difficulty"])
)
ranked = []
for team_id, games in runs.items():
games.sort()
average = sum(game[3] for game in games) / len(games) if games else 99
fixture_list = " ".join(
f"{opponent}({venue}){difficulty}"
for _, opponent, venue, difficulty in games
)
ranked.append((average, len(games), teams[team_id], fixture_list))
print(f"Fixture difficulty, GW{horizon.start}-GW{horizon.stop - 1} (easiest first)")
for average, count, team, fixture_list in sorted(ranked):
print(f"{team:<4} avg {average:.1f} over {count}: {fixture_list}")
unscheduled = sum(1 for fixture in fixtures if fixture["event"] is None)
print(f"\nUnscheduled future fixtures (event is null): {unscheduled}")
The output is intentionally compact. TOT(A)3 means an away fixture against Tottenham with a difficulty rating of 3 from the team's perspective. ARS(H)2 means a home fixture against Arsenal rated 2. The average is just a plain arithmetic mean, not a weighted score and not an FPL points projection.
Why the script averages and counts fixtures
A five-gameweek window doesn't guarantee five matches for every team. A blank gameweek reduces matches for that club, and a double gameweek can increase them. If you compare raw totals, teams with fewer fixtures will look "easier" simply because there are fewer ratings contributing.
So the script ranks by average difficulty and also prints the fixture count. That makes the table easier to read. It doesn't make different fixture counts truly equivalent, though - a team with four matches just has fewer opportunities to score. Treat the count as part of the decision, not decoration.
A dated output check
In the API snapshot checked on October 14, 2026, the next gameweek was GW7. The first rows of the GW7-GW11 output included Coventry, Manchester City, and Tottenham at an average rating of 2.6 over five fixtures each. The ordering comes from live data; it isn't a permanent recommendation. Fixtures, ratings, and the "next" gameweek can all change over time.
A practical validation is to compare one team's output with a player's element-summary fixture list. In that same snapshot, a Chelsea player's GW7-GW11 fixtures matched the CHE row: Everton away (3), Tottenham home (2), Manchester United home (4), Sunderland away (3), and Leeds home (3). If your venues or values differ, double-check that you used the correct side-specific difficulty field.
How should you interpret the fixture rankings?
A ranked table is a way to shrink a big fixture list into something you can actually use. It isn't a transfer instruction. The rating can help you spot a run worth a closer look, but the next question is whether the run matters for the specific player and the specific decision you're trying to make.
For example, an "easy" schedule might support a longer-term transfer if the player is likely to start and fits the role you expect. It's weaker evidence for a short-term move if minutes are uncertain, the team's attack is struggling, or you'd spend a transfer to make the change and take on the opportunity cost. The fixture number alone can't resolve those trade-offs.
A practical reading sequence
- Check the window. Confirm the gameweeks shown are the ones you meant to compare. "Next five" refers to five gameweek numbers, not necessarily five matches for each club.
- Check the fixture count. Look for blanks and doubles before comparing averages. A run with fewer matches can look attractive while offering fewer scoring chances.
- Inspect individual fixtures. A single tough match can hide inside an average that looks fine. Read the sequence, not just the rank.
- Match the rating to the player. Think about position, role, minutes, and how the player is likely to earn points. A single FDR doesn't separate defensive from attacking matchups.
- Compare with the cost of acting. A fixture run is one input into a transfer, captaincy, or bench choice. Weigh it against alternatives and the cost of using a transfer now.
That sequence is usually more reliable than treating the first row as an automatic list of targets. It keeps the data in its proper place: a filter to help you decide what to inspect, not an engine that tells you what to do.
What happens when there are blanks, doubles, or unscheduled matches?
A blank gameweek means there's no fixture row for that club in that gameweek. In the script, the average is computed from whatever fixtures are available, and the fixture count makes it visible that the club didn't get five match entries.
A double gameweek contributes two fixture rows, so a club can end up with more matches than the number of gameweeks in the window.
The script skips fixtures where event is null, because those games aren't assigned to a specific gameweek yet. It counts them at the end so they aren't quietly ignored. If you're planning around a postponed match, handle unscheduled fixtures separately rather than forcing them into an assumed gameweek.
What are the limits of FPL fixture difficulty?
The API gives you structured data, but it doesn't provide a full explanation of how FPL produces the ratings. In the cited 2026/27 snapshot, the relationship with venue-specific strength fields lined up cleanly - but that's evidence, not proof of the underlying process, and it doesn't guarantee the same mapping will hold in every season.
Also, the rating compresses multiple factors into one number. It doesn't separate attack vs defence difficulty, doesn't directly account for a player's minutes, and doesn't estimate expected points. A rating of 2 isn't a promise of returns, and a rating of 5 doesn't mean you should automatically bench the player.
Keep the data current and the model modest
Fetch fresh API data when you generate your table. Caching too long can leave you working with stale fixtures or outdated ratings, especially when match schedules shift. For a personal script, running a new request each time is often simpler than building a caching system. If you cache anyway, set an expiry and refresh when you need current decisions.
If you want something more player-specific, combine fixture difficulty with relevant performance indicators - like expected goals, expected assists, minutes, and the player's role. Keep the extra logic proportionate to the decision you're making. More columns don't automatically create a better forecast; they only help if their definitions are solid and you can explain how they affect the outcome.
Operational checks before relying on the output
- Check the response includes the fields your script expects; the API isn't formally documented and can change.
- Make sure
is_nextcorrectly identifies a gameweek before building the horizon. - Treat
event: nullas unassigned, not as "another normal gameweek." - Verify home/away values against a single team's fixture list or a player's
element-summary. - Keep the fixture count alongside any average/total.
- Remember that FDR is an input for analysis, not a points prediction.
These checks are small, but they prevent the most common failure mode: producing a neat table that ranks the wrong side of fixtures or makes blank weeks look more attractive than they really are.
Conclusion
FPL's fixture difficulty data is easy to pull, but using it well comes down to getting the perspective right and comparing like with like. Map team IDs to names, use the home/away difficulty fields for the corresponding team, and rank by average while showing the number of fixtures behind that average.
In the end, it's a practical way to scan upcoming runs - not a substitute for player-level analysis or a points forecast. Run it on current data, check blanks and doubles, and treat FDR as one input you can justify, not a decision you can outsource.
