๐ Plugin Development Guide
Complete guide to creating, installing, and configuring plugins for BlazorBlueprint
๐ฏ Overview
BlazorBlueprint uses a modular plugin system that allows you to extend functionality without modifying core code. Plugins are self-contained, independently versioned components.
๐ IPlugin Interface
All plugins must implement the IPlugin interface. For convenience, you can inherit from PluginBase which provides helper methods.
Interface Definition
Here's the complete IPlugin interface with comments explaining each member:
public interface IPlugin
{
// ========================================================================
// REQUIRED PROPERTIES
// ========================================================================
// Unique identifier for this plugin (e.g., "BlazorBlueprint.Plugins.MyPlugin")
// Used internally to identify the plugin in settings and databases
// Must be unique across all plugins
string Id { get; }
// Display name shown in PluginManagement UI (e.g., "My Awesome Plugin")
// This is what users see when browsing available plugins
string Name { get; }
// Plugin version using semantic versioning (e.g., "1.0.0")
// Follows Major.Minor.Patch format
string Version { get; }
// Description of what the plugin does
// Shown in PluginManagement to help users understand the plugin's purpose
string Description { get; }
// Plugin author name (e.g., "Your Name" or "Your Company")
// Shown in PluginManagement for attribution
string Author { get; }
// Category helps organize plugins in the UI (e.g., "Communication", "Utility")
// Used for filtering and grouping in PluginManagement
string Category { get; }
// Whether this plugin requires a premium license
// Set to true if plugin requires payment or subscription
bool IsPremium { get; }
// Whether the plugin is currently enabled for the site
// Managed by PluginManager - don't set this manually
bool IsEnabled { get; set; }
// ========================================================================
// LIFECYCLE METHODS
// ========================================================================
// Called during application startup to register plugin services
// Register your services here using dependency injection
// Example: services.AddScoped<IMyService, MyService>();
Task RegisterServicesAsync(IServiceCollection services, IConfiguration configuration);
// Called during app configuration (after service registration)
// Use this to configure middleware, SignalR hubs, etc.
// Example: app.MapHub<MyHub>("/hubs/myhub");
Task ConfigureAppAsync(WebApplication app);
// Called after application is fully started
// Use this for initialization that requires all services to be ready
// Example: Initialize background workers, cache warmup, etc.
Task OnApplicationStartupAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken = default);
// ========================================================================
// INTEGRATION METHODS
// ========================================================================
// Returns navigation menu items for the main sidebar
// These links appear in the navigation menu for all users
// Return empty if plugin doesn't need navigation items
IEnumerable<PluginNavItem> GetNavigationItems(IServiceProvider? serviceProvider = null);
// Returns admin pages that appear in PluginManagement
// These pages are shown as quick action buttons/links
// Return empty if plugin doesn't have admin pages
IEnumerable<PluginAdminPage> GetAdminPages();
// Returns routes (pages) your plugin provides
// Each route maps a URL to a Blazor component
// Routes are automatically registered when plugin is enabled
IEnumerable<PluginRoute> GetRoutes();
// Returns extensions to OrganisationPublicSettings schema (optional)
// Used if your plugin needs to extend the Site entity
// Return empty dictionary if not needed
Dictionary<string, Type> GetOrganisationSettingExtensions();
// ========================================================================
// SITE SETUP
// ========================================================================
// Called when a new site is created
// Use this to set up default data, create default settings, etc.
// Returns true if setup succeeded, false otherwise
// Example: Create default chat groups, initialize AI models, etc.
Task<bool> SetupNewOrganisationAsync(Organisation organisation, string adminUserId = null, IServiceProvider? serviceProvider = null);
// ========================================================================
// LAYOUT COMPONENTS
// ========================================================================
// Returns component type names that should be rendered in the main layout
// These components appear on all pages (unless filtered)
// Example: Floating chat button, cookie consent banner, etc.
// Return empty if plugin doesn't need layout components
IEnumerable<string> GetLayoutComponents(IServiceProvider? serviceProvider = null);
}๐ก Tip: Instead of implementing IPlugin directly, inherit from PluginBase. It provides default implementations for optional methods and helper methods that make plugin development much easier!
๐ Creating Your First Plugin
Step 1: Create Plugin Project
# Create new Razor Class Library project
dotnet new razorclasslib -n BlazorBlueprint.Plugins.MyPlugin
cd BlazorBlueprint.Plugins.MyPlugin
# Add project references
dotnet add reference ..\..\Core\BlazorBlueprint.Application\BlazorBlueprint.Application.csproj
dotnet add reference ..\..\Core\BlazorBlueprint.Domain\BlazorBlueprint.Domain.csprojStep 2: Implement Plugin Class (Recommended: Use PluginBase)
๐ก Tip: Inherit from PluginBase instead of implementing IPlugin directly. It provides helper methods and default implementations.
// Import necessary namespaces
using BlazorBlueprint.Application.Plugins; // PluginBase class
using BlazorBlueprint.Application.Interfaces; // IPlugin interface
using BlazorBlueprint.Domain.Constants; // ApplicationRoleConstants for authorization
using Microsoft.Extensions.DependencyInjection; // IServiceCollection for DI
using Microsoft.Extensions.Configuration; // IConfiguration for settings
namespace BlazorBlueprint.Plugins.MyPlugin;
/// <summary>
/// Main plugin class - inherits from PluginBase for convenience
/// PluginBase provides helper methods and default implementations
/// </summary>
public class MyPlugin : PluginBase
{
// ========================================================================
// REQUIRED PROPERTIES - These must be overridden
// ========================================================================
/// <summary>
/// Unique identifier for this plugin.
/// Convention: "BlazorBlueprint.Plugins.{PluginName}"
/// This ID must be unique across all plugins.
/// </summary>
public override string Id => "BlazorBlueprint.Plugins.MyPlugin";
/// <summary>
/// Display name shown in PluginManagement UI.
/// This is what users see in the plugin list.
/// </summary>
public override string Name => "My Plugin";
/// <summary>
/// Plugin version using semantic versioning (Major.Minor.Patch).
/// Update this when you release new versions.
/// </summary>
public override string Version => "1.0.0";
/// <summary>
/// Description of what the plugin does.
/// Shown in PluginManagement to help users understand the plugin.
/// </summary>
public override string Description => "A sample plugin that demonstrates basic functionality";
/// <summary>
/// Plugin author name.
/// Your name or organization name.
/// </summary>
public override string Author => "Your Name";
// ========================================================================
// OPTIONAL PROPERTIES - These have default implementations in PluginBase
// ========================================================================
/// <summary>
/// Category helps organize plugins in the UI.
/// Examples: "Communication", "E-commerce", "Social", "Utility"
/// </summary>
public override string Category => "Utility";
/// <summary>
/// Whether this is a premium/paid plugin.
/// Set to true if the plugin requires a license or subscription.
/// </summary>
public override bool IsPremium => false;
// ========================================================================
// REGISTER SERVICES (REQUIRED METHOD)
// ========================================================================
/// <summary>
/// Registers services your plugin needs in dependency injection.
/// This is called when the application starts up.
/// Services registered here can be injected into your pages and components.
/// </summary>
public override async Task RegisterServicesAsync(IServiceCollection services, IConfiguration configuration)
{
// Register your plugin's services here
// AddScoped means a new instance is created for each HTTP request
// Other options: AddSingleton (one instance for entire app), AddTransient (new instance every time)
services.AddScoped<IMyService, MyService>();
// You can also register:
// - Background services: services.AddHostedService<MyBackgroundService>();
// - Options/configuration: services.Configure<MyOptions>(options => { ... });
// - Repositories, other services, etc.
await Task.CompletedTask; // Required for async method
}
// ========================================================================
// REGISTER ROUTES (REQUIRED METHOD)
// ========================================================================
/// <summary>
/// Defines the routes (pages) your plugin provides.
/// Each route maps a URL to a Blazor component/page.
/// Routes are automatically registered when the plugin is enabled.
/// </summary>
public override IEnumerable<PluginRoute> GetRoutes()
{
// 1. Main plugin landing page - the default admin page
// This is where users go when they click "Open Plugin" in PluginManagement
// Route: /admin/plugin/myplugin
yield return new PluginRoute
{
Route = "/admin/plugin/myplugin", // The URL path
ComponentType = typeof(Web.Pages.Admin.Index), // The Razor component to render
RequiresAuthentication = true, // Must be logged in
RequiredRole = ApplicationRoleConstants.ORGANISATIONADMIN // Must have ORGANISATIONADMIN role
};
// 2. Public route - accessible to everyone
// This page appears in the main navigation menu
// Route: /plugin/myplugin/mypage
// CreatePublicRoute is a helper method from PluginBase
// It automatically constructs the route: /plugin/{pluginname}/mypage
yield return CreatePublicRoute("mypage", typeof(Web.Pages.MyPage));
// 3. Admin route - settings page
// Only ORGANISATIONADMIN users can access this
// Route: /admin/plugin/myplugin/settings
// CreateAdminRoute is a helper that constructs: /admin/plugin/{pluginname}/settings
yield return CreateAdminRoute("settings", typeof(Web.Pages.Admin.Settings), ApplicationRoleConstants.ORGANISATIONADMIN);
}
// ========================================================================
// NAVIGATION ITEMS (OPTIONAL METHOD)
// ========================================================================
/// <summary>
/// Adds items to the main navigation menu.
/// These links appear in the sidebar navigation for all users (or based on auth requirements).
/// This method is optional - omit it if your plugin doesn't need navigation items.
/// </summary>
public override IEnumerable<PluginNavItem> GetNavigationItems(IServiceProvider? serviceProvider = null)
{
// IMPORTANT: Choose ONE of the following options, not both!
// If you yield return multiple items with the same title, you'll get duplicates.
// ===== OPTION 1: Simple navigation item - direct link =====
// This creates a single menu item that links directly to your page
yield return CreateNavItem(
title: "My Plugin", // Text shown in menu
href: "/plugin/myplugin/mypage", // URL to navigate to
icon: "Settings", // Icon name (FluentUI icon)
order: 50, // Position in menu (lower = higher up)
requiresAuth: false // Whether user must be logged in
);
}
// ========================================================================
// ADMIN PAGES (OPTIONAL METHOD)
// ========================================================================
/// <summary>
/// Defines admin pages that appear in PluginManagement and your plugin's index page.
/// These pages are shown as quick action buttons/links.
/// This method is optional - omit it if your plugin doesn't have admin pages.
/// </summary>
public override IEnumerable<PluginAdminPage> GetAdminPages()
{
// These admin pages will be accessible from:
// 1. PluginManagement page - shown as links when you click "Open Plugin"
// 2. Plugin's main index page (/admin/plugin/myplugin) - shown as action buttons
// 3. Direct navigation - users can go directly to /admin/plugin/myplugin/{pagename}
// Settings page - order 100 (appears first)
yield return CreateAdminPage(
title: "Settings", // Display name
pageName: "settings", // URL segment (becomes /admin/plugin/myplugin/settings)
icon: "Settings", // Icon name for display
order: 100 // Display order (lower = appears first)
);
}
}๐ก Why PluginBase? It provides helper methods like CreatePublicRoute(), CreateAdminRoute(), CreateNavItem(), and CreateAdminPage() that handle route conventions automatically. Much easier than manually constructing routes!
Step 3: Add to Solution
# Add plugin to solution
dotnet sln add BlazorBlueprint.Plugins.MyPluginStep 4: Reference in Web Project
Edit Web/BlazorBlueprint.Web/BlazorBlueprint.Web.csproj:
<ItemGroup>
<ProjectReference Include="..\..\Plugins\BlazorBlueprint.Plugins.MyPlugin\BlazorBlueprint.Plugins.MyPlugin.csproj" />
</ItemGroup>โ That's it! Your plugin will be automatically discovered and loaded when you run the application.
๐ฆ Installation & Configuration
Method 1: Project Reference (Development)
For development, add the plugin as a project reference in Web/BlazorBlueprint.Web/BlazorBlueprint.Web.csproj:
<ItemGroup>
<ProjectReference Include="..\..\Plugins\MyPlugin\MyPlugin.csproj" />
</ItemGroup>Method 2: Folder-Based (Production)
For production or distribution, place plugin DLLs in the Web/Plugins/ folder:
Web/
โโโ Plugins/
โโโ MyPlugin/
โโโ MyPlugin.dll
โโโ MyPlugin.deps.json
โโโ [dependencies]Plugins in this folder are discovered at startup. Use "Rescan Plugins Folder" in Plugin Management to discover new plugins without restarting.
Enabling/Disabling Plugins
- Navigate to Plugin Management
- Find your plugin in the list
- Click "Install" to set it up for your site
- Use toggle to enable/disable the plugin
โ Best Practices
1. Plugin Isolation
- Don't modify core code - plugins should be self-contained
- Use interfaces for communication with core services
- Store all settings in
OrganisationPublicSettings.PluginSettings
2. Error Handling
- Wrap all plugin operations in try-catch blocks
- Don't let plugin errors crash the application
- Log errors with appropriate log levels
3. Route Conventions
- Public Routes:
/plugin/{pluginname}/{route} - Admin Routes:
/admin/plugin/{pluginname}/{route} - Use kebab-case for route names
4. Service Lifetime
- Use
Scopedfor request-specific services - Use
Singletonfor shared state or cache - Create scopes in background services using
IServiceProvider
๐ Real-World Examples
LiveChat Plugin
The LiveChat Plugin is a fully functional production plugin located at Plugins/BlazorBlueprint.Plugins.LiveChat/.
It demonstrates all common plugin features:
- โ Navigation menu items (with children)
- โ Admin pages with different authorization levels
- โ Public and authenticated routes
- โ SignalR hub with real-time communication
- โ Plugin settings (per-site configuration)
- โ Background services
- โ Service registration and dependency injection
- โ Site setup and initialization
- โ Layout components (floating chat button)
๐ก Perfect Reference: Study the LiveChat plugin's code to see how a production plugin is structured. It's well-commented and follows all best practices!
Complete Documentation
For a detailed step-by-step tutorial with full code examples, see the Plugin Development Guide.
The guide includes:
- โ Complete "Greetings Plugin" example from scratch
- โ Service interface and implementation examples
- โ Public and admin page examples with full code
- โ Navigation menu integration
- โ Settings management
- โ Build and test instructions
๐ฆ Ready to Download?
Build custom plugins and extend Blazor Blueprint's functionality. Download the free Developer version or get Enterprise with premium plugins and commercial licensing.
๐ Open Source on GitHub โข Free for personal/non-commercial use โข Enterprise license (ยฃ399) required for commercial use โข Full source code included