Skip to main content

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 calls translateAsync() in that case.

Built-in translator formatId values (reserved — do not reuse):

formatIdPluginOutput
sql-postgresqlPostgreSQL DDLschema.sql
sql-duckdbDuckDB DDLschema.sql
seed-data-sqlSeed Dataset Generatorschema.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 to panelTypeRegistry and 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.js
  • http://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:

FieldRequired
id
name
pluginType✅ (must be a valid type)
version
author
translateIf pluginType === 'translator'
importIf pluginType === 'importer'
validateIf 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:

PluginformatIdDescriptionDoc
PostgreSQL DDLsql-postgresqlCREATE TABLE with FK constraints
DuckDB DDLsql-duckdbDuckDB-flavoured DDL with FK constraints
Seed Dataset Generatorseed-data-sqlRealistic SQL INSERT seed rows via faker.jsView →

Deferred / Future Plugins

The following plugins are planned but deferred until after core PRD completion:

PluginTypeNotes
OWL/RDF exportertranslatorRequires OWL sidecar (Spring Boot) — see PRD §Appendix I.1
JSON Schema RegistryexporterJSON Schema → Confluent / Apicurio — PRD §Appendix I.2
XLSX WorkbooktranslatorSheetJS multi-sheet export — PRD §Appendix I.3

See schema-builder-PRD.md §Appendix I for full specifications.