Schema Builder — Plugin Authoring Guide
This guide explains how to build, test, and publish a Schema Builder plugin. Plugins extend Schema Builder at runtime without a rebuild — they are loaded as standard ES modules via the Plugin Manager.
Quick Start
mkdir my-plugin && cd my-plugin
npm init -y
npm install -D vite typescript
Create src/index.ts:
import type { SchemaBuilderPlugin } from 'schema-builder-plugin-types';
const plugin: SchemaBuilderPlugin = {
id: 'com.myorg.my-plugin',
name: 'My Plugin',
description: 'A short description shown in the Plugin Manager',
pluginType: 'translator',
formatId: 'my-format',
version: '1.0.0',
author: 'My Org',
icon: 'fa-solid fa-file-code',
translate(rows, sourceUri) {
const selected = rows.filter(r => r.selected);
return selected.map(r => `${r.mapTo}: ${r.targetType}`).join('\n');
},
};
export default plugin;
Build to a single ES module:
npx vite build --config vite.plugin.config.ts
Load via ⋮ → View Plugins → Upload Plugin (.js) or Load from URL.
Plugin Contract (SchemaBuilderPlugin)
Every plugin must export a default object satisfying this interface:
interface SchemaBuilderPlugin {
// ── Required for all plugin types ─────────────────────────────────
id: string; // Unique reverse-DNS id: 'com.myorg.plugin-name'
name: string; // Display name shown in Plugin Manager
description: string; // One-line description
pluginType: PluginType; // 'translator' | 'exporter' | 'panel' | 'importer' | 'validator'
version: string; // Semver: '1.0.0'
author: string;
icon: string; // FontAwesome class: 'fa-solid fa-database'
// ── TRANSLATOR ──────────────────────────────────────── ─────────────
formatId?: string; // Maps to MapperTargetFormat or a new format
translate?: (rows: MappingRow[], sourceUri: string) => string | Promise<string>;
// ── PANEL ──────────────────────────────────────────────────────────
panelComponent?: Component; // Vue 3 component to render as a full panel
supportedModes?: SessionMode[];
panelIcon?: string;
panelLabel?: string;
// ── IMPORTER ───────────────────────────────────────────────────────
import?(): Promise<string | null>; // Returns JSON-LD string or null (cancelled)
// ── VALIDATOR ──────────────────────────────────────────────────────
validate?(schema: object): Promise<ValidationIssue[]>;
// ── Lifecycle (all types) ──────────────────────────────────────────
onActivate?(): void;
onDeactivate?(): void;
}
MappingRow
interface MappingRow {
id: string; // UUID
nodeId: string; // Full @id URI e.g. "https://example.com/schema#Patient"
nodeType: string; // owl:Class | owl:DatatypeProperty | ...
nodeLabel: string; // Human-readable label
selected: boolean;
mapTo: string; // Target identifier (table name, key name, etc.)
targetType: string; // TABLE | TEXT | INTEGER | DATE | BOOLEAN | ...
notes: string;
}
ValidationIssue
interface ValidationIssue {
path: string; // JSON Pointer: "/entities/0/@id"
severity: 'error' | 'warning' | 'info';
message: string;
}
Plugin Types
translator — Convert nodes to a string output
The most common plugin type. Adds a new entry to the Mapper View format dropdown.
const csvPlugin: SchemaBuilderPlugin = {
id: 'com.myorg.csv-exporter',
pluginType: 'translator',
formatId: 'csv', // Will appear in Mapper View dropdown
name: 'CSV Export',
description: 'Exports selected nodes as a CSV file',
version: '1.0.0',
author: 'My Org',
icon: 'fa-solid fa-table',
translate(rows, sourceUri) {
const header = 'node_id,label,map_to,type';
const lines = rows
.filter(r => r.selected)
.map(r => `"${r.nodeId}","${r.nodeLabel}","${r.mapTo}","${r.targetType}"`);
return [header, ...lines].join('\n');
},
};
Async translators: if your plugin calls an external service (e.g. the OWL sidecar), return a
Promise<string>. The Mapper View callstranslateAsync()in that case.
Built-in translator formatId values (reserved — do not reuse):
formatId | Plugin | Output |
|---|---|---|
sql-postgresql | PostgreSQL DDL | schema.sql |
sql-duckdb | DuckDB DDL | schema.sql |
seed-data-sql | Seed Dataset Generator | schema.inserts.sql |
panel — Register a custom panel
Adds a new panel with a toolbar toggle button.
import MyCustomPanel from './MyCustomPanel.vue';
const panelPlugin: SchemaBuilderPlugin = {
id: 'com.myorg.custom-panel',
pluginType: 'panel',
name: 'SPARQL Explorer',
description: 'Browse the CareLex SPARQL endpoint',
version: '1.0.0',
author: 'My Org',
icon: 'fa-solid fa-diagram-project',
panelComponent: MyCustomPanel,
supportedModes: ['SchemaEditor'],
panelIcon: 'fa-solid fa-diagram-project',
panelLabel: 'SPARQL Explorer',
};
Auto-registration: Panel plugins registered via
pluginRegistry.register()are automatically added topanelTypeRegistryand receive a toolbar toggle button — no manual wiring needed.
importer — Add a new import source
const owlUrlImporter: SchemaBuilderPlugin = {
id: 'com.myorg.owl-url-importer',
pluginType: 'importer',
name: 'Import OWL from URL',
description: 'Fetch and convert an OWL ontology from any URL',
version: '1.0.0',
author: 'My Org',
icon: 'fa-solid fa-link',
async import() {
const url = prompt('Enter OWL ontology URL:');
if (!url) return null;
const res = await fetch(url);
const owl = await res.text();
// Convert owl → JSON-LD (call your own sidecar or library)
const jsonld = await convertOwlToJsonLd(owl);
return jsonld;
},
};
validator — Add custom validation rules
const shaclValidator: SchemaBuilderPlugin = {
id: 'com.myorg.shacl-validator',
pluginType: 'validator',
name: 'SHACL Validator',
description: 'Validates JSON-LD against SHACL shapes',
version: '1.0.0',
author: 'My Org',
icon: 'fa-solid fa-shield-halved',
async validate(schema) {
const issues: ValidationIssue[] = [];
// ... your SHACL evaluation logic ...
return issues;
},
};
Build Configuration
vite.plugin.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
build: {
lib: {
entry: 'src/index.ts',
formats: ['es'], // ES module only — required by the loader
fileName: 'index',
},
rollupOptions: {
// Treat Vue as external — Schema Builder provides it at runtime
external: ['vue'],
},
outDir: 'dist',
},
});
Build output: dist/index.js — a single ES module file.
Loading a Plugin
From a URL (CDN / dev server)
⋮ → View Plugins → Load from URL
Paste the URL to the built ES module, e.g.:
https://unpkg.com/my-plugin@1.0.0/dist/index.jshttp://localhost:3001/dist/index.js(local dev server)
From a file
⋮ → View Plugins → Upload Plugin (.js)
Select the dist/index.js file from disk. A Blob URL is created temporarily and revoked after import.
Programmatically
import { loadPluginFromUrl } from '@/plugins/pluginLoader';
import { loadPluginFromFile } from '@/plugins/pluginLoader';
// From URL
await loadPluginFromUrl('https://example.com/my-plugin/dist/index.js');
// From File object
await loadPluginFromFile(fileInput.files[0]);
Validation
The loader validates that the module default export includes:
| Field | Required |
|---|---|
id | ✅ |
name | ✅ |
pluginType | ✅ (must be a valid type) |
version | ✅ |
author | ✅ |
translate | If pluginType === 'translator' |
import | If pluginType === 'importer' |
validate | If pluginType === 'validator' |
Missing fields raise a descriptive Error shown in the Plugin Manager toast.
Lifecycle Hooks
const plugin: SchemaBuilderPlugin = {
// ...
onActivate() {
// Called immediately after pluginRegistry.register()
console.log('Plugin activated');
},
onDeactivate() {
// Called immediately before pluginRegistry.unregister()
// Clean up subscriptions, timers, etc.
console.log('Plugin deactivated');
},
};
Publishing to npm
npm publish --access public
Recommended package.json:
{
"name": "schema-builder-plugin-my-format",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"exports": {
".": "./dist/index.js"
},
"keywords": ["sureclinical", "schema-builder-plugin"],
"peerDependencies": {
"vue": "^3.0.0"
}
}
Once published, users can load via unpkg:
https://unpkg.com/schema-builder-plugin-my-format@latest/dist/index.js
Built-in Plugins Reference
The following translator plugins ship pre-registered with Schema Builder:
| Plugin | formatId | Description | Doc |
|---|---|---|---|
| PostgreSQL DDL | sql-postgresql | CREATE TABLE with FK constraints | — |
| DuckDB DDL | sql-duckdb | DuckDB-flavoured DDL with FK constraints | — |
| Seed Dataset Generator | seed-data-sql | Realistic SQL INSERT seed rows via faker.js | View → |
Deferred / Future Plugins
The following plugins are planned but deferred until after core PRD completion:
| Plugin | Type | Notes |
|---|---|---|
| OWL/RDF exporter | translator | Requires OWL sidecar (Spring Boot) — see PRD §Appendix I.1 |
| JSON Schema Registry | exporter | JSON Schema → Confluent / Apicurio — PRD §Appendix I.2 |
| XLSX Workbook | translator | SheetJS multi-sheet export — PRD §Appendix I.3 |
See schema-builder-PRD.md §Appendix I for full specifications.