Plugin API Reference
Complete API reference for developing Minimact plugins.
Core Interfaces
IMinimactPlugin
The base interface that all plugins must implement.
public interface IMinimactPlugin
{
string Name { get; }
string Version { get; }
string Description { get; }
string Author { get; }
VNode Render(object state);
bool ValidateState(object state);
PluginAssets GetAssets();
string GetStateSchema();
void Initialize(IServiceProvider services);
}Properties
Name
- Type:
string - Required: Yes
- Description: Unique plugin identifier (e.g., "Clock", "Weather")
- Example:
"Clock" - Notes: Must match the name used in
<Plugin name="..." />JSX
Version
- Type:
string - Required: Yes
- Description: Plugin version following semantic versioning (semver)
- Example:
"1.0.0" - Format:
MAJOR.MINOR.PATCH
Description
- Type:
string - Required: No
- Description: Human-readable description of the plugin
- Example:
"A customizable clock widget"
Author
- Type:
string - Required: No
- Description: Plugin author or organization
- Example:
"Minimact Team"
Methods
Render
VNode Render(object state)Renders the plugin with the provided state.
Parameters:
state(object) - The state object passed from the client
Returns: VNode - Virtual DOM representation of the plugin
Example:
public VNode Render(object state)
{
var typedState = (ClockState)state;
return new VElement("div",
new Dictionary<string, string> { ["className"] = "clock" },
$"{typedState.Hours}:{typedState.Minutes}"
);
}ValidateState
bool ValidateState(object state)Validates that the state matches the plugin's contract.
Parameters:
state(object) - The state object to validate
Returns: bool - True if valid, false otherwise
Default Implementation: Uses JSON Schema validation via GetStateSchema()
Example:
public override bool ValidateState(object state)
{
if (state is not ClockState clockState)
return false;
return clockState.Hours >= 0 && clockState.Hours <= 23 &&
clockState.Minutes >= 0 && clockState.Minutes <= 59;
}GetAssets
PluginAssets GetAssets()Returns the plugin's CSS, JavaScript, images, and fonts.
Returns: PluginAssets - Asset configuration
Example:
public override PluginAssets GetAssets()
{
return new PluginAssets
{
CssFiles = new List<string>
{
"/plugin-assets/Clock@1.0.0/clock-widget.css"
},
Source = AssetSource.Embedded
};
}GetStateSchema
string GetStateSchema()Returns the JSON Schema for state validation.
Returns: string - JSON Schema (Draft 7)
Default Implementation: Auto-generates from generic type parameter
Example:
public override string GetStateSchema()
{
return @"{
""type"": ""object"",
""properties"": {
""hours"": { ""type"": ""integer"", ""minimum"": 0, ""maximum"": 23 },
""minutes"": { ""type"": ""integer"", ""minimum"": 0, ""maximum"": 59 }
},
""required"": [""hours"", ""minutes""]
}";
}Initialize
void Initialize(IServiceProvider services)Called once when the plugin is registered. Use for setup, dependency injection, etc.
Parameters:
services(IServiceProvider) - Service provider for dependency injection
Example:
public override void Initialize(IServiceProvider services)
{
var logger = services.GetRequiredService<ILogger<ClockPlugin>>();
logger.LogInformation("Clock plugin initialized");
}Base Classes
MinimactPluginBase
Abstract base class providing default implementations.
public abstract class MinimactPluginBase : IMinimactPlugin
{
public abstract string Name { get; }
public abstract string Version { get; }
public virtual string Description => string.Empty;
public virtual string Author => string.Empty;
public abstract VNode Render(object state);
public virtual bool ValidateState(object state) { /* JSON Schema */ }
public virtual PluginAssets GetAssets() { /* Empty */ }
public virtual string GetStateSchema() { /* Auto-generate */ }
public virtual void Initialize(IServiceProvider services) { /* No-op */ }
}Use When:
- You want default implementations for optional methods
- You need custom state validation logic
- You're handling state as
object(not strongly-typed)
Example:
[MinimactPlugin("Badge")]
public class BadgePlugin : MinimactPluginBase
{
public override string Name => "Badge";
public override string Version => "1.0.0";
public override VNode Render(object state)
{
var badge = (BadgeState)state;
return new VElement("span",
new Dictionary<string, string> { ["className"] = $"badge {badge.Color}" },
badge.Text
);
}
}MinimactPlugin<TState>
Generic base class for strongly-typed state handling.
public abstract class MinimactPlugin<TState> : MinimactPluginBase
{
public sealed override VNode Render(object state)
{
return RenderTyped((TState)state);
}
protected abstract VNode RenderTyped(TState state);
public override string GetStateSchema()
{
return JsonSchemaGenerator.Generate<TState>();
}
}Use When:
- You want type-safe state handling
- You want automatic JSON Schema generation
- You want compile-time type checking
Example:
[MinimactPlugin("Clock")]
public class ClockPlugin : MinimactPlugin<ClockState>
{
public override string Name => "Clock";
public override string Version => "1.0.0";
protected override VNode RenderTyped(ClockState state)
{
return new VElement("div",
new Dictionary<string, string> { ["className"] = "clock" },
$"{state.Hours:D2}:{state.Minutes:D2}"
);
}
}Attributes
MinimactPluginAttribute
Marks a class as a Minimact plugin for auto-discovery.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MinimactPluginAttribute : Attribute
{
public string Name { get; }
public MinimactPluginAttribute(string name);
}Usage:
[MinimactPlugin("Clock")]
public class ClockPlugin : MinimactPlugin<ClockState>
{
// ...
}Notes:
- Required for auto-discovery
- Name should match the
Nameproperty - Only one attribute per class
Data Classes
PluginAssets
Defines the assets (CSS, JS, images, fonts) used by a plugin.
public class PluginAssets
{
public List<string> CssFiles { get; set; } = new();
public List<string> JsFiles { get; set; } = new();
public Dictionary<string, string> Images { get; set; } = new();
public List<string> Fonts { get; set; } = new();
public AssetSource Source { get; set; } = AssetSource.Embedded;
}Properties
CssFiles
- Type:
List<string> - Description: CSS file paths or URLs
- Example:
["/plugin-assets/Clock@1.0.0/clock.css"]
JsFiles
- Type:
List<string> - Description: JavaScript file paths or URLs
- Example:
["/plugin-assets/Chart@1.0.0/chart.js"]
Images
- Type:
Dictionary<string, string> - Description: Image name → URL mapping
- Example:
{ ["icon"] = "/plugin-assets/Clock@1.0.0/icon.png" }
Fonts
- Type:
List<string> - Description: Font file paths or URLs
- Example:
["/plugin-assets/Clock@1.0.0/custom-font.woff2"]
Source
- Type:
AssetSource - Description: Where assets are located
- Values:
AssetSource.Embedded- Embedded in assembly (default)AssetSource.Cdn- External CDN URLsAssetSource.Mixed- Combination of both
Example:
public override PluginAssets GetAssets()
{
return new PluginAssets
{
CssFiles = new List<string>
{
"/plugin-assets/Clock@1.0.0/clock-widget.css"
},
Images = new Dictionary<string, string>
{
["icon"] = "/plugin-assets/Clock@1.0.0/icon.svg"
},
Source = AssetSource.Embedded
};
}AssetSource
Enum defining where plugin assets are located.
public enum AssetSource
{
Embedded, // Assets embedded in assembly
Cdn, // External CDN URLs
Mixed // Combination of both
}Usage:
new PluginAssets
{
CssFiles = new List<string> { "https://cdn.example.com/widget.css" },
Source = AssetSource.Cdn
}VNode API
VElement
Virtual DOM element node.
public class VElement : VNode
{
public string Tag { get; set; }
public Dictionary<string, string> Props { get; set; }
public List<VNode> Children { get; set; }
public string? Key { get; set; }
// Constructors
public VElement(string tag)
public VElement(string tag, Dictionary<string, string> props)
public VElement(string tag, string textContent)
public VElement(string tag, Dictionary<string, string> props, string textContent)
public VElement(string tag, Dictionary<string, string> props, VNode[] children)
public VElement(string tag, VNode[] children)
}Constructors
VElement(string tag)
Creates an element with no props or children.
var div = new VElement("div");
// <div></div>VElement(string tag, Dictionary<string, string> props)
Creates an element with props, no children.
var div = new VElement("div", new Dictionary<string, string>
{
["className"] = "container",
["id"] = "main"
});
// <div className="container" id="main"></div>VElement(string tag, string textContent)
Creates an element with text content.
var span = new VElement("span", "Hello World");
// <span>Hello World</span>VElement(string tag, Dictionary<string, string> props, string textContent)
Creates an element with props and text content.
var button = new VElement("button",
new Dictionary<string, string> { ["className"] = "btn" },
"Click Me"
);
// <button className="btn">Click Me</button>VElement(string tag, Dictionary<string, string> props, VNode[] children)
Creates an element with props and child nodes.
var div = new VElement("div",
new Dictionary<string, string> { ["className"] = "card" },
new VNode[]
{
new VElement("h1", "Title"),
new VElement("p", "Content")
}
);
// <div className="card"><h1>Title</h1><p>Content</p></div>VElement(string tag, VNode[] children)
Creates an element with child nodes, no props.
var ul = new VElement("ul", new VNode[]
{
new VElement("li", "Item 1"),
new VElement("li", "Item 2")
});
// <ul><li>Item 1</li><li>Item 2</li></ul>VText
Virtual DOM text node.
public class VText : VNode
{
public string Content { get; set; }
public VText(string content)
}Usage:
var text = new VText("Hello World");
// Hello WorldNote: Usually not needed - use VElement(tag, textContent) instead.
Fragment
Virtual DOM fragment (multiple root elements).
public class Fragment : VNode
{
public List<VNode> Children { get; set; }
public Fragment(VNode[] children)
}Usage:
var fragment = new Fragment(new VNode[]
{
new VElement("h1", "Title"),
new VElement("p", "Paragraph")
});
// <h1>Title</h1><p>Paragraph</p>Plugin Manager API
PluginManager
Service for managing plugin lifecycle.
public class PluginManager
{
public void AutoDiscover()
public void Register(IMinimactPlugin plugin)
public IMinimactPlugin? GetPlugin(string name)
public IMinimactPlugin? GetPlugin(string name, string version)
public IMinimactPlugin? GetLatestCompatibleVersion(string name, string minVersion)
public VNode? RenderPlugin(string name, object state)
public IReadOnlyDictionary<string, IMinimactPlugin> GetAllPlugins()
public IReadOnlyDictionary<string, IMinimactPlugin>? GetPluginVersions(string name)
public bool IsPluginRegistered(string name)
public bool Unregister(string name)
}Methods
AutoDiscover
void AutoDiscover()Scans all loaded assemblies for plugins with [MinimactPlugin] attribute.
Example:
var pluginManager = serviceProvider.GetRequiredService<PluginManager>();
pluginManager.AutoDiscover();Register
void Register(IMinimactPlugin plugin)Registers a plugin instance explicitly.
Parameters:
plugin- Plugin instance to register
Example:
var clockPlugin = new ClockPlugin();
pluginManager.Register(clockPlugin);GetPlugin (by name)
IMinimactPlugin? GetPlugin(string name)Gets the latest version of a plugin by name.
Parameters:
name- Plugin name
Returns: Plugin instance or null if not found
Example:
var plugin = pluginManager.GetPlugin("Clock");GetPlugin (by name and version)
IMinimactPlugin? GetPlugin(string name, string version)Gets a specific version of a plugin.
Parameters:
name- Plugin nameversion- Plugin version (e.g., "1.0.0")
Returns: Plugin instance or null if not found
Example:
var plugin = pluginManager.GetPlugin("Clock", "1.0.0");GetLatestCompatibleVersion
IMinimactPlugin? GetLatestCompatibleVersion(string name, string minVersion)Gets the latest version compatible with the specified minimum version.
Parameters:
name- Plugin nameminVersion- Minimum required version
Returns: Latest compatible plugin instance or null
Example:
// Gets latest 1.x.x version (not 2.0.0)
var plugin = pluginManager.GetLatestCompatibleVersion("Clock", "1.0.0");RenderPlugin
VNode? RenderPlugin(string name, object state)Validates state and renders a plugin.
Parameters:
name- Plugin namestate- State object
Returns: VNode or null if plugin not found or validation fails
Example:
var state = new ClockState { Hours = 14, Minutes = 30, Seconds = 0 };
var vnode = pluginManager.RenderPlugin("Clock", state);GetAllPlugins
IReadOnlyDictionary<string, IMinimactPlugin> GetAllPlugins()Gets all registered plugins (latest versions only).
Returns: Dictionary of plugin name → plugin instance
Example:
var allPlugins = pluginManager.GetAllPlugins();
foreach (var (name, plugin) in allPlugins)
{
Console.WriteLine($"{name} v{plugin.Version}");
}GetPluginVersions
IReadOnlyDictionary<string, IMinimactPlugin>? GetPluginVersions(string name)Gets all versions of a specific plugin.
Parameters:
name- Plugin name
Returns: Dictionary of version → plugin instance, or null if not found
Example:
var versions = pluginManager.GetPluginVersions("Clock");
// { "1.0.0" => ClockPlugin, "1.1.0" => ClockPlugin, "2.0.0" => ClockPlugin }IsPluginRegistered
bool IsPluginRegistered(string name)Checks if a plugin is registered.
Parameters:
name- Plugin name
Returns: True if registered, false otherwise
Example:
if (pluginManager.IsPluginRegistered("Clock"))
{
// Use plugin
}Unregister
bool Unregister(string name)Unregisters a plugin.
Parameters:
name- Plugin name
Returns: True if unregistered, false if not found
Example:
pluginManager.Unregister("Clock");JSON Schema Validator
JsonSchemaValidator
Validates JSON objects against JSON Schema (Draft 7).
public static class JsonSchemaValidator
{
public static bool Validate(object state, string schemaJson)
}Supported Schema Features
Type Validation:
"type": "null""type": "boolean""type": "number""type": "integer""type": "string""type": "array""type": "object"
Object Validation:
"required": ["prop1", "prop2"]"properties": { "prop1": {...} }
String Validation:
"minLength": 5"maxLength": 100"enum": ["value1", "value2"]
Number Validation:
"minimum": 0"maximum": 100
Array Validation:
"minItems": 1"maxItems": 10"items": {...}
Example:
var schema = @"{
""type"": ""object"",
""properties"": {
""hours"": { ""type"": ""integer"", ""minimum"": 0, ""maximum"": 23 }
},
""required"": [""hours""]
}";
var state = new { hours = 14 };
bool isValid = JsonSchemaValidator.Validate(state, schema); // true
var invalidState = new { hours = 25 };
bool isValid2 = JsonSchemaValidator.Validate(invalidState, schema); // falseJsonSchemaGenerator
Generates JSON Schema from C# types.
public static class JsonSchemaGenerator
{
public static string Generate<T>()
public static string Generate(Type type)
}Supported Types
| C# Type | JSON Schema Type |
|---|---|
string | "type": "string" |
int, long, short | "type": "integer" |
float, double, decimal | "type": "number" |
bool | "type": "boolean" |
T[], List<T> | "type": "array" |
| Class | "type": "object" |
Example:
public class ClockState
{
public int Hours { get; set; }
public int Minutes { get; set; }
public string? Theme { get; set; }
}
var schema = JsonSchemaGenerator.Generate<ClockState>();
// {
// "$schema": "http://json-schema.org/draft-07/schema#",
// "type": "object",
// "properties": {
// "hours": { "type": "integer" },
// "minutes": { "type": "integer" },
// "theme": { "type": "string" }
// },
// "required": ["hours", "minutes"]
// }Configuration API
MinimactOptions
Configuration options for the Minimact framework.
public class MinimactOptions
{
// Existing options...
public bool AutoDiscoverPlugins { get; set; } = true;
public List<IMinimactPlugin> ExplicitPlugins { get; set; } = new();
public PluginAssetOptions PluginAssets { get; set; } = new();
public MinimactOptions RegisterPlugin<T>() where T : IMinimactPlugin, new()
public MinimactOptions RegisterPlugin(IMinimactPlugin plugin)
}Properties
AutoDiscoverPlugins
- Type:
bool - Default:
true - Description: Enable automatic plugin discovery via reflection
ExplicitPlugins
- Type:
List<IMinimactPlugin> - Default: Empty list
- Description: Plugins registered explicitly (bypasses auto-discovery)
PluginAssets
- Type:
PluginAssetOptions - Default: New instance with defaults
- Description: Plugin asset serving configuration
Methods
RegisterPlugin<T>
MinimactOptions RegisterPlugin<T>() where T : IMinimactPlugin, new()Registers a plugin by type.
Example:
builder.Services.AddMinimact(options =>
{
options.RegisterPlugin<ClockPlugin>();
});RegisterPlugin (instance)
MinimactOptions RegisterPlugin(IMinimactPlugin plugin)Registers a plugin instance.
Example:
builder.Services.AddMinimact(options =>
{
var clockPlugin = new ClockPlugin();
options.RegisterPlugin(clockPlugin);
});PluginAssetOptions
Configuration for plugin asset serving.
public class PluginAssetOptions
{
public string BasePath { get; set; } = "/plugin-assets";
public bool VersionAssetUrls { get; set; } = true;
public int CacheDuration { get; set; } = 86400;
}Properties
BasePath
- Type:
string - Default:
"/plugin-assets" - Description: Base URL path for serving plugin assets
Example:
options.PluginAssets.BasePath = "/assets/plugins";
// Assets served at: /assets/plugins/Clock@1.0.0/clock.cssVersionAssetUrls
- Type:
bool - Default:
true - Description: Include version in asset URLs
Example:
options.PluginAssets.VersionAssetUrls = true;
// URL: /plugin-assets/Clock@1.0.0/clock.css
options.PluginAssets.VersionAssetUrls = false;
// URL: /plugin-assets/Clock/clock.cssCacheDuration
- Type:
int - Default:
86400(24 hours) - Description: Cache duration in seconds
Example:
options.PluginAssets.CacheDuration = 3600; // 1 hourExtension Methods
AddMinimact
Registers Minimact services including plugin system.
public static IServiceCollection AddMinimact(this IServiceCollection services)
public static IServiceCollection AddMinimact(
this IServiceCollection services,
Action<MinimactOptions> configure)Example:
// Default configuration
builder.Services.AddMinimact();
// Custom configuration
builder.Services.AddMinimact(options =>
{
options.AutoDiscoverPlugins = true;
options.RegisterPlugin<ClockPlugin>();
options.PluginAssets.CacheDuration = 3600;
});UseMinimact
Adds Minimact middleware including plugin asset serving.
public static IApplicationBuilder UseMinimact(
this IApplicationBuilder app,
string manifestPath = "./Generated/routes.json")Example:
var app = builder.Build();
app.UseMinimact();
app.Run();UsePluginAssets
Adds plugin asset serving middleware.
public static IApplicationBuilder UsePluginAssets(
this IApplicationBuilder builder,
string basePath = "/plugin-assets",
bool versionAssetUrls = true,
int cacheDuration = 86400)Example:
app.UsePluginAssets(
basePath: "/assets/plugins",
versionAssetUrls: true,
cacheDuration: 3600
);Note: Usually not needed - UseMinimact() calls this automatically.
TypeScript Types
Client-Side State Types
Generate TypeScript types from C# state classes:
# Manual generation (planned for Phase 6)
dotnet minimact-plugin generate-typesExample Output:
// Generated from ClockState.cs
export interface ClockState {
hours: number;
minutes: number;
seconds: number;
date: string;
theme: 'light' | 'dark';
timezone: string;
showTimezone: boolean;
showSeconds: boolean;
use24Hour: boolean;
}Usage in TSX:
import { ClockState } from './generated/plugin-types';
const [time, setTime] = useState<ClockState>({
hours: 14,
minutes: 30,
seconds: 0,
date: 'October 29, 2025',
theme: 'light',
timezone: 'UTC',
showTimezone: false,
showSeconds: true,
use24Hour: true
});
<Plugin name="Clock" state={time} />Error Handling
Common Exceptions
PluginNotFoundException
Thrown when a requested plugin is not found.
var plugin = pluginManager.GetPlugin("NonExistent");
if (plugin == null)
{
throw new PluginNotFoundException("NonExistent");
}StateValidationException
Thrown when plugin state fails validation.
if (!plugin.ValidateState(state))
{
throw new StateValidationException(plugin.Name, state);
}AssetNotFoundException
Thrown when a plugin asset cannot be found.
// Middleware returns 404 if asset not found
GET /plugin-assets/Clock@1.0.0/missing.css → 404 Not FoundBest Practices
1. Always Use Generic Base Class
Good:
public class ClockPlugin : MinimactPlugin<ClockState>
{
protected override VNode RenderTyped(ClockState state)
{
// Type-safe access to state
return new VElement("div", $"{state.Hours}:{state.Minutes}");
}
}Bad:
public class ClockPlugin : MinimactPluginBase
{
public override VNode Render(object state)
{
var clockState = (ClockState)state; // Manual casting
return new VElement("div", $"{clockState.Hours}:{clockState.Minutes}");
}
}2. Embed Assets in Assembly
Good:
<ItemGroup>
<EmbeddedResource Include="assets\**\*" />
</ItemGroup>Bad:
// Relying on external CDN (adds latency)
new PluginAssets
{
CssFiles = new List<string> { "https://cdn.example.com/widget.css" },
Source = AssetSource.Cdn
}3. Version Your Assets
Good:
CssFiles = new List<string>
{
"/plugin-assets/Clock@1.0.0/clock.css"
}Bad:
CssFiles = new List<string>
{
"/plugin-assets/Clock/clock.css" // No version
}4. Use JSON Schema Validation
Good:
// Auto-generated from ClockState type
public override string GetStateSchema()
{
return JsonSchemaGenerator.Generate<ClockState>();
}Bad:
// No validation
public override bool ValidateState(object state)
{
return true; // Accepts anything
}Version Compatibility
Semver Rules
MAJOR.MINOR.PATCH
- MAJOR: Breaking changes (incompatible state contract)
- MINOR: New features (backward compatible)
- PATCH: Bug fixes (backward compatible)
Example:
1.0.0→1.1.0✅ Compatible1.0.0→2.0.0❌ Breaking change1.5.0→1.5.1✅ Compatible
Multi-Version Support
Minimact can load multiple versions of the same plugin:
// App depends on Clock@1.0.0
<Plugin name="Clock" state={time} />
// Another plugin depends on Clock@2.0.0
// Both versions loaded side-by-sidePerformance Considerations
Template-Based Rendering
Plugins leverage Minimact's Template Patch System for instant updates:
- First Render: Plugin renders full VNode → Server sends HTML
- State Change: Template patches applied (0ms latency)
- Server Verification: Confirms or corrects in background
Asset Caching
Assets are cached with aggressive headers:
Cache-Control: public, max-age=86400
ETag: "Clock-1.0.0-12345678"First Request: ~5ms (read from assembly) Cached Request: 0ms (304 Not Modified)
