๐งฉ Build a GhostStat plugin
Everything in GhostStat that isn't the core โ heatmaps, session recording,
funnels, cohorts, the lot โ is a plugin. And the plugin system is open: if
you can write a little PHP and JavaScript, you can add a dashboard widget, a
whole tracking module, or an integration, and it will look and behave exactly
like the rest of the app.
A plugin is one folder, discovered by its manifest.json, loaded as a class
whose methods are the hooks. Copy the starter, rename, ship.
The kit
- ๐ Plugin Dev Guide โ Building a GhostStat plugin โ the why: the plugin model, hooks,
routes, the design system, SQL, and i18n, matched to how GhostStat actually
loads plugins.
- ๐ Cheat-Sheet (one page) โ Building a GhostStat plugin โ folder layout, the hook table,
logic.php in miniature, and the do/don't rules at a glance.
- ๐ฆ Starter skeleton โ Building a GhostStat plugin โ a complete working
example plugin
to copy: manifest โ instance with hooks โ data โ templates โ tracking โ SQL โ
translations.
Quickstart
- Copy the starter to
plugins/<your-slug>/.
- Rename everything
example โ yours: the slug/name in manifest.json, the
ExamplePlugin class, the Example_* functions, the ex_ table prefix, the
.gs-plugin-example CSS scope, the ex. i18n key prefix, and the
example_* event names.
- Mirror
assets/ to /cdn/plugins/<slug>/assets/, bump the manifest
version, then Admin โ Plugins โ Rescan. Enable, grant to plans, test.
The house style (so your plugin is indistinguishable from core)
- Bootstrap 5 + bootstrap-icons, and the GhostStat tokens (
var(--accent),
gs-card, gs-table-wrap). Never redefine tokens; scope your rules under
.gs-plugin-<slug>.
- No cookies, no localStorage, no fingerprinting โ ever. Send tracking
through the shared gs.js queue; never touch the core events/sessions
tables.
- Fully responsive, zero horizontal scroll on mobile.
- Every visible string is a translation key (see below) โ never hard-code UI
text.
๐ Translations โ and how to contribute one
Plugin strings live in i18n/<code>.php โ one PHP file per language that
returns a flat key => string array, en.php as the source of truth. Keys are
namespaced by a short prefix (the starter uses ex.). Resolve them with the
global t('ex.key') on the server and TR('ex.key') in your dashboard
JS. A missing key falls back to English, then to the key, so nothing ever renders
blank.
Want to help translate GhostStat or a plugin into your language? It's one of the
easiest, most valuable things you can do here:
- Grab the plugin's
i18n/en.php.
- Copy it to
i18n/<yourlang>.php and translate the values only โ leave the
keys exactly as they are.
- Post it under Plugin Translations with your language tag
(Dutch / German / French / Spanish, or open a Community Request for a new
one). We review it, drop it in, bump the version, and redeploy.
A reviewed, kept-current language pack is The Translator path to a free
GhostStat Pro account for life โ see The Ghost Guild.
Where to go next
- Stuck on an endpoint? Ask under API Questions (in this zone).
- Built something? Show it in Showcases โ real plugins are the best proof the
system works, and shipping one is The Builder path to Pro-for-life.
Post your questions, half-finished ideas, and code below. This is where GhostStat
grows sideways โ everything the core doesn't do yet, someone here can.