The PolymodHandler class is the central hub for Friday Night Funkin’s modding system, built on the Polymod framework. It handles mod discovery, loading, script initialization, and asset management.
Overview
PolymodHandler manages the complete mod lifecycle:
- Scans and validates mods against API version requirements
- Loads mod assets and scripts in priority order
- Configures sandboxing and security restrictions
- Hot-reloads mods during development
- Registers scripted classes and modules
API Version
public static var API_VERSION(get, never):String;
public static final API_VERSION_RULE:String = ">=0.8.0 <0.9.0";
The API version system ensures mod compatibility:
- API_VERSION: Returns the current game version (e.g., “0.8.0”)
- API_VERSION_RULE: Semantic versioning rule that defines compatible mod versions
- Mods must specify their
api_version in _polymod_meta.json
Loading Mods
Load All Mods
PolymodHandler.loadAllMods();
Loads all discovered mods from the mods folder, useful for development.
Load Enabled Mods
PolymodHandler.loadEnabledMods();
Loads only the mods enabled in the player’s save data. This is the default behavior when starting the game.
Load Specific Mods
PolymodHandler.loadModsById(["my-mod", "another-mod"]);
Loads an ordered list of mods by their IDs. Order matters - later mods can override earlier ones.
Load No Mods
PolymodHandler.loadNoMods();
Initializes Polymod without loading any mods. Still configures the framework.
Mod Discovery
Get All Mods
var allMods:Array<ModMetadata> = PolymodHandler.getAllMods();
for (mod in allMods) {
trace('Found mod: ${mod.title} v${mod.modVersion}');
trace(' ID: ${mod.id}');
trace(' Description: ${mod.description}');
}
Returns metadata for all mods in the mods folder, including disabled ones.
Get Enabled Mods
var enabledMods:Array<ModMetadata> = PolymodHandler.getEnabledMods();
Returns metadata only for mods that are currently enabled in save data.
Get Loaded Mod IDs
var loadedIds:Array<String> = PolymodHandler.loadedModIds;
List of mod IDs that were successfully loaded in the current session.
Hot Reloading
PolymodHandler.forceReloadAssets();
Force-reloads all mod assets and scripts from disk. Useful for rapid iteration during development:
- Clears all module and script caches
- Reloads all mods (uses
loadAllMods by default)
- Reloads all game registries (songs, characters, stages, etc.)
- Calls
onCreate on all modules
Hot reloading freezes the game briefly while assets are reloaded. It’s designed for development, not production.
Mod Lifecycle
When mods are loaded, PolymodHandler follows this sequence:
- Scan: Discovers all mods in the mods folder
- Validate: Checks API version compatibility
- Initialize: Configures Polymod with security rules
- Load Assets: Loads and merges mod assets (images, audio, data)
- Parse Scripts: Parses
.hxc scripted class files
- Register Classes: Registers scripted classes with the engine
- Load Modules: Instantiates module scripts
- Call onCreate: Dispatches CREATE event to all modules
Security & Sandboxing
PolymodHandler implements strict security rules to prevent malicious mods:
Blacklisted Classes
These classes cannot be imported in mod scripts:
Sys - System command execution
sys.* - File system access
cpp.Lib - Native library loading
haxe.Unserializer - Arbitrary class instantiation
polymod.* - Framework manipulation
- API classes that could enable cheating
Sandboxed Alternatives
Safe alternatives are provided:
// Restricted file access
Polymod.addImportAlias('funkin.util.FileUtil', funkin.util.FileUtilSandboxed);
// Read-only leaderboard access
Polymod.addImportAlias('funkin.api.newgrounds.Leaderboards',
funkin.api.newgrounds.Leaderboards.LeaderboardsSandboxed);
// Safe reflection utilities
Polymod.addImportAlias('Reflect', funkin.util.ReflectUtil);
Polymod.addImportAlias('Type', funkin.util.ReflectUtil);
Default Imports
Commonly used classes are automatically available:
import funkin.Assets;
import funkin.Paths;
import funkin.Preferences;
import funkin.util.Constants;
import flixel.FlxG;
Mod Folder Structure
mods/
└── my-mod/
├── _polymod_meta.json # Mod metadata (required)
├── _polymod_icon.png # Mod icon (optional)
├── data/ # Data files (JSON, text)
├── images/ # Image assets
├── sounds/ # Sound effects
├── music/ # Music tracks
├── songs/ # Chart data
└── scripts/ # Script files (.hxc)
Every mod requires a _polymod_meta.json file:
{
"title": "My Cool Mod",
"description": "Adds new content to the game",
"contributors": [
{"name": "YourName"}
],
"api_version": "0.8.0",
"mod_version": "1.0.0",
"license": "Apache-2.0"
}
Parse Rules
PolymodHandler configures how different file types are merged:
// Text files support line-based merging
output.addType('txt', TextFileFormat.LINES);
// Script files support plaintext merging
output.addType('hscript', TextFileFormat.PLAINTEXT);
output.addType('hxc', TextFileFormat.PLAINTEXT);
This allows mods to append to or modify existing game data.
Error Handling
Errors during mod loading are handled by PolymodErrorHandler:
- Script Parse Errors: Syntax errors in scripts show a popup
- Script Runtime Exceptions: Runtime errors show a popup
- Import Errors: Missing or blacklisted imports are reported
- Version Mismatches: Incompatible mods are skipped
Example: Checking Loaded Mods
import funkin.modding.PolymodHandler;
class MyScript {
public static function main() {
// Check what mods are loaded
trace('Loaded mods:');
for (modId in PolymodHandler.loadedModIds) {
trace(' - $modId');
}
// Get detailed info about all available mods
var allMods = PolymodHandler.getAllMods();
trace('\nTotal mods found: ${allMods.length}');
for (mod in allMods) {
var status = PolymodHandler.loadedModIds.contains(mod.id) ? "LOADED" : "DISABLED";
trace('[$status] ${mod.title} (${mod.id}) - v${mod.modVersion}');
}
}
}