STEMBlock AI Plugin Development Guide
What is a Plugin?
A plugin is a self-contained feature module that registers itself with the STEMBlock AI platform. Each plugin consists of:
- Plugin Manifest — Metadata (name, version, icon, category, UI extension points)
- Evaluation Domain (optional) — AI evaluation providers with a factory pattern
- UI Extension Points — Navigation links injected into the sidebar per role
- Organization Installation — Per-org install/uninstall via the admin UI or API
Architecture Overview
PluginsCoreModule (Global)
├── EvaluationRegistry — tracks evaluation domains
├── PipelineExecutor — runs pipeline-based evaluations
├── PluginManifestRegistry — tracks plugin manifests
└── PluginsService — install/uninstall/query per org
Each plugin module (e.g., EnglishWritingModule):
├── Registers its manifest in onModuleInit
├── Registers its evaluation domain in onModuleInit
├── Contains its own providers, services, controllers
└── Extends BaseProviderFactory for AI provider selection
Creating a New Plugin
Step 1: Create the Manifest
Create a manifest file in your module directory:
// src/my-plugin/my-plugin.manifest.ts
import type { PluginManifest } from '../plugins/manifest/plugin-manifest.types';
export const MY_PLUGIN_MANIFEST: PluginManifest = {
pluginId: 'math-problem-solving', // Unique identifier
version: '1.0.0',
displayName: 'Math Problem Solving',
description: 'AI-powered math evaluation and step-by-step feedback',
author: 'STEMBlock AI',
icon: 'CalculatorIcon', // Heroicon name
category: 'Mathematics',
evaluationDomain: {
domainId: 'math-problem-solving',
executionModel: 'pipeline', // or 'capabilities'
scoreDimensions: ['accuracy', 'methodology', 'explanation'],
},
featureFlag: 'MATH_EVALUATION', // Maps to FeatureFlag enum
ui: [
{
slot: 'sidebar-nav',
route: '/student/math',
label: 'Math',
icon: 'CalculatorIcon',
roles: ['STUDENT'],
},
{
slot: 'sidebar-nav',
route: '/coach/math',
label: 'Math',
icon: 'CalculatorIcon',
roles: ['COACH'],
},
],
requiredEnvVars: ['MATH_EVALUATOR_PROVIDER'],
};
Step 2: Create Your Evaluation Provider (if applicable)
Choose between Pipeline or Capabilities execution model:
Pipeline — for ordered, sequential stages (e.g., moderation -> feedback -> scoring):
import { PipelineEvaluationProvider } from '../plugins/evaluation';
export abstract class MathEvaluatorProvider extends PipelineEvaluationProvider<MathContext> {
abstract getStageDefinitions(): PipelineStageDefinition[];
abstract executeStage(stageId: string, context: MathContext): Promise<StageResult>;
}
Capabilities — for independent operations:
import { CapabilitiesEvaluationProvider } from '../plugins/evaluation';
export abstract class MathEvaluatorProvider extends CapabilitiesEvaluationProvider {
abstract getCapabilities(): CapabilityDefinition[];
abstract executeCapability(capabilityId: string, request: unknown): Promise<unknown>;
}
Step 3: Create Your Provider Factory
Extend BaseProviderFactory to get env-var-driven provider selection with mock fallback:
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { BaseProviderFactory } from '../plugins/evaluation/base-provider.factory';
@Injectable()
export class MathEvaluatorFactory extends BaseProviderFactory<MathEvaluatorProvider> {
constructor(
configService: ConfigService,
private readonly geminiProvider: GeminiMathProvider,
private readonly mockProvider: MockMathProvider,
) {
super(configService);
}
protected getEnvVarName(): string { return 'MATH_EVALUATOR_PROVIDER'; }
protected getDefaultType(): string { return 'mock'; }
protected getProviderMap(): Map<string, MathEvaluatorProvider> {
return new Map([
['gemini', this.geminiProvider],
]);
}
protected getMockProvider(): MathEvaluatorProvider {
return this.mockProvider;
}
}
Step 4: Create Your NestJS Module
Register with the plugin framework in onModuleInit:
import { Module, type OnModuleInit } from '@nestjs/common';
import { EvaluationRegistry } from '../plugins/evaluation/evaluation-registry.service';
import { PluginManifestRegistry } from '../plugins/manifest/plugin-manifest.registry';
import { MY_PLUGIN_MANIFEST } from './my-plugin.manifest';
@Module({
imports: [DatabaseModule],
controllers: [MathController],
providers: [
MathService,
MathEvaluatorFactory,
GeminiMathProvider,
MockMathProvider,
],
exports: [MathService],
})
export class MathModule implements OnModuleInit {
constructor(
private readonly evaluationRegistry: EvaluationRegistry,
private readonly manifestRegistry: PluginManifestRegistry,
) {}
onModuleInit() {
this.evaluationRegistry.register({
domainId: 'math-problem-solving',
name: 'Math Problem Solving',
factoryToken: 'MathEvaluatorFactory',
executionModel: 'pipeline',
});
this.manifestRegistry.registerManifest(MY_PLUGIN_MANIFEST);
}
}
Step 5: Add to AppModule
// src/app.module.ts
imports: [
// ... existing modules
MathModule, // Add your new module
]
Step 6: Add FeatureFlag (if needed)
Update prisma/schema.prisma:
enum FeatureFlag {
// ... existing flags
MATH_EVALUATION // Add new flag
}
Run: npx prisma migrate dev --name add_math_feature_flag
Plugin Manifest Reference
| Field | Type | Required | Description |
|---|---|---|---|
pluginId | string | Yes | Unique identifier (kebab-case) |
version | string | Yes | Semantic version |
displayName | string | Yes | Human-readable name |
description | string | Yes | Short description |
author | string | Yes | Author name |
icon | string | Yes | Heroicon component name |
category | string | Yes | Plugin category |
evaluationDomain | object | No | AI evaluation config |
featureFlag | string | Yes | Maps to FeatureFlag enum |
ui | PluginUIExtensionPoint[] | Yes | Frontend extension points |
requiredEnvVars | string[] | No | Required environment variables |
UI Extension Point
| Field | Type | Description |
|---|---|---|
slot | 'sidebar-nav' | Where to render (currently only sidebar) |
route | string | Next.js route path |
label | string | Display text |
icon | string | Heroicon component name |
roles | string[] | Which roles see this link |
Evaluation Provider Types
Pipeline Providers
Best for: Sequential evaluation stages where order matters and earlier stages can short-circuit.
Example: English Writing uses Moderation -> Feedback -> Assessment.
Key methods:
getStageDefinitions()— returns ordered list of stagesexecuteStage(stageId, context)— runs a single stage, returnsStageResult
The PipelineExecutor service handles:
- Sorting stages by
order - Running stages sequentially
- Short-circuit logic (e.g., stop if moderation flags content)
- Error handling for required stages
Capabilities Providers
Best for: Independent operations that don't depend on each other.
Example: STEM Evaluation exposes evaluateSubmission, generateCoachFeedback, generateParentInsights.
Key methods:
getCapabilities()— returns list of available operationsexecuteCapability(capabilityId, request)— runs a specific operation
Installing a Plugin
API Endpoints
GET /api/v1/plugins/available — List all registered plugins (ADMIN, ORG_ADMIN)
GET /api/v1/plugins/installed — List installed plugins for current org + role
POST /api/v1/plugins/install — Install: { pluginId: string }
DELETE /api/v1/plugins/:pluginId — Uninstall plugin
Admin UI
Navigate to Settings > App Store to see all available apps. Each card shows:
- App name, description, version, category, author
- Install/Uninstall button
- "Installed" badge for active apps
Seed Data
When running npx prisma db seed, both english-writing and ai-evaluation (formerly stem-evaluation) apps are auto-installed for all organizations.
Frontend Extension Points
The PluginsProvider context fetches installed plugins for the current user and provides:
const { plugins, navLinks, loading } = usePlugins();
plugins— all installed apps with UI extension points filtered by user rolenavLinks— flattened list of sidebar nav links from all installed appsloading— whether the apps query is still loading
Navigation.tsx renders navLinks dynamically for each role section, replacing hardcoded plugin-specific links.
To add a new icon for your app to the App Store page, update the ICON_MAP in app/settings/plugins/page.tsx.
Example: Math Problem Solving Plugin
A complete example plugin is included in the codebase at src/workflows/math-problem-solving/. This demonstrates the full plugin pattern with:
Backend Structure
src/workflows/math-problem-solving/
├── providers/
│ ├── math-evaluator.interface.ts # Abstract provider (3-stage: Validate → Feedback → Score)
│ ├── mock-math.provider.ts # Mock provider for testing
│ ├── math-evaluator.factory.ts # Extends BaseProviderFactory
│ └── index.ts
├── dto/
│ └── submit-math.dto.ts # Input validation
├── math-problem-solving.manifest.ts # Plugin manifest
├── math-problem-solving.service.ts # Business logic
├── math-problem-solving.controller.ts # API endpoints
└── math-problem-solving.module.ts # NestJS module with OnModuleInit
Frontend Pages
app/student/math/page.tsx # Student view — select problem, submit answer, view AI feedback
app/coach/math/page.tsx # Coach view — placeholder for reviewing student submissions
API Endpoints
GET /api/v1/math/demo-problems — List demo math problems
POST /api/v1/math/evaluate — Evaluate a math submission
Demo: Install Flow
The Math plugin is registered but NOT installed by default. This makes it perfect for demoing the install/uninstall flow:
- Start the backend — the Math plugin registers its manifest on startup (check logs for "Registered plugin: Math Problem Solving")
- Log in as ORG_ADMIN — navigate to Settings > App Store (
/settings/plugins) - See all 3 apps — English Writing and AI Evaluation show "Installed", Math shows Install button
- Click Install on Math Problem Solving
- Navigate as STUDENT — the "Math" link now appears in the sidebar (dynamic from API)
- Go to
/student/math— select a demo problem, type your answer, click "Submit & Evaluate" - View AI feedback — validation, scoring (accuracy/methodology/explanation/completeness), and detailed feedback
- Uninstall — go back to Settings > App Store, click Uninstall on Math. The nav link disappears
This demonstrates the complete lifecycle: registration → discovery → installation → dynamic UI → usage → uninstallation.
Developer SDK
For a streamlined plugin development experience, use the STEMBlock AI Plugin SDK (@stemblockai/plugin-sdk). The SDK repo is at ../stemblockai-plugin-sdk/.
SDK Features
- CLI Tool (
stemblockai-plugin) — Scaffold, validate, bundle, and test plugins - Project Templates —
evaluation-plugin(with AI pipeline) andbasic-plugin(pages only) - Manifest Validator — Validates all fields, formats, and constraints before submission
- Plugin Bundler — Packages plugins into
.stemblockdistribution format - Sandbox Testing — Test plugins in an isolated mock NestJS runtime
Quick Start
# Install the SDK
npm install -g @stemblockai/plugin-sdk
# Create a new plugin
stemblockai-plugin create "Science Quiz" --template evaluation-plugin
# Validate, test, and bundle
cd science-quiz
stemblockai-plugin validate
stemblockai-plugin sandbox --verbose
npm run bundle
Package Format
The .stemblock format is a gzipped JSON archive containing the manifest, compiled backend module, frontend pages, and assets. See the SDK documentation at ../stemblockai-plugin-sdk/docs/ for the full specification: