SYS.PLUGIN_DOCS
> GITMANAGER — Plugin Development Guide
Everything you need to build a GitManager plugin: the chroma API, every extension point, and a complete working example.
Jump to section
What Plugins Can Do
ChromaLabs GitManager can be extended with plugins. A plugin can:
- >add tags/badges to repositories (sidebar + repo header) and to files (changes list + file explorer)
- >add buttons — repo actions in the header, and right-click actions on files
- >add custom tabs on every repository with fully custom content
- >open custom windows (modal panels) with any content
- >register file viewers and editors for specific file extensions in the Files tab
- >run git commands, read/write files inside repos (text and binary), and keep persistent per-plugin storage
Installing a Plugin
A plugin is a folder. Drop it into the plugins directory and reload:
- In GitManager: Settings → Plugins → Open plugins folder (%APPDATA%\ChromaLabs GitManager\plugins)
- Copy the plugin folder in (e.g. plugins\my-plugin\)
- Settings → Plugins → Reload plugins (or restart the app)
Plugins can be disabled per-plugin from Settings → Plugins without deleting them.
SECURITY NOTE
Plugins run inside GitManager with full access, exactly like any installed application. Only install plugins you trust.
Anatomy of a Plugin
my-plugin/
├── plugin.json required manifest
└── main.js entry scriptplugin.json:
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "What it does",
"author": "You",
"main": "main.js"
}- >
id— lowercase letters, digits, dashes/underscores. Must be unique. - >
main— a filename inside the plugin folder (no paths).
main.js runs once at load with one global: chroma, the plugin API. Everything you contribute is registered through it:
'use strict'
chroma.addFileBadge((file) =>
file.path.endsWith('.lua') ? { text: 'LUA', color: '#22d3ee' } : null
)Badges (Tags)
chroma.addRepoBadge((repo) => badge | null)
chroma.addFileBadge((file) => badge | null)- >
badge={ text: string, color?: '#hex', tooltip?: string } - >Repo badges show in the sidebar row and the repo header. File badges show on rows in the changes list and the file explorer.
- >Providers are called synchronously on render and must be fast; return
nullfor no badge. Exceptions are swallowed.
repo context:
{ id, name, nickname, path, groupId, branch, status }
// status (may be null):
// { branch, upstream, ahead, behind, files: [{path, kind, staged}], hasConflicts, ... }file context: { repoId, path, kind? } — path is repo-relative with forward slashes; kind (added/modified/…) is present for changes-list rows.
Custom Tabs
chroma.addTab({
id: 'dashboard',
title: 'Dashboard',
render(container, repo) {
container.innerHTML = '<div style="padding:16px">…</div>'
return () => { /* optional cleanup when the tab unmounts */ }
}
})The tab appears after the built-in tabs on every repository. render receives a plain DOM element — use any DOM technique you like. Return a cleanup function if you attach timers/listeners.
Custom Windows
chroma.openWindow({ title: 'Results', width: 640, render(container) { ... } })Opens a modal panel. Same render/cleanup contract as tabs.
File Viewers & Editors
chroma.registerFileViewer({
extensions: ['.md'],
canEdit: true,
render(container, ctx) {
// ctx: { repoId, path, content, save? }
}
})- >Takes over the preview pane in the Files tab for matching extensions (the user can always flip to the raw view with the Raw toggle).
- >
ctx.contentis the file text (empty string for binary files — read binary content yourself withchroma.readFileBinary). - >With
canEdit: true,ctx.save(newText)writes the file and refreshes. Binary editors should write withchroma.writeFileBinaryinstead.
Working with Repos and Files
const r = await chroma.git(repoId, ['log', '--oneline', '-n', '10'])
// -> { ok, stdout, stderr } (non-interactive; never prompts)
const text = await chroma.readFile(repoId, 'path/in/repo.txt')
await chroma.writeFile(repoId, 'path/in/repo.txt', 'new content')
const bytes = await chroma.readFileBinary(repoId, 'textures/thing.dds') // Uint8Array
await chroma.writeFileBinary(repoId, 'textures/thing.dds', bytes)
const entries = await chroma.listDir(repoId, 'subdir') // [{ name, dir }]
chroma.refreshRepo(repoId) // re-check git status after you changed filesAll paths are repo-relative and confined to the repository — a plugin cannot reach outside a repo through these calls.
Import/Export Dialogs
For touching files outside a repo, plugins go through the user:
const picked = await chroma.openFileDialog([{ name: 'Images', extensions: ['png', 'jpg'] }])
// -> { name, data: Uint8Array } | null
const savedPath = await chroma.saveFileDialog('texture.png',
[{ name: 'PNG', extensions: ['png'] }], bytes) // -> path | nullStorage & Toasts
const data = await chroma.storage.get() // whole storage object (or null)
await chroma.storage.set({ anything: true }) // persisted as JSON per plugin
chroma.toast('success', 'Done!') // levels: info | success | warn | errorStyling
Plugin DOM lives inside the app, so the theme's CSS variables are available:
var(--bg) var(--bg2) var(--border) var(--text) var(--text-dim)
var(--accent) var(--success) var(--warn) var(--error) var(--mono)Built-in utility classes like btn btn-tiny, btn btn-small, btn-primary also work on your buttons. Using these keeps plugins looking native in light, dark, and ChromaLabs themes.
Complete Example
The Markdown Tools plugin is a working example that demonstrates every extension point: an MD file badge, an MD* repo badge for uncommitted markdown, a TODO-scanner repo button with a results window, a per-repo Notes tab persisted via plugin storage, and a rendered markdown viewer with an edit/save mode.
Distributing Plugins
Zip the plugin folder and share it — installing is unzip → plugins folder → Reload plugins. There is no store or signing (yet); treat plugin zips like any other software you'd run. Want your plugin on the approved plugins list? Share it in the ChromaLabs Discord for review.
Troubleshooting
- >A plugin that fails to load shows its error in Settings → Plugins and in a toast at startup.
- >
plugin.jsonproblems (badid, missingmain) are reported per-plugin; other plugins still load. - >Exceptions inside badge providers are silently ignored (so a broken badge can't break the UI); exceptions in actions/tabs/viewers surface as toasts or inline error text.
Questions about the plugin API?
Join Our Discord Support Server