The fluXis plugin system lets external assemblies extend the game without modifying core code. Plugins are most commonly used to add map importers — translation layers that convert beatmaps from other rhythm games (osu!, Quaver, StepMania) into the native fluXisDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/InventiveRhythm/fluXis/llms.txt
Use this file to discover all available pages before exploring further.
.fms format. Each plugin can also declare a settings panel that appears in the in-game settings overlay, and a persistent configuration file backed by an INI store. At startup, PluginManager scans several well-known locations for qualifying assemblies and loads every Plugin subclass it finds.
The Plugin Abstract Class
Every plugin begins by subclassing fluXis.Plugins.Plugin. The class is deliberately small — three abstract identity properties and three optional virtual methods:
AssemblyName and Hash are set to internal set — you do not assign them yourself. PluginManager populates them automatically when it loads the assembly, using the SHA-256 hash of the DLL file.Identity Properties
| Property | Type | Purpose |
|---|---|---|
Name | string | Human-readable display name shown in the settings overlay. |
Author | string | Name of the plugin’s author. |
Version | System.Version | Semantic version, e.g. new Version(1, 2, 0). |
AssemblyName | string | Set by PluginManager — the CLR assembly name. |
Hash | string | SHA-256 of the DLL, set by PluginManager for integrity tracking. |
The MapImporter Class
If your plugin adds support for a new map format, override CreateImporter() and return a subclass of fluXis.Import.MapImporter. The base class provides the full import infrastructure — temporary folder management, .fms package creation, database registration, and progress notifications — so your subclass only needs to implement the parsing logic.
Plugin Configuration with PluginConfigManager
To persist settings between sessions, create a config manager that extends PluginConfigManager<TLookup>, where TLookup is an enum that defines your setting keys. The base class wraps osu!framework’s IniConfigManager, so each plugin gets its own <ID>.config.ini file inside the shared plugins storage directory.
CreateConfig(Storage storage) so that PluginManager can initialise it at load time before CreateSettings() is ever called.
How PluginManager Loads Plugins
PluginManager is a regular osu!framework Component that runs three discovery passes in sequence when the game starts:
App-domain scan
Iterates every assembly already loaded into the current
AppDomain. It accepts assemblies whose name starts with fluXis (case-insensitive). This is how the built-in importers (fluXis.Import.osu, etc.) are picked up when built as part of the same solution.Run-folder scan
Searches the directory where the fluXis executable lives for files matching
fluXis.*.dll and loads each one via Assembly.LoadFrom.PluginManager reflects over its exported types, finds every concrete subclass of Plugin, creates an instance with Activator.CreateInstance, stamps AssemblyName and Hash, calls CreateConfig(pluginStorage), and adds the plugin to its internal list.
Real Example: The osu! Importer Plugin
TheOsuPlugin class is the reference implementation shipped with fluXis. It demonstrates all three extension points — identity, config, and settings — in a single concise class:
- OsuPlugin.cs
- OsuPluginConfig.cs
Other Built-in Importers
The pattern established byOsuPlugin is repeated identically across the other first-party importers. They differ only in their identity values, the config key enums, and which MapImporter subclass they return.
StepmaniaPlugin
Name: Stepmania Importer
Version: 1.1.0
Config key:
Version: 1.1.0
Config key:
GameLocation — path to the StepMania Songs directory.QuaverPlugin
Name: Quaver Importer
Version: 1.2.0
Config key:
Version: 1.2.0
Config key:
GameLocation — path to the Quaver install directory.OsuPlugin
Name: osu! Importer
Version: 1.2.0
Config keys:
Version: 1.2.0
Config keys:
GameLocation and SkipBackgrounds.Minimum Viable Plugin
The smallest possible plugin that compiles and loads correctly looks like this. It does nothing beyond registering itself, but it establishes the naming conventions and structure you need to build on:What happens if CreateImporter() returns null?
What happens if CreateImporter() returns null?
The
Importer property on the base Plugin class is annotated [CanBeNull] and defaults to returning null if CreateImporter() is not overridden. PluginManager still loads the plugin and registers it in the UI — it simply won’t appear in the file-association or drag-and-drop import pipeline. This is useful for plugins that provide only configuration or scripting extensions without a map importer.Where is the plugins data folder located?
Where is the plugins data folder located?
PluginManager calls storage.GetStorageForDirectory("plugins") to obtain a scoped storage object, where storage is the root fluXis data storage resolved via osu!framework’s dependency injection. On Windows this typically resolves to %APPDATA%/fluXis/plugins/. Config INI files and any plugin-written assets land inside this directory.Can a plugin ship multiple Plugin subclasses?
Can a plugin ship multiple Plugin subclasses?
Yes.
PluginManager iterates assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Plugin))), so every concrete Plugin subclass in an assembly is instantiated and registered independently. In practice, the built-in importers each ship exactly one plugin class per assembly.