How to
Build Plugins.
Architecture
Every plugin is a plain JS IIFE (immediately-invoked function expression) that calls
window.pazatorPlugins.register() with a plugin definition object.
There are no dependencies, no build step, no package manager. You can install a plugin
by dropping a URL or pasting code directly into the Plugins tab.
The plugin system lives in app/js/core/plugins.js (~485 lines).
It also houses Tastur, the app-wide event bus. The system manages
lifecycle (register, init, enable, disable, unload), persistence of enabled/disabled
states, and a UI manager with search/filter cards.
Plugin Definition
The object you pass to register() has these fields:
{
id: string, // unique, e.g. "my-awesome-plugin"
name: string, // display name
version: string, // semver, e.g. "1.0.0"
description: string, // short blurb
icon: string, // Font Awesome class, e.g. "fas fa-rocket"
author: string, // your name/handle
// Lifecycle hooks
onLoad: function(api), // called when enabled
onUnload: function(api), // called when disabled
// UI extensions
tabs: [TabDef], // register new tab(s)
menuItems: [MenuItemDef], // add menu entries
// Event handlers
handlers: { eventName: function(data) }
}
Minimal Example
(function () {
'use strict';
window.pazatorPlugins.register({
id: 'hello-pazator',
name: 'Hello Pazator',
version: '0.1.0',
description: 'Shows a greeting when enabled',
icon: 'fas fa-wave',
author: 'You',
onLoad: function () {
alert('Hello from Pazator!');
}
});
})();
Paste that into the Plugins tab code area and hit install. That's it.
Plugin API
Your onLoad and onUnload hooks receive
an api object with these capabilities:
api.store — Data Store
Direct reference to pazatorStore. Full CRUD access to all entities:
api.store.getHumans() // array of Person entities api.store.getOthers() // array of Organization/entity records api.store.getTags() // array of tags api.store.getCases() // array of cases api.store.getRelatedObjects() // graph edges api.store.searchObjects(query) // search across entities
api.events — Tastur Event Bus
Subscribe to app events or emit your own:
var unsub = api.events.on('entity_changed', function (data) {
console.log('Entity changed:', data.id, data.action);
});
// call unsub() to clean up (or let deinitPlugin do it via handlers)
Built-in events you can hook into:
entity_changed—{id, type, action}humans_changed/others_changeddata_changed— any data mutationdata_saved/data_loadedthreat_detected— TIDE scan resultsearch_performed—{query, results}
api.settings — KV Store
Persist plugin settings (stored in IndexedDB KV store):
api.settings.get('myKey').then(function (val) { ... });
api.settings.set('myKey', someValue);
api.modals — Modal Helpers
api.modals.alert('Message'); // simple alert
api.modals.confirm('Proceed?'); // returns boolean
api.modals.show({title, body, actions});// custom modal
api.tab — Register Tabs
Add an entirely new tab to the app with api.tab.register(cfg):
api.tab.register({
id: 'my-tab',
label: 'My Tab',
render: function (container) {
container.innerHTML = '<h2>Hello from plugin!</h2>';
}
});
This automatically adds a tab button in the sidebar and a content panel. Remove with
api.tab.unregister('my-tab').
api.menu — Menu Items
Add items to the app's menu bar:
api.menu.register({
menu: 'tools', // 'view' | 'data' | 'tools' | 'system' | 'help'
label: 'My Action',
icon: 'fas fa-bolt',
action: function () { /* handle click */ }
});
api.menu.unregister('my-action-id');
api.log(msg) — Logging
Logs with your plugin's ID as prefix: [Plugin:my-plugin] msg.
Event Handlers (handlers)
Instead of manually subscribing in onLoad, you can list handlers in the
plugin definition. The system subscribes/unsubscribes them automatically on enable/disable:
window.pazatorPlugins.register({
id: 'example',
name: 'Example',
...
handlers: {
entity_changed: function (data) {
if (data.action === 'add' && data.type === 'human') {
api.modals.alert('New person added!');
}
},
data_saved: function () {
console.log('Data saved');
}
}
});
Installation Methods
1. From URL
Host your plugin JS anywhere (a gist, your own server, a CDN), paste the URL into
the Plugins tab's URL input, and click Install. The app loads it via a dynamic
<script> tag.
2. From Code
Paste the full plugin IIFE source directly into the code textarea on the Plugins tab.
The system wraps it in a new Function('api', code) call, so you can
also write code that uses the api parameter directly.
3. Built-in (bundled in app/js/plugins/)
Place a .js file in app/js/plugins/ and load it
in app/index.html. See server-search.js as a
reference — it's a bundled plugin that's disabled by default.
Real Example: Server Search
The built-in Server Search plugin (app/js/plugins/server-search.js)
shows the full pattern:
- Wraps everything in an IIFE
- Adds a menu item under the Data menu via
menuItems - Opens a modal on click (full custom DOM, no helper)
- Fetches from the PZLS search API
- Registers with
window.pazatorPlugins.register()
Read the source for a complete real-world example of DOM creation, API calls, async flows, and cleanup.
Disable / Uninstall
Users can toggle your plugin on/off from the Plugins tab cards. When toggled off,
the system calls onUnload, unsubscribes all handlers,
removes registered tabs and menu items. State is persisted across page reloads.
Best Practices
- Wrap in an IIFE to avoid leaking globals
- Clean up in
onUnload(remove DOM, intervals, etc.) - Use
api.settingsfor persistence, not localStorage directly - Prefix any custom CSS classes with
plugin-to avoid conflicts - Catch errors in async code so a broken plugin doesn't break the app
Future
The plugin system is minimal by design. Upcoming additions may include a plugin marketplace, sandboxed iframe isolation, and a richer API surface for hooks into the analysis pipeline.