What a save holds

A save holds players, clubs and competitions across the game world. The available history varies by career; it is not a complete archive of every match or injury.

30 readers get at it. Each one returns a Table of records, and each one reads the save you point it at and nothing else.

People

players(), contracts(), suspensions(), staff(), staff_lists()

Players carry names, birth date and age, nationality, club, height, positions, 52 attributes, the 8 personality attributes, current and potential ability, reputation, transfer value, condition, traits, contract and any unserved ban. contracts() is the same contract record as a table of its own, which is what you want when you are looking across every deal in the save rather than at one club’s players. Staff carry their people data, ability, personality and how they prefer a side to be run. Of the attributes a staff profile rates, fmsave currently reads only adaptability.

Clubs

clubs(), managed_clubs(), finances(), sponsorships(), facilities(), stadiums(), affiliates(), job_vacancies()

managed_clubs() is the club you run, and is empty between jobs. finances() is monthly figures per club, in the save’s own unit. sponsorships() includes historical contracts, so a row does not necessarily describe current income: an ended contract can keep a non-zero annual value, so filter on end against the save’s date. A running contract can still sit outside the screen’s sponsorship income. A contract’s length is total_value / annual_value years, not the span between start and end. stadiums() holds capacities and ownership; names are available for only some grounds.

Club data still has limits. The meaning of some sponsorship amounts is unconfirmed, and some finance histories still fail continuity checks. Passing the reader checks does not confirm every field’s meaning. See Trusting a number before using these fields in an analysis.

Nations

Player and club nation fields use ids within that save. nations() gives the matching game database id for looking up a nation in an external list. Join a nation field to Nation.nation_id, then use Nation.database_id as the key in your own nation-name map. Nation names are not stored in the save.

Competitions

stages(), competitions(), fixtures(), league_tables(), competition_rules(), transfer_windows(), player_match_stats(), player_season_stats(), player_season_history()

Fixtures provide dates, teams, rounds and scores where fmsave can decode an unambiguous result.

league_tables() holds a table only once its clubs have played in it. Before a division’s first match it has no table for that division, although the game shows one with every club on zero, and fmsave export league-tables notes on standard error when it writes no table. Use fixtures() for the division’s clubs and matches until then. competition_rules() takes a rules block’s competition from the table stored after it, so before the season starts fewer blocks carry a competition_id.

player_season_stats() is each player’s current season, as the squad and player screens show it: one row per competition type and team, with counts such as minutes, xG, passes and tackles, and the per-90 and percentage figures the game shows. player_match_stats() reads individual match histories stored within player records. Coverage varies by save and player; it is not a complete career match log. Empty output passes validation when every player explicitly has no stored history. Incomplete or unresolved histories fail the reader checks.

Injuries

injury_types(), injuries()

injuries() reads retained injury history, not a complete career medical record.

Only a TYPED row, a recent or current episode, carries an injury type_id. A HISTORY row never does, so a save with no recent injuries has no types on any row. The names come from injury_types(), which the save keeps only in per-match files. On the saves measured, a new career and a pre-season save held none, so injury_types() was empty and type_name stayed None even where type_id was set; they appeared after the first competitive match.

Your own club’s work

training(), mentoring(), tactics(), set_pieces(), selections()

These readers return data for the club you manage. Other clubs return no rows. tactics() includes stored preset and alternate tactics; a row is a copy for one team, so the same tactic can appear more than once. selections() holds each team’s stored starters and substitutes. A starter’s slot is a tactic slot, but nothing stored says which tactic is selected, and the substitutes can keep players past the next match’s bench size.

Limits and missing values

Names from the installed game database. fmsave ships no competition, league, nation or city names and reads nothing from your game install. Some stadium names are available in the save. See Competition names below.

Unresolved links. Some rules blocks cannot be linked to a competition. A club’s home ground is inferred from fixtures where possible. Missing links remain None.

Unconfirmed meanings. Most tactic settings and roles have no confirmed labels; staff jobs are labelled on FM27 only. They keep their raw numbers. Other fields can have a value whose meaning is still unconfirmed; Trusting a number explains how to check.

Partial history. A missing score can mean the result was not retained, is not yet decoded, or could not be joined unambiguously. Injury history also has limits. Neither reader promises complete career coverage.

Unsupported fields. fmsave does not expose today’s availability, a full matchday lineup (selections() holds the stored picks), most staff attributes, club debt or asking prices. That does not establish whether the save stores them. Season statistics do include yellow and red cards; check each field’s status before using it.

Unreadable values remain None. Empty tables need context: the save may explicitly have no records, or the reader may have failed to recover them. Use validation and table coverage to check the distinction.

Competition names

Competitions carry a database_id, the id every outside name source is keyed on. Supply a map and fmsave fills in name and every denormalised competition_name:

import fmsave

competition_names = fmsave.read_competition_names("competition-names.csv")
with fmsave.open("career.fm", competition_names=competition_names) as career_save:
    for competition in career_save.competitions():
        print(competition.database_id, competition.name)  # 12345 "Example League"

The file is UTF-8, 2 columns, database_id then name; a leading header row is skipped. Any mapping of database id to name works in its place, and fmsave export --competition-names PATH takes the same file.

Without a matching map entry, name and competition_name remain None. Competitions without a database_id cannot use this map; competitions created during a career may have no entry in an outside name source.

Which reader has the field you want

The reference lists every record type and every field. If you are not sure a number means what you think it means, read Trusting a number first.

player_season_history() reads archived statistics for each player and team. It keeps the archive’s order and includes an unconfirmed candidate season year and competition reference. Imported history can omit detailed counts. See exporting for those limits and for the player-season-history CLI table.