Lifecycle Hooks
React to game events with lifecycle hooks
Hooks let your mod respond to game events. Register a callback function that will be called when the event occurs.
Mod context is preserved automatically. When you register a hook, the modding API captures your mod's identity at registration time. This means api.storage.get() and api.storage.set() work correctly inside hook callbacks, even though your mod's script has already finished executing by the time the hook fires.
Error isolation: If your callback throws an error, it is caught and logged ā other mods' callbacks for the same hook will still run. You will not crash the game.
Available Hooks#
onGameInit#
Called when the game initializes. Use this to set up your mod.
window.SubwayBuilderAPI.hooks.onGameInit(() => {
console.log('Game initialized!');
});onDayChange#
Called when the in-game day changes. Receives the new day number (1-indexed, matching the UI clock).
window.SubwayBuilderAPI.hooks.onDayChange((day) => {
if (day % 100 === 0) {
console.log(`Milestone: Day ${day}!`);
}
});onCityLoad#
Called when a city is loaded. Receives the city code.
window.SubwayBuilderAPI.hooks.onCityLoad((cityCode) => {
console.log(`Loaded city: ${cityCode}`);
if (cityCode === 'MTL') {
// Do Montreal-specific initialization
}
});onMapReady#
Called when the map is fully loaded. Receives the MapLibre map instance.
window.SubwayBuilderAPI.hooks.onMapReady((map) => {
console.log('Map is ready!', map);
// Access raw MapLibre instance
map.on('click', (e) => {
console.log('Clicked:', e.lngLat);
});
});onStationBuilt#
Called when a station is constructed from blueprint.
window.SubwayBuilderAPI.hooks.onStationBuilt((station) => {
console.log(`Station built: ${station.name}`);
});onStationDeleted#
Called when a station is deleted.
window.SubwayBuilderAPI.hooks.onStationDeleted((stationId, stationName) => {
console.log(`Station "${stationName}" (${stationId}) was deleted`);
});onRouteCreated#
Called when a new route is created.
window.SubwayBuilderAPI.hooks.onRouteCreated((route) => {
console.log(`New route: ${route.bullet} (${route.id})`);
});onRouteDeleted#
Called when a route is deleted.
window.SubwayBuilderAPI.hooks.onRouteDeleted((routeId, routeBullet) => {
console.log(`Route ${routeBullet} (${routeId}) deleted`);
});onTrackBuilt#
Called when blueprint tracks are constructed.
window.SubwayBuilderAPI.hooks.onTrackBuilt((tracks) => {
console.log(`${tracks.length} tracks constructed`);
});onBlueprintPlaced#
Called when the player places blueprint tracks (before construction).
window.SubwayBuilderAPI.hooks.onBlueprintPlaced((tracks) => {
console.log(`${tracks.length} blueprint tracks placed`);
});onDemandChange#
Called when demand/population data is loaded for a city. If you register this callback after demand data has already loaded, it fires immediately with the current data so your mod doesn't miss the event.
window.SubwayBuilderAPI.hooks.onDemandChange((popCount) => {
console.log(`Demand data loaded: ${popCount} commuter groups`);
});onTrackChange#
Called when tracks are added or removed.
window.SubwayBuilderAPI.hooks.onTrackChange((changeType, count) => {
console.log(`Tracks ${changeType}: ${count} tracks`);
// changeType is 'add' or 'delete'
});onTrainSpawned#
Called when a new train is generated.
window.SubwayBuilderAPI.hooks.onTrainSpawned((train) => {
console.log(`Train spawned on route ${train.routeId}`);
});onTrainDeleted#
Called when a train is removed from service.
window.SubwayBuilderAPI.hooks.onTrainDeleted((trainId, routeId) => {
console.log(`Train ${trainId} deleted from route ${routeId}`);
});onPauseChanged#
Called when the game is paused or resumed.
window.SubwayBuilderAPI.hooks.onPauseChanged((isPaused) => {
console.log(`Game ${isPaused ? 'paused' : 'resumed'}`);
});onSpeedChanged#
Called when game speed changes.
window.SubwayBuilderAPI.hooks.onSpeedChanged((newSpeed) => {
console.log(`Game speed changed to: ${newSpeed}`);
// newSpeed is 'slow', 'normal', 'fast', or 'ultrafast'
});onMoneyChanged#
Called when the player's money changes (from any source ā fares, construction, mods calling setMoney, etc.).
window.SubwayBuilderAPI.hooks.onMoneyChanged((newBalance, change, type, category) => {
console.log(`${type}: $${Math.abs(change)} (${category || 'general'})`);
console.log(`New balance: $${newBalance}`);
// type is 'revenue' or 'expense'
// category examples: 'construction', 'trainOperational', 'mod-setMoney'
});onGameSaved#
Called after the game is saved (manual or autosave).
window.SubwayBuilderAPI.hooks.onGameSaved((saveName) => {
console.log(`Game saved: ${saveName}`);
});onGameLoaded#
Called after a save is loaded. Fires every time a save is loaded, including when returning to the main menu and loading a different save. If registered after a save has already been loaded, fires immediately with the current save name so your mod doesn't miss the event.
window.SubwayBuilderAPI.hooks.onGameLoaded((saveName) => {
console.log(`Game loaded: ${saveName}`);
});onWarning#
Called when the game issues a warning (e.g., insufficient funds, track collision).
window.SubwayBuilderAPI.hooks.onWarning((warning) => {
console.log(`Warning [${warning.type}]: ${warning.message}`);
// warning.id - unique warning identifier
// warning.type - warning category
// warning.message - human-readable message
});onError#
Called when a recoverable error occurs. Useful for monitoring and debugging.
window.SubwayBuilderAPI.hooks.onError((error) => {
console.log(`Error [${error.type}]: ${error.message} (recoverable: ${error.recoverable})`);
// error.type - error category
// error.message - human-readable error description
// error.recoverable - whether the game can continue
});onGameEnd#
Called when exiting the game (returning to menu or closing). Use this to clean up resources.
window.SubwayBuilderAPI.hooks.onGameEnd(() => {
console.log('Game ending, cleaning up...');
// Clean up intervals, listeners, etc.
});Hook Summary#
| Hook | Parameters | Description | |||
|---|---|---|---|---|---|
onGameInit | none | Game initialization | |||
onDayChange | day: number | Day changed | |||
onCityLoad | cityCode: string | City loaded | |||
onMapReady | map: MapLibre | Map ready | |||
onStationBuilt | station: Station | Station constructed | |||
onStationDeleted | stationId: string, stationName: string | Station deleted | |||
onRouteCreated | route: Route | Route created | |||
onRouteDeleted | routeId: string, routeBullet: string | Route deleted | |||
onTrackBuilt | tracks: Track[] | Tracks constructed | |||
onBlueprintPlaced | tracks: Track[] | Blueprints placed | |||
onDemandChange | popCount: number | Demand data loaded (late-fire) | |||
onTrackChange | `type: 'add' \ | 'delete', count: number` | Tracks added/removed | ||
onTrainSpawned | train: Train | Train created | |||
onTrainDeleted | trainId: string, routeId: string | Train removed | |||
onPauseChanged | isPaused: boolean | Pause state changed | |||
onSpeedChanged | `speed: 'slow' \ | 'normal' \ | 'fast' \ | 'ultrafast'` | Speed changed |
onMoneyChanged | `balance: number, change: number, type: 'revenue' \ | 'expense', category?: string` | Money changed | ||
onGameSaved | saveName: string | Game saved | |||
onGameLoaded | saveName: string | Game loaded (late-fire) | |||
onWarning | warning: { id: string, type: string, message: string } | Game warning | |||
onError | error: { type: string, message: string, recoverable: boolean } | Error occurred | |||
onGameEnd | none | Exiting the game |
Late-fire hooks: onDemandChange and onGameLoaded fire immediately if registered after the event has already occurred, so mods that load late still get the data they need.