# 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: ```text %APPDATA%\CurseForge\BetterCurseForge\plugins\ ``` Each plugin lives in its own dedicated subfolder: ```text 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: ```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" } ``` ### 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: ```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 }; ``` ::: 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 `` 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 ``. Returns a cleanup function that unmounts the style tag: ```javascript 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`): ```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` Safe method monkey-patching for objects and functions: #### `patcher.before(object, methodName, callback)` Runs your callback before the target method executes. Receives `(args)`: ```javascript 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: ```javascript 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)`: ```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()` Application-wide event emitter: ```javascript 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 ```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; } ``` :::