Plugins
Extend OpenAnalyst with custom data sources, visualizations, agent capabilities, and export formats. Build, test, and publish plugins to the marketplace.
Plugin Architecture Overview
The OpenAnalyst plugin system allows developers to extend the platform with custom functionality that integrates natively into the web application. Plugins run in an isolated sandboxed environment within the browser, communicating with the host application through a structured message-passing API. They have access to a defined set of platform capabilities depending on the permissions declared in their manifest.
Plugins are distributed as ZIP archives containing a manifest file, JavaScript entry point, and any static assets. They are installed by workspace administrators through the Settings page or the plugin marketplace and can be enabled or disabled per workspace.
Note: Plugins execute entirely within the user's browser for web app plugins, or within the OpenAnalyst agent runtime for agent plugins. They do not run on OpenAnalyst servers and cannot access other workspaces' data.
Plugin Types
There are four categories of plugins, each targeting a different extension point:
| Type | Key Identifier | What It Extends |
|---|---|---|
| Data Source Plugin | datasource | Adds a new connector type to the dataset library (e.g., a proprietary API or internal data warehouse) |
| Visualization Plugin | visualization | Adds custom chart or table types to dashboards and reports |
| Agent Plugin | agent | Provides additional tools and skills that AI agents can invoke during a session |
| Export Plugin | export | Adds new export destinations or file formats (e.g., push to Notion, export as PPTX) |
Plugin Manifest
Every plugin must include a manifest.json file at the root of its distribution archive. The manifest declares the plugin identity, type, entry point, required permissions, and configuration schema.
{
"id": "com.example.radar-chart",
"name": "Radar Chart",
"version": "1.2.0",
"description": "Adds a customizable radar/spider chart visualization type.",
"author": {
"name": "Example Corp",
"email": "plugins@example.com",
"url": "https://example.com"
},
"type": "visualization",
"entry": "dist/index.js",
"icon": "assets/icon.svg",
"minAppVersion": "2.0.0",
"permissions": [
"read:query_results",
"write:dashboard"
],
"configSchema": {
"type": "object",
"properties": {
"colorScheme": {
"type": "string",
"enum": ["default", "monochrome", "warm", "cool"],
"default": "default",
"title": "Color Scheme"
},
"showLabels": {
"type": "boolean",
"default": true,
"title": "Show axis labels"
}
}
}
}Plugin Entry Point and Lifecycle Hooks
The entry point file must export a default object conforming to the plugin interface for its declared type. The plugin host calls lifecycle hooks at predictable moments during the plugin's existence within the application.
// dist/index.js (Visualization Plugin entry point)
import { registerVisualization } from '@openanalyst/plugin-sdk';
export default registerVisualization({
// Called once when the plugin is first loaded
onLoad(context) {
console.log('Radar Chart plugin loaded, version:', context.pluginVersion);
},
// Called when a chart instance is created on a dashboard
onMount(container, queryResults, config) {
renderRadarChart(container, queryResults, config);
},
// Called when query results or config change while the chart is displayed
onUpdate(container, queryResults, config) {
updateRadarChart(container, queryResults, config);
},
// Called when the chart is removed from the dashboard
onUnmount(container) {
destroyRadarChart(container);
},
// Called when the plugin is uninstalled from the workspace
onUnload() {
// Clean up any global state
},
});Plugin API
Plugins communicate with OpenAnalyst through the plugin API object injected into their context. The available methods depend on the declared permissions.
// Context object available in lifecycle hooks
interface PluginContext {
pluginId: string;
pluginVersion: string;
workspaceId: string;
// Storage: key-value store scoped to this plugin and workspace
storage: {
get(key: string): Promise<unknown>;
set(key: string, value: unknown): Promise<void>;
delete(key: string): Promise<void>;
};
// Notifications
notify: {
success(message: string): void;
error(message: string): void;
info(message: string): void;
};
// Access to query results (requires read:query_results permission)
queries: {
getLatestResults(queryId: string): Promise<QueryResults>;
runQuery(sql: string, datasetId: string): Promise<QueryResults>;
};
// Dashboard write access (requires write:dashboard permission)
dashboard: {
addWidget(config: WidgetConfig): Promise<void>;
updateWidget(id: string, config: Partial<WidgetConfig>): Promise<void>;
};
}Example: Creating a Custom Chart Plugin
The following walkthrough creates a minimal but functional radar chart plugin using D3.js.
- Scaffold the plugin directory structure:
radar-chart/ ├── manifest.json ├── package.json ├── src/ │ └── index.ts ├── assets/ │ └── icon.svg └── dist/ # generated by build - Install the plugin SDK and build dependencies:
npm install @openanalyst/plugin-sdk d3 npm install --save-dev typescript esbuild @types/d3 - Implement the visualization:
// src/index.ts import * as d3 from 'd3'; import { registerVisualization } from '@openanalyst/plugin-sdk'; function render(container: HTMLElement, rows: Record<string, number>[], config: Record<string, unknown>) { const categories = Object.keys(rows[0]); const values = categories.map((c) => rows[0][c] as number); const max = d3.max(values) ?? 1; const width = container.clientWidth; const height = container.clientHeight; const radius = Math.min(width, height) / 2 - 40; d3.select(container).selectAll('*').remove(); const svg = d3.select(container) .append('svg') .attr('width', width) .attr('height', height) .append('g') .attr('transform', `translate(${width / 2},${height / 2})`); const angleSlice = (Math.PI * 2) / categories.length; const rScale = d3.scaleLinear().range([0, radius]).domain([0, max]); // Draw axes categories.forEach((cat, i) => { const angle = angleSlice * i - Math.PI / 2; svg.append('line') .attr('x1', 0).attr('y1', 0) .attr('x2', rScale(max) * Math.cos(angle)) .attr('y2', rScale(max) * Math.sin(angle)) .attr('stroke', '#444').attr('stroke-width', 1); if (config.showLabels) { svg.append('text') .attr('x', (rScale(max) + 12) * Math.cos(angle)) .attr('y', (rScale(max) + 12) * Math.sin(angle)) .attr('text-anchor', 'middle') .attr('fill', '#ccc') .attr('font-size', '12px') .text(cat); } }); // Draw data polygon const radarLine = d3.lineRadial<number>() .radius((d) => rScale(d)) .angle((_, i) => i * angleSlice) .curve(d3.curveLinearClosed); svg.append('path') .datum(values) .attr('d', radarLine) .attr('fill', 'rgba(255, 133, 82, 0.3)') .attr('stroke', '#ff8552') .attr('stroke-width', 2); } export default registerVisualization({ onMount(container, results, config) { render(container, results.rows, config); }, onUpdate(container, results, config) { render(container, results.rows, config); }, onUnmount(container) { d3.select(container).selectAll('*').remove(); }, }); - Build and package:
# Build npx esbuild src/index.ts --bundle --outfile=dist/index.js --format=esm # Package as ZIP zip -r radar-chart-v1.2.0.zip manifest.json dist/ assets/
Example: Creating a Data Source Plugin
A data source plugin registers a new connection type that users can select when adding a dataset. It must implement a connection test method and a data fetch method.
// src/index.ts
import { registerDataSource } from '@openanalyst/plugin-sdk';
export default registerDataSource({
// Renders the connection form fields
configFields: [
{ key: 'apiEndpoint', label: 'API Endpoint URL', type: 'url', required: true },
{ key: 'apiToken', label: 'API Token', type: 'password', required: true },
{ key: 'tableName', label: 'Resource Name', type: 'text', required: true },
],
// Validates the connection; throw to indicate failure
async testConnection(config) {
const response = await fetch(config.apiEndpoint + '/health', {
headers: { Authorization: `Bearer ${config.apiToken}` },
});
if (!response.ok) throw new Error(`Connection test failed: ${response.statusText}`);
},
// Returns the schema (columns) of the data source
async getSchema(config) {
const response = await fetch(`${config.apiEndpoint}/schema/${config.tableName}`, {
headers: { Authorization: `Bearer ${config.apiToken}` },
});
const schema = await response.json();
return schema.fields.map((f: { name: string; type: string }) => ({
name: f.name,
type: f.type,
}));
},
// Fetches data; params contains filters, limit, offset
async fetchData(config, params) {
const url = new URL(`${config.apiEndpoint}/data/${config.tableName}`);
if (params.limit) url.searchParams.set('limit', String(params.limit));
if (params.offset) url.searchParams.set('offset', String(params.offset));
const response = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${config.apiToken}` },
});
return response.json();
},
});Plugin Permissions
Plugins must declare every permission they need in the manifest. Users are shown these permissions during installation. Attempting to use an undeclared permission throws a PermissionDeniedError.
| Permission | Grants Access To |
|---|---|
read:query_results | Read query results for datasets the user can access |
write:dashboard | Add or update widgets on dashboards |
read:reports | Read report definitions and generated outputs |
write:reports | Create and modify reports |
read:datasets | Read dataset metadata (not raw data) |
write:datasets | Create and update datasets |
network | Make outbound HTTP requests to domains listed in allowedDomains |
storage | Persist key-value data in the plugin storage namespace |
Warning: Plugins requesting the network permission must also declare an allowedDomains array in the manifest. Requests to domains not in this list are blocked at the network layer.
Publishing to the Marketplace
- Create a developer account at app.openanalyst.com and enroll in the Developer Program from Settings.
- Build and test your plugin locally by installing it as a private plugin in a test workspace.
- Run the plugin validator to check manifest completeness and permission declarations:
npx @openanalyst/plugin-cli validate ./radar-chart-v1.2.0.zip - Submit the plugin for review through the Developer Portal. The review process typically takes 3-5 business days.
- Once approved, your plugin appears in the public marketplace. You can push updates by submitting a new ZIP with an incremented
versionin the manifest.