5.5 KiB
Writing Plugins
Plugins are small JavaScript modules that run inside CurseForge. They can change the DOM, patch methods at runtime, and save settings between sessions.
Plugin folder
Plugins live in the BetterCurseForge user directory:
%APPDATA%\CurseForge\BetterCurseForge\plugins\
Each plugin gets its own subfolder:
plugins/
└── compact-mode/
├── manifest.json # Required: Plugin metadata
├── index.js # Required: Entry script
└── styles.css # Optional: Companion stylesheet
The Open Plugins Folder button in the desktop app opens it.
Manifest (manifest.json)
manifest.json describes the plugin:
{
"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"
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Name shown in the management UI. |
version |
string |
Yes | Semantic version (e.g. 1.0.0). |
description |
string |
No | Short summary of what the plugin does. |
author |
string |
No | Author name or handle. |
main |
string |
No | Entry script. Defaults to index.js. |
styles |
string |
No | Stylesheet that's injected automatically when the plugin is enabled. Defaults to styles.css. |
Lifecycle
A plugin's script runs when the plugin is enabled. It gets the BetterCurseForge 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 STYLESHEETS
If your plugin folder has a styles.css (or the file named in "styles" in manifest.json), BetterCurseForge injects it into <head> when the plugin is enabled and removes it when the plugin is disabled. You don't need to inject or clean it up yourself.
:::
Plugin API
1. BetterCurseForge.injectCSS(css)
Injects CSS into <head>. Returns a function that removes the style tag:
const removeStyles = BetterCurseForge.injectCSS(`
.addon-row { padding: 4px 8px !important; }
`);
// Later in cleanup:
plugin.onDisable = () => {
removeStyles();
};
2. BetterCurseForge.settings
Reads and writes settings that are 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
Helpers for monkey-patching methods on objects. Each call returns a function that undoes the patch:
patcher.before(object, methodName, callback)
Runs your callback before the original method. The callback gets (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 original method. The callback gets (args, result) and can return a new value to replace the result:
const unpatch = BetterCurseForge.patcher.after(targetObj, 'getItemTitle', (args, result) => {
return `⚡ ${result}`;
});
plugin.onDisable = () => unpatch();
patcher.instead(object, methodName, callback)
Replaces the original method. The callback gets (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()
An event emitter shared across the app:
const onReady = () => console.log('CurseForge UI is fully loaded!');
BetterCurseForge.on('ready', onReady);
plugin.onDisable = () => {
BetterCurseForge.off('ready', onReady);
};
Example: Compact Mode
This plugin makes CurseForge's layout denser.
::: 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;
}
:::