Stations define the individual frequencies, services, or logical sectors within an FIR. They represent what can be called over the voice system (e.g. Delivery, Ground, Tower, Approach sectors), independently of how controllers log in on VATSIM.
Station configuration is stored in stations.toml or stations.json inside an FIR's dataset directory.
dataset/{FIR_CODE}/stations.{toml|json}
The file must contain a single top-level array named stations.
TOML
[[stations]]
# Station 1 definition
[[stations]]
# Station 2 definitionJSON representation:
- Station: A callable frequency/service (e.g.,
LOWW_TWR,LOWW_N1) - Position: A controller login that can cover one or more stations (e.g.,
LOWW_TWRorLOVV_N_CTR) - Coverage: Which position is currently handling a station and will receive calls for it
Each station entry describes one callable station.
| Field | Type | Required | Description |
|---|---|---|---|
id |
String | Yes | Globally unique station identifier (e.g., LOWW_TWR, LOVV_N1). Must start with the FIR's two-letter country code. |
parent_id |
String | No | Optional parent station ID, used to inherit coverage from another station. |
controlled_by |
Array of strings | If no parent_id is set |
Ordered list of Position IDs used for coverage resolution (highest priority first). Required unless parent_id is set. |
The following validation rules apply:
idmust be unique across the entire datasetcontrolled_by:- must contain at least one position if present
- must not contain duplicates
- If a station does not define
parent_id, it must definecontrolled_by - If a station defines
parent_id,controlled_bybecomes optional
The controlled_by field defines the coverage order for a station.
It is an ordered list of Position IDs, evaluated from start to end:
- The first online position in the list is considered to be covering the station
- If the highest-priority position goes offline, coverage falls back to the next one
- If multiple listed positions are online at the same time, only the first matching one is used
This field is used for coverage calculations.
The parent_id field allows a station to inherit coverage priority from another station.
Inheritance works by appending the resolved controlled_by list of the parent station to the station's own controlled_by list.
This process is transitive: if the parent station itself has a parent_id, its parent's coverage list is appended as well.
The effective coverage list for a station is resolved as follows:
- Start with the station's own
controlled_bylist (if defined) - If
parent_idis set:- Resolve the parent station's coverage list
- Append it to the station's list
- Repeat recursively until no
parent_idremains - The final list is evaluated in order for online positions
If a position ID appears multiple times in the final list (e.g. because it is listed in controlled_by for multiple stations in the inheritance chain), only the first occurrence is used.
When configuring stations, watch out for these issues:
- Order matters: put the position with the highest priority first
- Missing positions: make sure all positions referenced by
controlled_byexist - Circular inheritance: if a
parent_idpoints to a station already processed in the inheritance chain, the coverage resolution will stop at that point and the resulting coverage list will be incomplete
This example defines a station that is normally handled by one position, but can be covered by another if the primary controller is offline.
[[stations]]
id = "LOWW_TWR"
controlled_by = ["LOWW_TWR", "LOWW_E_TWR"]What this means in practice:
- If
LOWW_TWRis online, it covers the Tower station - If
LOWW_TWRgoes offline, butLOWW_E_TWRis online, coverage falls back toLOWW_E_TWR - If both are online,
LOWW_TWRhas priority overLOWW_E_TWRand thus covers the station
This example defines a station with coverage inheritance using a parent_id.
[[stations]]
id = "LOWW_DEL"
parent_id = "LOWW_GND"
controlled_by = ["LOWW_DEL"]
[[stations]]
id = "LOWW_GND"
parent_id = "LOWW_TWR"
controlled_by = ["LOWW_GND", "LOWW_W_GND"]
[[stations]]
id = "LOWW_TWR"
controlled_by = ["LOWW_TWR", "LOWW_E_TWR"]What this means in practice:
LOWW_DELis controlled by the same-named positionLOWW_DEL- Since
LOWW_DELhasLOWW_GNDas a parent, its coverage list is extended with the coverage list ofLOWW_GND(LOWW_GND,LOWW_W_GND) - Since
LOWW_GNDhasLOWW_TWRas a parent, its coverage list is extended with the coverage list ofLOWW_TWR(LOWW_TWR,LOWW_E_TWR) - The final coverage list for
LOWW_DELisLOWW_DEL,LOWW_GND,LOWW_W_GND,LOWW_TWR,LOWW_E_TWR - If no lower level position is online,
LOWW_DELwill be covered byLOWW_E_TWR
In this example, some positions appear both in a station and in its parent. This is allowed and expected. The final coverage list is deduplicated in a stable manner, meaning that the order of the remaining elements is preserved and only the first occurrence of each position is kept.
[[stations]]
id = "LOWW_W_GND"
parent_id = "LOWW_GND"
controlled_by = ["LOWW_W_GND", "LOWW_GND", "LOWW_TWR", "LOWW_E_TWR"]
[[stations]]
id = "LOWW_GND"
parent_id = "LOWW_TWR"
controlled_by = ["LOWW_GND", "LOWW_W_GND", "LOWW_E_TWR", "LOWW_TWR"]What this means in practice:
LOWW_W_GNDis controlled byLOWW_W_GND,LOWW_GND,LOWW_TWRandLOWW_E_TWRin that order- Since
LOWW_W_GNDhasLOWW_GNDas a parent, its coverage list is extended with the coverage list ofLOWW_GND(LOWW_GND,LOWW_W_GND,LOWW_E_TWR,LOWW_TWR) - The parent station's coverage is added to the end of the list, resulting in
LOWW_W_GND,LOWW_GND,LOWW_TWR,LOWW_E_TWR,LOWW_GND,LOWW_W_GND,LOWW_E_TWR,LOWW_TWR - After stable deduplication, the final coverage list for
LOWW_W_GNDisLOWW_W_GND,LOWW_GND,LOWW_TWR,LOWW_E_TWR - This ensures that coverage priority is properly maintained even if stations define overlapping coverage lists
In this example, some stations do not define their own coverage list, but instead rely entirely on their parents' coverage list.
[[stations]]
id = "LOVV_N5"
parent_id = "LOVV_N6"
[[stations]]
id = "LOVV_N6"
parent_id = "LOVV_N7"
[[stations]]
id = "LOVV_N7"
controlled_by = [
"LOVV_NU_CTR",
"LOVV_EU_CTR",
"LOVV_U_CTR",
"LOVV_N_CTR",
"LOVV_E_CTR",
"LOVV_CTR",
"LOVV_C_CTR",
]What this means in practice:
LOVV_N5has nocontrolled_bylist of its own, so its coverage list is determined entirely by its parentLOVV_N6LOVV_N6has no coverage list of its own either, but definesLOVV_N7as its parentLOVV_N7is controlled byLOVV_NU_CTR,LOVV_EU_CTR,LOVV_U_CTR,LOVV_N_CTR,LOVV_E_CTR,LOVV_CTR,LOVV_C_CTR- The final coverage list for
LOVV_N5(andLOVV_N6) isLOVV_NU_CTR,LOVV_EU_CTR,LOVV_U_CTR,LOVV_N_CTR,LOVV_E_CTR,LOVV_CTR,LOVV_C_CTR
{ "stations": [ { /* Station 1 definition */ }, { /* Station 2 definition */ }, ], }