mods

Registry of mods whose scripts executed without throwing.

api.mods. 2 members. The loader registers a mod after its script returns, so a mod is absent during its own script, and mods later in load order are absent until their scripts run. Hot reload empties the registry before the scripts re-execute.

MemberReturnsSummary
isLoaded(modId)booleanWhether a mod id is in the loaded-mod registry.
getLoadedMods()LoadedModInfo[]A copy of every loaded mod's registry entry.

isLoaded#

api.mods.isLoaded(modId: string): boolean

Whether a mod id is in the loaded-mod registry.

A mod enters the registry after its script finishes without throwing. Disabled mods, mods with manifest errors and mods whose script threw are absent. During a mod's own script execution its own id is not yet registered.

Parameter modId (string): the id from the other mod's manifest.

Returns: boolean. true when registered; false otherwise, including when the lookup throws.

js
// optional integration with another mod
if (api.mods.isLoaded('com.example.better-fares')) {
    console.log('Better Fares found: skipping our own fare tweaks');
}

getLoadedMods#

api.mods.getLoadedMods(): LoadedModInfo[]

A copy of every loaded mod's registry entry.

Entries are added in load order after each script finishes without throwing.

Returns: LoadedModInfo[]. Array of fresh LoadedModInfo copies (editing them changes nothing); an empty array when the lookup throws.

js
for (const mod of api.mods.getLoadedMods()) {
    console.log(`${mod.name} ${mod.version} by ${mod.author} (${mod.id})`);
}

LoadedModInfo#

Registry entry for a loaded mod, copied from its scanned manifest.

The loader fills id, name and version from the manifest and author from author.name.

FieldTypeDefaultDescription
idstringrequiredManifest id; the key used by storage and by mods.isLoaded.
namestringrequiredManifest name, the display name.
versionstringrequiredManifest version.
authorstringnoneManifest author.name. Unknown when the manifest's author is missing or falsy (the scanner substitutes { name: 'Unknown' }); undefined when author is present but is not an object with name.

Load Order#

At startup the loader lists the folders in the mods directory, keeps the mods that are enabled and have a valid manifest, and runs their scripts one after another in directory listing order. The order is not configurable, and no manifest field declares dependencies.

A mod enters the mods registry only after its script returns without throwing. So at the top of a script, mods that ran earlier are visible and mods that run later are not, and a mod never sees its own id. Check for a dependency inside a hook instead. onGameInit fires only for new games, so also use onGameLoaded to cover save loads:

js
const checkDependency = () => {
    if (!api.mods.isLoaded('transit-data-core')) {
        api.ui.showNotification('This mod needs Transit Data Core.', 'warning');
    }
};
api.hooks.onGameInit(checkDependency);
api.hooks.onGameLoaded(checkDependency);

Hot reload (reloadMods) empties the registry and runs the scripts again in the same order.

Communicating Between Mods#

The API has no message channel between mods. Every script runs in the same page, wrapped in its own function scope, so a namespaced property on window is the simplest shared channel:

js
// Provider mod
window.MyStatsMod = {
    version: '1.0.0',
    getDailyRidership: () => api.gameState.getRidershipStats().totalRiders,
};

// Consumer mod, inside a hook so the provider has loaded
api.hooks.onGameInit(() => {
    if (api.mods.isLoaded('my-stats-mod') && window.MyStatsMod) {
        console.log(window.MyStatsMod.getDailyRidership());
    }
});

storage is not a shared channel: while a mod's script or hook callback runs, calls use that mod's own file and ignore an explicit modId.