BetterCurseForge/docs/guide/plugins.md

5.7 KiB

Writing Plugins

BetterCurseForge features a lightweight plugin system that allows you to extend CurseForge Launcher functionality, manipulate the DOM, hook runtime methods, and persist configuration.


Plugin Directory Structure

Plugins are stored in the BetterCurseForge user directory:

%APPDATA%\CurseForge\BetterCurseForge\plugins\

Each plugin lives in its own dedicated subfolder:

plugins/
└── compact-mode/
    ├── manifest.json       # Required: Plugin metadata
    ├── index.js            # Required: Entry script
    └── styles.css          # Optional: Companion stylesheet

You can open the plugins folder directly from the BetterCurseForge desktop GUI by clicking Open Plugins Folder.


Plugin Manifest (manifest.json)

The manifest.json file declares your plugin's metadata:

{
  "id": "compact-mode",
  "name": "Compact Mode",
  "version": "1.0.0",
  "description": "Reduces padding across mod lists, sidebar items, and modpack details for high-density browsing.",
  "author": "ModderDev",
  "main": "index.js",
  "styles": "styles.css"
}

Manifest Fields

Field Type Required Description
name string Yes Display name shown in the management UI.
version string Yes Semantic version string (e.g. 1.0.0).
description string No Brief summary of what the plugin accomplishes.
author string No Author name or handle.
main string No Entry JavaScript file. Defaults to index.js.
styles string No Companion stylesheet injected automatically on enable. Defaults to styles.css.

Plugin Lifecycle & Execution

Plugin scripts are executed when enabled, receiving the BetterCurseForge global API and a plugin context object:

// index.js
// BetterCurseForge passes (BetterCurseForge, plugin)
const { injectCSS, patcher, settings, on, off } = BetterCurseForge;

console.log('[MyPlugin] Plugin initialized!');

// Attach an onDisable cleanup handler:
plugin.onDisable = () => {
  console.log('[MyPlugin] Plugin disabled! Cleaning up...');
  // Remove any custom DOM elements, event listeners, or intervals here
};

::: tip AUTOMATIC STYLE INJECTION & REMOVAL If your plugin folder contains a styles.css (or file declared in "styles" in manifest.json), BetterCurseForge automatically injects it into <head> when your plugin is enabled and removes it when disabled! You do not need to manually inject or clean up companion stylesheets. :::


BetterCurseForge Plugin APIs

1. BetterCurseForge.injectCSS(css)

Injects custom CSS dynamically into <head>. Returns a cleanup function that unmounts the style tag:

const removeStyles = BetterCurseForge.injectCSS(`
  .addon-row { padding: 4px 8px !important; }
`);

// Later in cleanup:
plugin.onDisable = () => {
  removeStyles();
};

2. BetterCurseForge.settings

Stores and retrieves persistent settings saved to disk (settings.json):

// Retrieve a stored setting (or null if not set)
const isCustomFont = await BetterCurseForge.settings.get('compactMode.customFont');

// Store a setting
await BetterCurseForge.settings.set('compactMode.density', 'ultra');

3. BetterCurseForge.patcher

Safe method monkey-patching for objects and functions:

patcher.before(object, methodName, callback)

Runs your callback before the target method executes. Receives (args):

const unpatch = BetterCurseForge.patcher.before(console, 'error', (args) => {
  console.warn('[Intercepted Console Error]', ...args);
});

plugin.onDisable = () => unpatch();

patcher.after(object, methodName, callback)

Runs after the target method completes. Receives (args, result). You can return a modified value:

const unpatch = BetterCurseForge.patcher.after(targetObj, 'getItemTitle', (args, result) => {
  return `⚡ ${result}`;
});

plugin.onDisable = () => unpatch();

patcher.instead(object, methodName, callback)

Completely supersedes the target method. Receives (args, originalMethod):

const unpatch = BetterCurseForge.patcher.instead(targetObj, 'fetchData', (args, originalFn) => {
  console.log('Intercepting fetchData call...');
  return originalFn(...args);
});

plugin.onDisable = () => unpatch();

4. BetterCurseForge.on(event, callback) / .off() / .emit()

Application-wide event emitter:

const onReady = () => console.log('CurseForge UI is fully loaded!');

BetterCurseForge.on('ready', onReady);

plugin.onDisable = () => {
  BetterCurseForge.off('ready', onReady);
};

Complete Example: Compact Mode Plugin

This plugin adds a high-density layout mode to CurseForge.

::: code-group

{
  "id": "compact-mode",
  "name": "Compact Mode",
  "version": "1.0.0",
  "description": "Reduces margins and padding for high-density modpack browsing.",
  "author": "BetterCurseForge Team",
  "main": "index.js",
  "styles": "styles.css"
}
// Add compact layout class to the document root
document.documentElement.classList.add('bcf-compact-layout');

console.log('[Compact Mode] Activated');

// Clean up when toggled off
plugin.onDisable = () => {
  document.documentElement.classList.remove('bcf-compact-layout');
  console.log('[Compact Mode] Deactivated');
};
/* Automatically injected when plugin is enabled */
.bcf-compact-layout .tile-view-item {
  width: 150px !important;
  height: 240px !important;
}

.bcf-compact-layout .action-button-container {
  height: 48px !important;
  margin: 6px 0 !important;
}

.bcf-compact-layout .details-title {
  font-size: 13px !important;
}

:::