Getting Started
In this tutorial we’ll build a Session Timer plugin — a simple timer extension that tracks session and encounter time. By the end, you’ll have a working plugin loaded in Overseer and know how to publish it to the marketplace.
Prerequisites: Node.js, npm, and Overseer installed.
Step 1: Find the Plugins directory
Open Overseer and go to Settings > Data and Storage. The Data directory path is shown at the top.
Default locations:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Overseer Studio/ |
| Windows | %APPDATA%\Overseer Studio\ |
| Linux | ~/.config/Overseer Studio/ |
Open that directory and look for a Plugins/ subdirectory. Create it if it doesn’t exist.
Step 2: Create your plugin directory
Inside Plugins/, create a directory for your plugin. Scoped directories keep plugins organized:
Plugins/@yourname/session-timer/Replace yourname with a handle that will become your scope in step 4.
Open that directory in your terminal for the rest of the tutorial.
Step 3: Set up the project
Initialize npm and install the SDK:
npm init -y
npm install --save-dev @overseer-studio/sdkAdd the overseer CLI to your npm scripts in package.json:
{
"scripts": {
"overseer": "overseer"
}
}Step 4: Log in and claim a scope
Log in to the marketplace:
npm run overseer -- loginThen claim your scope. A scope is a namespace prefix (@yourname) that makes your plugin IDs unique across all developers:
npm run overseer -- scope claim @yournameStep 5: Initialize the plugin
npm run overseer -- initThe prompt will show your claimed scope and ask for a plugin name and display details. After the prompts, it creates:
manifest.json— the plugin manifestextensions/— directory for your extensionspresets/— directory for presets (unused today)
Your directory should look like this:
@yourname/session-timer/
package.json
manifest.json
extensions/
presets/Step 6: Create the extension
An extension is the tile content — in this case, the timer UI. Create the extension directory and its files:
extensions/session-timer/
manifest.json
index.html
assets/
icon.pngextensions/session-timer/manifest.json
{
"id": "@yourname/session-timer",
"label": "Session Timer",
"description": "Track session and encounter time.",
"category": "Organization",
"version": "0.1.0",
"icon": { "$ref": "assets/icon.png" },
"source": { "$ref": "index.html" }
}extensions/session-timer/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Session Timer</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: system-ui, sans-serif;
background: transparent;
color: #e0e0e0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
gap: 20px;
}
#display {
font-size: 3rem;
font-variant-numeric: tabular-nums;
letter-spacing: 0.05em;
}
.controls { display: flex; gap: 8px; }
button {
background: #3f3f46;
color: #fafafa;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-size: 0.9rem;
cursor: pointer;
}
button:hover { background: #52525b; }
</style>
</head>
<body>
<div id="display">00:00:00</div>
<div class="controls">
<button id="toggle">Start</button>
<button id="reset">Reset</button>
</div>
<script>
let elapsed = 0;
let timer = null;
const display = document.getElementById('display');
const toggleBtn = document.getElementById('toggle');
const resetBtn = document.getElementById('reset');
function format(s) {
const h = String(Math.floor(s / 3600)).padStart(2, '0');
const m = String(Math.floor(s % 3600 / 60)).padStart(2, '0');
const sec = String(s % 60).padStart(2, '0');
return `${h}:${m}:${sec}`;
}
toggleBtn.addEventListener('click', () => {
if (timer) {
clearInterval(timer);
timer = null;
toggleBtn.textContent = 'Start';
} else {
timer = setInterval(() => {
elapsed++;
display.textContent = format(elapsed);
}, 1000);
toggleBtn.textContent = 'Pause';
}
});
resetBtn.addEventListener('click', () => {
clearInterval(timer);
timer = null;
elapsed = 0;
display.textContent = format(0);
toggleBtn.textContent = 'Start';
});
</script>
</body>
</html>assets/icon.png
Add a square PNG icon (at least 64×64 pixels) at extensions/session-timer/assets/icon.png. This appears in the tile picker.
Step 7: Update the plugin manifest
Open manifest.json (the plugin manifest at the root) and add a reference to the extension you just created:
{
"id": "@yourname/session-timer",
"name": "Session Timer",
"version": "0.1.0",
"author": "Your Name",
"extensions": [
{ "$ref": "extensions/session-timer/manifest.json" }
],
"presets": [],
"themes": [],
"locales": []
}Step 8: Test it
Restart Overseer. Create a tile, open the extension picker, and look under Organization. “Session Timer” should appear with your icon.
Select it. The timer loads inside the tile. Click Start to begin counting.
Plugin changes are picked up at startup. Restart Overseer whenever you change a manifest or replace asset files.
Troubleshooting
If the extension doesn’t appear:
- Validate your JSON — a missing comma or bracket will silently fail. Use a JSON validator.
- Check the icon path — the file referenced by
iconmust exist at the exact path relative to the extension manifest. - Restart Overseer — plugins load at startup only.
Step 9: Publish (optional)
Publishing makes your plugin available to others in the marketplace. It’s not required to use the plugin yourself — it’s already working locally.
Build the plugin:
npm run overseer -- buildThen publish:
npm run overseer -- publishThat’s it. When it finishes, you’ll see the public URL for your plugin.
For more detail on the publishing workflow — versioning, tokens, CI — see Publishing a Plugin.
What’s next
- Add configuration — let users customize the timer with a label or color. See Building Extensions.
- Add inter-extension events — broadcast timer events to other extensions. See the SDK Reference.
- Package themes, locales, or presets — see Themes, Locales, Presets.
- Look up every manifest field — see Extension Manifest and Plugin Manifest.