Creating Custom Databases
Custom databases of teams and players can be created and edited in-game using the built-in database editing features. Alternatievly, for users familiar with JSON, databases can be provided as database.json
files.
{ "display_name": "My Example Database", "players": [ ... ], "teams": [ ... ] }
This page describes the expected JSON schema for custom databases. The game will validate custom databases before loading them, and will report any errors preventing the database from being used.
Where are my databases?
The local folder where the game will load custom databases from depends on your OS*.
Windows
macOS
Linux
*Databases installed from Steam Workshop are installed in a separate location determined by Steam.
Every valid database consists of exactly one database.json
file in a unique folder placed directly in the custom databases folder listed above. For example, let's look at the below directory structure:
BangAverageFootball/ custom/ databases/ MyValidDatabase/ database.json MyInvalidDatabase1/ wip.json MyInvalidDatabase1.json
There is only one valid database here: MyValidDatabase
. It's the only database correctly formatted as a database.json
file within a subfolder of the custom databases folder. The others are not valid and will be ignored by the game.
You can have a look at an example of a small valid database in the _help/
folder in the custom databases directory. Copying the entire ExampleDatabase
folder into the custom databases directory will make it visible as a custom database in the game.
Uploading to Steam Workshop
Databases can be shared with others by uploading them to the Steam Workshop, as long as they:
- Are valid, with no warnings or errors.
- Include a
preview.png
file at least 512px x 512px.
Databases can be uploaded to Steam Workshop directly from the database management screen; no additional software is needed:
Object Schemas
Databases consist of two types of objects: players and teams. Each object type has its own schema which must be strictly adhered to; the game will reject database which deviate from this schema!
Player Schema
JSON Examples
{ "id": "EXAMPLE_PLAYER_10", "team_id": "EXAMPLE_TEAM_1", "shirt_number": 10, "info": { "first_name": "Eddie", "last_name": "Example", "skin": 5, "hair": "BROWN", "hairstyle": "SHORT", "facial_hair": "GOATEE" }, "positions": ["CM", "SC"], "stats": { "speed": 8, "control" 8, "passing": 7, "shooting": 9, "power": 7, "tackling": 3, "handling": 1, "agility": 7 } }
{ "id": "KELLY_SMITH_GK", "team_id": "WOODFORD_ROVERS", "shirt_number": 1, "info": { "first_name": "Kelly", "last_name": "Smith", "skin": 1, "hair": "BLACK", "hairstyle": "TIGHT_BUN", "facial_hair": "NONE" }, "positions": ["GK"], "stats": { "speed": 4, "control" 4, "passing": 7, "shooting": 3, "power": 8, "tackling": 2, "handling": 8, "agility": 7 } }
Object Fields
id
- string
A unique identifier for this player. Any string can be used as an id
(e.g. "731"
, "JANE_DOE"
, "HJ8DM3B"
etc.), but every player's id
must be unique.
team_id
- string
The id
for this player's team. Every player can have at most 1 team. If team_id
is null
, this player will be considered a Free Agent.
shirt_number
- integer
The shirt/squad number associated with this player. If this player's team_id
is null
, then shirt_number
can also be null
.
info
- object
first_name
, last_name
- string (max 20 chars each)
The first and last names for this player. A player can have a first name, a last name, or both, but not neither. If only one name is provided, the game will assume this player is mononymous (e.g. "Rivaldo", "Moses", "Cher").
skin
- integer
This player's skin tone, ranging from 1
(darkest) to 5
(palest).
hair
- string
This player's hair colour; see Hair Colors.
hairstyle
- string
This player's hairstyle; see Hairstyles.
facial_hair
- string
This player's facial hair; see Facial Hair.
positions
- array of string (see Positions)
This player's on-field position(s). Each player can have up to 3 positions, and must have at least 1.
stats
- object
This player's in-game stats. Each stat can range from 1-10 inclusive, and any combination of stats is permitted, no matter how silly.
Note that the player's average rating is automatically calculated by the game when the database is loaded, and is not provided by the database itself.
Team Schema
JSON Examples
{ "id": "EXAMPLE_TEAM_1", "name": "Example United FC", "short_name": "Example Utd", "first_xi": [ "EXAMPLE_PLAYER_1", "EXAMPLE_PLAYER_2", ... "EXAMPLE_PLAYER_10", "EXAMPLE_PLAYER_11" ], "formation": "4-4-2", "kits": { "home": { "style": "STRIPES", "shirt_primary": "BLUE_ROYAL", "shirt_secondary": "WHITE", "shorts": "BLUE_ROYAL", "socks": "WHITE" }, "away": { "style": "SOLID", "shirt_primary": "YELLOW", "shirt_secondary": "BLACK", "shorts": "BLACK", "socks": "YELLOW" } } }
{ "id": "WOODFORD_ROVERS", "name": "Woodford Rovers FC", "short_name": "Woodford", "first_xi": [ "KELLY_SMITH_GK", "VERONICA_MONTEIRO_CB", ... "CLARA_PEDERSEN_LM", "NICOLA_JENKINS_SC" ], "formation": [ [ [ 2, 1 ], [ 2, 0 ] ], [ [ 4, 0 ], [ 4, 0 ] ], [ [ 6, 1 ], [ 6, 0 ] ], [ [ 1, 3 ], [ 1, 2 ] ], [ [ 7, 3 ], [ 7, 2 ] ], [ [ 3, 5 ], [ 3, 4 ] ], [ [ 5, 5 ], [ 5, 4 ] ], [ [ 2, 8 ], [ 2, 7 ] ], [ [ 6, 8 ], [ 6, 7 ] ], [ [ 4, 10 ], [ 4, 9 ] ] ], "kits": { "home": { "style": "HOOPS", "shirt_primary": "RED", "shirt_secondary": "BLACK", "shorts": "WHITE", "socks": "BLACK" }, "away": { "style": "SOLID", "shirt_primary": "BLUE_LIGHT", "shirt_secondary": "BLUE_LIGHT", "shorts": "BLACK", "socks": "BLUE_LIGHT" } } }
Object Fields
id
- string
A unique identifier for this team. Any string can be used as an id
(e.g. "472"
, "EXAMPLE_UNITED"
, "P9IA12C"
etc.), but every team's id
must be unique.
name
- string (max length 23)
The full, unshortened name for this team (e.g. "Example United", "AFC Farnsby City" etc.).
short_name
- string (max length 15)
A shortened version of this team's name, more appropriate for informal reference or narrower UIs (e.g. "Example Utd", "Farnsby" etc.).
first_xi
- array of string (see Player id
)
The IDs of the 11 players who will start matches for this team by default, including one goalkeeper. Players are ordered back-to-front, right-to-left.
formation
- string or array of array (see Formation Nodes)
This team's default starting formation. This can still be changed in-game when using the team. A string preset can be used (see Formation Presets) or a custom formation can be defined by describing each 'node' which will be occupied by an on-field player. Note that if defining a custom formation:
- The goalkeeper is never included, and the formation should consist of exactly 10 nodes.
- Nodes are ordered back-to-front, right-to-left; the first node is the player closest to the goalkeeper (presumably a defender), and the last node is the player furthest upfield (presumably a striker).
kits
- object
This team's kits/strips/uniforms.
home
, away
- object
Each team must have two kits: a home kit and an away kit. For gameplay reasons, it is recommended that the away kit is significantly different visually from the home kit.
style
- string
One of "SOLID"
, "STRIPES"
or "HOOPS"
. Each kit style will appear differently in-game.
"SOLID"

"STRIPES"

"HOOPS"

Note that a plain/single-colour shirt can be achieved by making shirt_primary
and shirt_secondary
the same for any style.
shirt_primary
, shirt_secondary
, shorts
, socks
- string
The colour for each kit component. Kits can use any combination of colours, no matter how gaudy. See Kit Colors.
Other Data Types
Kit Colors
Hair Colors
Hairstyles
"BALD"

"BALDING"

"AFRO"

"AFRO_SHORT"

"BOB"

"BRAID_PONYTAIL"

"BRAIDS"

"BUN"

"BUZZ"

"FANCY"

"FLATTOP"

"LONG"

"MESSY"

"MOHAWK"

"PONYTAIL"

"QUIFF"

"SHORT"

"SHOULDER_LENGTH"

"TIGHT_BUN"

Facial Hair
"NONE"

"ANCHOR"

"CHINSTRAP"

"CIRCLE"

"FULL"

"GOATEE"

"MOUSTACHE_BIG"

"MOUSTACHE_SMALL"

"MUTTON_CHOPS"

"SHADOW"

"STUBBLE"

Positions
Each player can have up to 3 positions, and must have at least 1. There are no restrictions on position combinations, no matter how silly; you're very welcome to have a GK
/RB
/LM
maverick if you want.
"GK"
"RB"
"LB"
"CB"
"DCM"
"RM"
"LM"
"CM"
"ACM"
"SC"
Formation Presets
Formations which already exist as presets within the game can be used in custom databases simple by referring to a preset's identifier (all listed below):
"4-4-2"
"4-4-2 W"
"4-4-2 D"
"4-2-3-1"
"4-3-3"
"4-1-4-1"
"4-4-1-1"
"3-4-3"
"3-5-2"
Formation Nodes
Custom formations can be defined as arrays of Formation Nodes. Each Formation Nodes describes where a given outfield player will position themselves on the field. Players will position themselves based on the grid below (note that it is rotated 180° compared to the in-game formation editor):
RIGHT ← ← ← ← LEFT 0 1 2 3 4 5 6 7 8 0 DEFENCE 1 ↓ 2 ↓ 3 ↓ 4 ↓ 5 ↓ 6 ↓ 7 ↓ 8 ↓ 9 ↓ 10 ATTACK
Formation Nodes contain two separate positions: one describing where a player will be when their team is in possession, and another describing where they'll be when their team is out of possession (i.e. a more defensive position).
[ [ x1, y1 ], // Position when in possession. [ x2, y2 ] // Position when out of possession. ]
So for example, let's consider the following Formation Node for a left-back:
[ [ 8, 2 ], [ 6, 0 ] ]
In possession, this player will position themselves at [ 8, 2 ]
(i.e. x = 8, y = 2). This player is playing extremely wide; x = 8 is the left-most co-ordinate on the grid. y = 2 is about halfway between the centre backs and the midfield, so this player is presumably expected to provide support to wide midfielders.
Out of possession however, this player will position themselves at [ 6, 0 ]
(i.e. x = 6, y = 0). This is a narrower position than their in-possession position; x = 6 is much closer to the centre of defence (and the goal) and y = 0 is as close to the goal line as a player can be, so this player is clearly expected to drop much deeper when their team loses the ball.

The fact that players can have both an attacking position and a defensive position offers a lot of tactical flexibility and fluidity. However, bear in mind that a player will run between their two positions whenever the ball changes between teams, so a large distance between the positions will increase the risk that the player will be caught out of possession when their team loses the ball!
Typescript-style Schema
If you're familiar with Typescript and would prefer to understand database schemas as Typescript types, a rough set of types can be found below. Note that int
is not actually a valid Typescript type, but is used here instead of number
for clarity.
// The game uses colours from the AAP-64 palette. // See: https://lospec.com/palette-list/aap-64 type KitColor = | "BLACK" // #060608 | "BLUE_DARK" // #143464 | "BLUE_LIGHT" // #249fde | "BLUE_ROYAL" // #285cc4 | "BLUE_SLATE" // #849be4 | "BLUE_SLATE_DARK" // #588dbe | "BROWN" // #71413b | "BROWN_LIGHT" // #bb7547 | "GREEN_DARK" // #1a7a3e | "GREEN_FOREST" // #14a02e | "GREEN_KIWI" // #9cdb43 | "GREEN_LIGHT" // #59c135 | "GREEN_PINE" // #24523b | "GREY_DARK" // #6d758d | "GREY_LIGHT" // #b3b9d1 | "LAVENDER" // #b9bffb | "LAVENDER_LIGHT" // #e3e6ff | "MAROON" // #73172d | "ORANGE_DARK" // #fa6a0a | "ORANGE_LIGHT" // #f9a31b | "PINK_DARK" // #e86a73 | "PINK_LIGHT" // #f5a097 | "PURPLE" // #793a80 | "PURPLE_DARK" // #403353 | "PURPLE_LIGHT" // #bc4a9b | "RED" // #b4202a | "TURQUOISE_LIGHT" // #a6fcdb | "WHITE" // #ffffff | "YELLOW" // #fffc40; type Kit = { // Each kit style will appear differently in game: // * "SOLID": the primary shirt colour will fill the torso area, and the secondary shirt // colour will fill the sleeves. // * "STRIPES": the primary and secondary shirt colours will be arranged in vertical stripes. // * "HOOPS": the primary and secondary shirt colours will be arranged in horizontal stripes. // Note that a plain/single-colour shirt can be achieved by making `shirt_primary` and // `shirt_secondary` the same for any style. style: "SOLID" | "STRIPES" | "HOOPS"; shirt_primary: KitColor; shirt_secondary: KitColor; shorts: KitColor; socks: KitColor; }; type FormationNode = [ // Player grid position when team is in possession. [int, int], // Player grid position when team is out of possession. [int, int], ]; type FormationPreset = | "4-4-2" | "4-4-2 W" | "4-4-2 D" | "4-2-3-1" | "4-3-3" | "4-1-4-1" | "4-4-1-1" | "3-4-3" | "3-5-2"; // Formations can either use a preset already in the game, or define a custom formation by // describing each 'node' which will be occupied by a specific player. Note that if defining // a custom formation: // * The goalkeeper is never included, and the formation should consist of exactly 10 nodes. // * Nodes are ordered back-to-front, right-to-left; the first node is the player closest // to the goalkeeper (presumably a defender), and the last node is the player furthest // upfield (presumably a striker). type Formation = FormationPreset | Array<FormationNode>; type Team = { // Any string can be used to identify a team. // e.g. "472", "EXAMPLE_UNITED", "P9IA12C" etc. id: string; // The full name, unshortened of the team (max 25 chars). // e.g. "Example United", "Farnsby City" etc. name: string; // A shortened version of the team's name. // e.g. "Example "Example Utd", "Farnsby" etc. short_name: string; kits: { home: Kit; away: Kit; }, // The team's default starting formation. This can still be changed in-game when using the team. formation: Formation; // The IDs of the 11 players who will start matches for this team by default, // including one goalkeeper. Players are ordered back-to-front, right-to-left. first_xi: Array<string>; }; type HairColor = | "BROWN_LIGHT" | "BROWN" | "BROWN_DARK" | "BLACK" | "GREY" | "BLOND" | "GINGER" | "GREEN" | "BLUE" | "RED"; // In addition to these values, you can also use custom hairstyles from local files or Steam Workshop. // If the custom hairstyle isn't found by the game, the player will default to "BALD". type Hairstyle = | "BALD" | "BALDING" | "AFRO" | "AFRO_SHORT" | "BOB" | "BRAIDS" | "BRAID_PONYTAIL" | "BUN" | "BUZZ" | "FANCY" | "FLATTOP" | "LONG" | "MESSY" | "MOHAWK" | "PONYTAIL" | "QUIFF" | "SHORT" | "SHOULDER_LENGTH" | "TIGHT_BUN"; // In addition to these values, you can also use custom facial hair from local files or Steam Workshop. // If the custom facial hair style isn't found by the game, the player will default to "NONE". type FacialHair = | "NONE" | "ANCHOR" | "CHINSTRAP" | "CIRCLE" | "FULL" | "GOATEE" | "MOUSTACHE_BIG" | "MOUSTACHE_SMALL" | "MUTTON_CHOPS" | "SHADOW" | "STUBBLE"; type PlayerInfo = { // A player can have a first name, last name, or both, but not neither. If only one // is provided, the game will assume they are mononymous (e.g. "Rivaldo", "Moses", "Cher"). first_name?: string | null; last_name?: string | null; // Skin tone must be a integer from 1-5 (darkest to palest). skin: number; hair: HairColor; // Note that for both `hairstyle` and `facial_hair`, custom values can be used as long as // they are loaded correctly by the game (e.g. via Steam Workshop or local files). If the // game can't find the custom styles, the player will just have no hair or facial hair. hairstyle: Hairstyle; facial_hair: FacialHair; }; // Note that although the game will indicate formation positions with other position types // not listed here (e.g. "RWB", "LW"), they aren't actually eligible as player positions // (so for example, a "RWB" player should actually be "RB"/"RM"). type Position = | "GK" | "RB" | "LB" | "CB" | "DCM" | "RM" | "LM" | "CM" | "ACM" | "SC"; // Each player stat must be an integer and can range from 1-10 inclusive. Any combination of // stats is permitted, no matter how silly. // Note that the player's average rating is automatically calculated by the game when the // database is loaded. type PlayerStats = { speed: int; control: int; passing: int; shooting: int; power: int; tackling: int; handling: int; agility: int; }; type Player = { // Any string can be used to identify a player. // e.g. "731", "JANE_DOE", "HJ8DM3B" etc. id: string; // The player's names and appearance data. info: PlayerInfo; // A player with no `team_id` will be considered a Free Agent. team_id?: string | null; // `shirt_number` is only required if the player has a `team_id`. Free agents do not // require numbers (if one is provided, the game will ignore it). // Must be an integer if provided. shirt_number?: number | null; // Each player can have a maximum of 3 positions. positions: Array<Position>; stats: PlayerStats; }; type Database = { // An optional name for the database to display in menus e.g. "World Nations", "Retro Clubs" etc. // If `display_name` isn't provided, the game will instead show the name of the database folder. display_name?: string; // Used to decide which gender to use for non-English strings e.g. "jugador" or "jugadora". // Defaults to "male" if omitted. gender?: "male" | "female"; teams: Array<Team>; players: Array<Player>; };