Appearance
Plugin Structure
A complete reference for plugin file organization.
Folder Location
Plugins live in ~/.gemshell/plugins/:
~/.gemshell/plugins/
├── my-plugin/
│ ├── plugin.js # Manifest (required)
│ ├── style.css # Styles (optional)
│ ├── template.html # HTML template (optional)
│ └── script.js # JavaScript (optional)
└── another-plugin/
└── ...plugin.js (Required)
The manifest file defines your plugin:
javascript
module.exports = {
// Required
name: 'My Plugin',
version: '1.0.0',
// Optional metadata
author: 'Your Name',
description: 'What your plugin does',
gemshell: '>=0.6.0', // Minimum GemShell version
// Files to inject into the game
inject: ['style.css', 'template.html', 'script.js'],
// User-configurable settings
settings: [
{ id: 'enabled', type: 'checkbox', label: 'Enable', default: true }
],
// Build hooks (optional)
async onPreBuild(context, settings) { },
async onModifyAssets(context, settings) { },
async onBeforePackage(context, settings) { },
async onPostBuild(context, settings) { },
async onBuildError(context, settings) { }
};style.css (Optional)
CSS styles injected into <head>:
css
.my-plugin-element {
position: fixed;
z-index: 99999;
/* ... */
}template.html (Optional)
HTML with template syntax, injected into <body>:
html
{{#if enabled}}
<div class="my-plugin-element" id="my-element">
{{message}}
</div>
{{/if}}script.js (Optional)
JavaScript injected at end of <body>:
javascript
// GEMSHELL_SETTINGS is auto-injected with setting values
const { enabled, message } = GEMSHELL_SETTINGS;
if (!enabled) return;
// Your code here
const el = document.getElementById('my-element');Injection Order
Files are injected in this order:
- CSS into
<head>(styles load first) - HTML into
<body>(DOM elements created) - JS into
<body>(can access elements)
File Naming
You can name files anything, just reference them in inject:
javascript
inject: ['styles/main.css', 'ui/overlay.html', 'src/app.js']Paths are relative to your plugin folder.
Minimal Plugin
The simplest plugin needs only plugin.js:
javascript
module.exports = {
name: 'Hello',
version: '1.0.0',
async onPreBuild(context, settings) {
console.log('Building:', context.config.title);
}
};Full Example
my-fps-counter/
├── plugin.js
├── style.css
├── template.html
└── script.jsplugin.js:
javascript
module.exports = {
name: 'FPS Counter',
version: '1.0.0',
author: 'Developer',
description: 'Displays FPS in-game',
gemshell: '>=0.6.0',
inject: ['style.css', 'template.html', 'script.js'],
settings: [
{ id: 'enabled', type: 'checkbox', label: 'Show FPS', default: true },
{ id: 'position', type: 'select', label: 'Position',
options: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
default: 'top-left' }
]
};style.css:
css
.fps-counter {
position: fixed;
padding: 4px 8px;
background: rgba(0, 0, 0, 0.7);
color: #4ade80;
font-family: monospace;
font-size: 14px;
z-index: 99999;
}
.fps-counter.top-left { top: 10px; left: 10px; }
.fps-counter.top-right { top: 10px; right: 10px; }
.fps-counter.bottom-left { bottom: 10px; left: 10px; }
.fps-counter.bottom-right { bottom: 10px; right: 10px; }template.html:
html
{{#if enabled}}
<div class="fps-counter {{position}}" id="fps-display">-- FPS</div>
{{/if}}script.js:
javascript
const { enabled } = GEMSHELL_SETTINGS;
if (!enabled) return;
const display = document.getElementById('fps-display');
let frames = 0, lastTime = performance.now();
function update() {
frames++;
const now = performance.now();
if (now - lastTime >= 1000) {
display.textContent = frames + ' FPS';
frames = 0;
lastTime = now;
}
requestAnimationFrame(update);
}
update();