Bondage Studio Bondage Studio
☰ Docs

Plugin API

Call window.bmm.api from a mod or plugin to read and control BMM — mods, registries, settings, UI, and events.

BMM exposes a stable, namespaced API at window.bmm.api for mods, plugins, and third-party code running on the same page. Use it to read and control mods, registries, settings and the UI, and to subscribe to lifecycle events.

For embedding BMM inside a host (a browser shell, an Electron client, or a reverse-proxy client) and intercepting its storage / network, see platform-integration.

Readiness

window.bmm.api is installed once BMM’s app shell has mounted, so it may be absent for a moment at startup. Guard for it, and poll or wait if you load early:

function withBmm(fn) {
  if (window.bmm?.api) return fn(window.bmm.api);
  const timer = setInterval(() => {
    if (window.bmm?.api) {
      clearInterval(timer);
      fn(window.bmm.api);
    }
  }, 50);
}

withBmm((api) => {
  console.log("BMM", api.version, "on", api.platform.name);
});

api.version is the API/bridge protocol version (a number). api.platform identifies where BMM is running:

interface BmmPlatformInfo {
  id: string;            // "browser", "studio-bondage-club", "electron-foo", ...
  name: string;
  version?: string;
  capabilities: string[];
  embedded: boolean;     // true when running inside a host (not a plain tab)
}

Mods

const api = window.bmm.api;

api.mods.list();                                // installed mods (BmmModInfo[])
api.mods.available();                           // every installable mod
api.mods.get(modId, registryId);               // BmmModInfo | null
api.mods.isInstalled(modId, registryId);
api.mods.isEnabled(modId, registryId);
api.mods.isLoaded(modId, registryId);

api.mods.install(modId, registryId, "stable"); // install + enable (version optional)
api.mods.enable(modId, registryId);
api.mods.disable(modId, registryId);           // needs a reload to fully unload
api.mods.remove(modId, registryId);
api.mods.setVersion(modId, registryId, "beta");

api.mods.progress();                            // ModLoadProgress snapshot

A mod is addressed by the pair (modId, registryId). Each entry looks like:

interface BmmModInfo {
  modId: string;
  registryId: string;
  name: string;
  author: string;
  enabled: boolean;
  loaded: boolean;
  selectedVersion: string;
  availableVersions: string[];
  tags?: string[];
  type?: string;
  sourceUrl?: string;
}

Disabling or removing an already-loaded mod needs a page reload to fully take effect — most BC mods can’t be unloaded once executed. BMM handles the reload when the mod manager closes (or defers to the host; see platform-integration).

Registries

api.registries.list();
api.registries.add("https://example.com/manifest.json"); // type defaults to "fusam"
api.registries.remove(registryId);
await api.registries.refresh();                           // re-fetch all manifests

Settings

api.settings.getAll();
api.settings.get("modCacheEnabled");
api.settings.set("modCacheEnabled", true);
api.settings.isLocked("modCacheEnabled");  // true when pinned by the host

When BMM runs inside a host that pins a setting, set() is a no-op for that key and isLocked() returns true. Check it before offering the user a toggle.

UI control

api.ui.open("mod-manager");        // open a page (defaults to "mod-manager")
api.ui.close();                    // close back to the launcher
api.ui.current();                  // active page name, or null
api.ui.setLauncherVisible(true);   // force the launcher on/off; null = auto-manage

Page names: "mod-manager", "registry-manager", "log-viewer", "settings".

Events

api.events.on(type, listener) returns an unsubscribe function:

const off = api.events.on("loadProgress", (p) => {
  console.log(`${p.loaded}/${p.total} loaded, ${p.errored} failed`);
});
// later: off();
EventPayloadFires when
readyBmmPlatformInfoThe API has been installed.
loadProgressModLoadProgressA mod’s load lifecycle changes.
modsChangedModConfig[]A mod is installed / enabled / version-changed / removed.
settingsChangedAppSettingsA setting changes.
pageChanged{ page }The active page changes (null = closed).
reloadRequested{ reason }BMM wants a reload but the host owns reloads.

Mod SDK

BMM owns and initializes the BC Mod SDK (window.bcModSdk). api.sdk hands you the authoritative instance — the same one every mod registers against, so all hooks and patches share one chain.

const sdk = api.sdk.get();          // ModSDKGlobalAPI | null
if (sdk) {
  const mod = sdk.registerMod({ name: "MyMod", fullName: "My Mod", version: "1.0.0" });
  mod.hookFunction("ChatRoomSendChat", 1, (args, next) => next(args));
}

// Convenience wrapper (returns the per-mod API, or null if the SDK isn't ready):
const mod = api.sdk.registerMod({ name: "MyMod", fullName: "My Mod", version: "1.0.0" });

api.sdk.isHijacked();  // true if BC's bundled SDK loaded before BMM

Prefer api.sdk.get() over reading window.bcModSdk directly: BMM guarantees the instance is the authoritative one by the time the API is ready, and resolves the race with BC’s own bundled SDK. isHijacked() reports the rare case where BC’s SDK initialized first and couldn’t be replaced — hooks still work, but BMM’s crash diagnostics are reduced.

Debug report

Register a named section that appears in BMM’s debug report and log viewer (FUSAM-compatible with FUSAM.registerDebugMethod):

api.log.registerDebugMethod("My Plugin", () => {
  return `state: ${JSON.stringify(myPluginState)}`;
});

Notes

  • Mods and plugins share the page with BMM; the API is not a security boundary.
  • <script src> mod loads go through the browser directly and are not affected by a host’s fetch override.