🚇/Docs

Development Tools

New in v1.0.0

Hot reload, TypeScript support, and debugging utilities

This guide covers development tools that make building mods faster and easier.

Hot Reload#

During development, you can reload all mods without restarting the game:

javascript
// Reload all mods (clears mod state and re-executes mod scripts)
await window.SubwayBuilderAPI.reloadMods();

What Gets Cleared#

Hot reload clears:

  • All registered callbacks (except mapReady - map is already loaded)
  • UI components
  • Custom layers, sources, and styles
  • Custom train types (keeps built-in heavy-metro and light-metro)
  • Custom cities (keeps built-in cities)

The mod loader then re-executes all enabled mod scripts.

Development Workflow#

  1. Make changes to your mod file
  2. Open browser console (F12 or Cmd+Option+I)
  3. Run await window.SubwayBuilderAPI.reloadMods()
  4. Your changes take effect immediately

<Callout type="tip"> Create a keyboard shortcut in your mod for quick reloading:

javascript
document.addEventListener('keydown', (e) => {
    if (e.key === 'F5' && e.ctrlKey) {
        e.preventDefault();
        window.SubwayBuilderAPI.reloadMods();
    }
});

</Callout>

TypeScript Support#

The API is fully typed. If using TypeScript:

typescript
import type { ModdingAPI } from '@/app/game/moddingAPI';

declare global {
    interface Window {
        SubwayBuilderAPI: ModdingAPI;
    }
}

// Now you get autocomplete!
window.SubwayBuilderAPI.registerCity({
    // TypeScript will validate this
});

Type Definitions#

The game exports all its types, so you can reference them in your mod:

typescript
import type { City, Station, Route, Train, TrainType, StationType } from '@/app/game/types';

<Callout type="info"> Remember: Mods run via new Function(), so ES6 import statements won't work in the mod script itself. Use TypeScript types only for development-time checking. </Callout>

Debugging#

Console Logging#

The Modding API logs all operations with a [Modding API] prefix:

javascript
// Check console for:
// [Modding API] City registered: BERLIN
// [Modding API] Hook registered: onDayChange
// [Modding API] UI component registered: my-stats-panel

Inspect Registered Content#

javascript
// List all registered cities
console.table(window.SubwayBuilderAPI.utils.getCities());

// Get current map instance
const map = window.SubwayBuilderAPI.utils.getMap();
console.log('Map loaded:', !!map);

// Check constants/game rules
const rules = window.SubwayBuilderAPI.utils.getConstants();
console.log('Starting money:', rules.STARTING_MONEY);

Inspect Game State#

javascript
const api = window.SubwayBuilderAPI;

// Stations
const stations = api.gameState.getStations();
console.log('Stations:', stations.length);
console.table(
    stations.map((s) => ({
        name: s.name,
        id: s.id
    }))
);

// Routes
const routes = api.gameState.getRoutes();
console.log('Routes:', routes.length);
routes.forEach((r) => {
    console.log(`Route ${r.id}: ${r.stationIds.length} stops`);
});

// Trains
const trains = api.gameState.getTrains();
console.log('Trains:', trains.length);

// Budget
const budget = api.gameState.getBudget();
console.log('Money:', budget.money);
console.log('Bonds:', budget.bonds);

Load City Data Files#

If you need to read `demand_data.json` or other city files inside a mod, do not use `fetch()` (Electron file:// paths and gz files won’t work reliably). Use api.utils.loadCityData() instead:

javascript
const api = window.SubwayBuilderAPI;
const cityCode = api.utils.getCityCode();

const demandData = await api.utils.loadCityData(`/data/${cityCode}/demand_data.json.gz`);
console.log('Pops:', demandData.pops.length);

Common Issues#

Mod Not Loading#

  1. Check the console for errors
  2. Verify manifest.json has correct id and main fields
  3. Ensure the mod is enabled in the Mods menu

Hook Not Firing#

  1. Verify the hook name is correct (e.g., onDayChange not ondaychange)
  2. Make sure the game state triggers your hook (e.g., onStationBuilt needs a station to be built)
  3. Check if an error in your callback is preventing execution

UI Not Appearing#

  1. Check that the placement ID is valid (e.g., 'settings-menu', 'escape-menu-buttons')
  2. Verify your component doesn't throw errors during render
  3. Use console.log in your component to verify it's being called

City Not Appearing#

  1. Ensure registerCity is called before the city select screen loads
  2. Check that data files exist in the correct location
  3. Verify coordinates are within valid ranges

Data File Validation#

The modding API exposes Zod schemas for validating city data files:

javascript
const api = window.SubwayBuilderAPI;

// Validate demand data
const result = api.utils.schemas.DemandDataSchema.safeParse(myDemandData);

if (result.success) {
    console.log('✅ Demand data is valid!');
} else {
    console.error('❌ Validation errors:', result.error.errors);
}

Available Schemas#

SchemaValidates
DemandDataSchemademand_data.json
BuildingIndexSchemabuildings_index.json (standard)
OptimizedBuildingIndexSchemabuildings_index.json (optimized)
RoadsGeojsonSchemaroads.geojson
RunwaysTaxiwaysSchemarunways_taxiways.geojson

Tips#

  1. Check API availability: Always check window.SubwayBuilderAPI exists before using
  2. Use hooks for timing: Register content in onGameInit to ensure proper loading
  3. Test incrementally: Add one feature at a time and verify it works
  4. Check console: Modding API logs all operations with [Modding API] prefix
  5. Cities need data: Custom cities require data files in public/data/{CODE}/