2026-10-06 15:03:16 +00:00
# Writing Plugins
2026-10-25 13:03:15 +00:00
Plugins are small JavaScript modules that run inside CurseForge. They can change the DOM, patch methods at runtime, and save settings between sessions.
2026-10-06 15:03:16 +00:00
---
2026-10-25 13:03:15 +00:00
## Plugin folder
2026-10-06 15:03:16 +00:00
2026-10-25 13:03:15 +00:00
Plugins live in the BetterCurseForge user directory:
2026-10-06 15:03:16 +00:00
```text
%APPDATA%\CurseForge\BetterCurseForge\plugins\
```
2026-10-25 13:03:15 +00:00
Each plugin gets its own subfolder:
2026-10-06 15:03:16 +00:00
```text
plugins/
└── compact-mode/
├── manifest.json # Required: Plugin metadata
├── index.js # Required: Entry script
└── styles.css # Optional: Companion stylesheet
```
2026-10-25 13:03:15 +00:00
The **Open Plugins Folder** button in the desktop app opens it.
2026-10-06 15:03:16 +00:00
---
2026-10-25 13:03:15 +00:00
## Manifest (`manifest.json`)
2026-10-06 15:03:16 +00:00
2026-10-25 13:03:15 +00:00
`manifest.json` describes the plugin:
2026-10-06 15:03:16 +00:00
```json
{
"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"
}
```
2026-10-25 13:03:15 +00:00
### Fields
2026-10-06 15:03:16 +00:00
| Field | Type | Required | Description |
| :--- | :--- | :--- | :--- |
2026-10-25 13:03:15 +00:00
| `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. |
2026-10-06 15:03:16 +00:00
| `author` | `string` | No | Author name or handle. |
2026-10-25 13:03:15 +00:00
| `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` . |
2026-10-06 15:03:16 +00:00
---
2026-10-25 13:03:15 +00:00
## Lifecycle
2026-10-06 15:03:16 +00:00
2026-10-25 13:03:15 +00:00
A plugin's script runs when the plugin is enabled. It gets the `BetterCurseForge` API and a `plugin` context object:
2026-10-06 15:03:16 +00:00
```javascript
// 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
};
```
2026-10-25 13:03:15 +00:00
::: 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.
2026-10-06 15:03:16 +00:00
:::
---
2026-10-25 13:03:15 +00:00
## Plugin API
2026-10-06 15:03:16 +00:00
### 1. `BetterCurseForge.injectCSS(css)`
2026-10-25 13:03:15 +00:00
Injects CSS into `<head>` . Returns a function that removes the style tag:
2026-10-06 15:03:16 +00:00
```javascript
const removeStyles = BetterCurseForge.injectCSS(`
.addon-row { padding: 4px 8px !important; }
`);
// Later in cleanup:
plugin.onDisable = () => {
removeStyles();
};
```
---
### 2. `BetterCurseForge.settings`
2026-10-25 13:03:15 +00:00
Reads and writes settings that are saved to disk (`settings.json`):
2026-10-06 15:03:16 +00:00
```javascript
// 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`
2026-10-25 13:03:15 +00:00
Helpers for monkey-patching methods on objects. Each call returns a function that undoes the patch:
2026-10-06 15:03:16 +00:00
#### `patcher.before(object, methodName, callback)`
2026-10-25 13:03:15 +00:00
Runs your callback before the original method. The callback gets `(args)` :
2026-10-06 15:03:16 +00:00
```javascript
const unpatch = BetterCurseForge.patcher.before(console, 'error', (args) => {
console.warn('[Intercepted Console Error]', ...args);
});
plugin.onDisable = () => unpatch();
```
#### `patcher.after(object, methodName, callback)`
2026-10-25 13:03:15 +00:00
Runs after the original method. The callback gets `(args, result)` and can return a new value to replace the result:
2026-10-06 15:03:16 +00:00
```javascript
const unpatch = BetterCurseForge.patcher.after(targetObj, 'getItemTitle', (args, result) => {
return `⚡ ${result}` ;
});
plugin.onDisable = () => unpatch();
```
#### `patcher.instead(object, methodName, callback)`
2026-10-25 13:03:15 +00:00
Replaces the original method. The callback gets `(args, originalMethod)` :
2026-10-06 15:03:16 +00:00
```javascript
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()`
2026-10-25 13:03:15 +00:00
An event emitter shared across the app:
2026-10-06 15:03:16 +00:00
```javascript
const onReady = () => console.log('CurseForge UI is fully loaded!');
BetterCurseForge.on('ready', onReady);
plugin.onDisable = () => {
BetterCurseForge.off('ready', onReady);
};
```
---
2026-10-25 13:03:15 +00:00
## Example: Compact Mode
2026-10-06 15:03:16 +00:00
2026-10-25 13:03:15 +00:00
This plugin makes CurseForge's layout denser.
2026-10-06 15:03:16 +00:00
::: code-group
```json [manifest.json]
{
"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"
}
```
```javascript [index.js]
// 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');
};
```
```css [styles.css]
/* 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;
}
```
:::