Skip to content

Plugin Architecture

Understanding the nopCommerce plugin architecture is essential for building well-integrated extensions.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                     Nop.Web (Presentation)                   │
│  ┌─────────────────────────────────────────────────────────┐│
│  │                   Nop.Web.Framework                     ││
│  │  ┌───────────────┐  ┌───────────────┐  ┌─────────────┐ ││
│  │  │   Your Plugin │  │ Other Plugins │  │  Widgets    │ ││
│  │  └───────────────┘  └───────────────┘  └─────────────┘ ││
│  └─────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────┤
│                     Nop.Services (Business Logic)           │
├─────────────────────────────────────────────────────────────┤
│                     Nop.Data (Data Access)                  │
├─────────────────────────────────────────────────────────────┤
│                     Nop.Core (Domain Models)                │
└─────────────────────────────────────────────────────────────┘

Core Libraries

Nop.Core

The foundation layer containing:

  • Domain entities (Product, Customer, Order, etc.)
  • Interfaces for core functionality
  • Configuration classes
  • Caching abstractions
  • Common utilities

Nop.Data

Data access layer providing:

  • Entity Framework Core DbContext
  • Repository pattern implementation
  • Database migrations
  • Data providers (SQL Server, MySQL, PostgreSQL)

Nop.Services

Business logic layer with:

  • Service interfaces and implementations
  • Event publishing/handling
  • Caching strategies
  • Logging and auditing

Nop.Web.Framework

Web-specific functionality:

  • Base controllers
  • Action filters
  • Tag helpers
  • MVC infrastructure

Virtual Service Methods (New in 4.90)

nopCommerce 4.90 introduces virtual service methods for cleaner extensibility. This allows you to override specific service behaviors without replacing entire service implementations.

Why Virtual Methods?

Virtual methods reduce boilerplate code and make customizations:

  • Less error-prone - Override only what you need
  • More maintainable - Smaller, focused changes
  • Upgrade-friendly - Core updates won't break your overrides

Overriding a Service Method

csharp
// Override specific behavior in a core service
public class CustomProductService : ProductService
{
    public CustomProductService(
        ICacheKeyService cacheKeyService,
        IRepository<Product> productRepository,
        // ... other dependencies
    ) : base(cacheKeyService, productRepository /* ... */)
    {
    }

    // Override just the method you need to customize
    public override async Task<IList<Product>> GetProductsByIdsAsync(int[] productIds)
    {
        // Add custom logic before
        var products = await base.GetProductsByIdsAsync(productIds);
        // Add custom logic after
        return products;
    }
}

Registering the Override

csharp
public class PluginStartup : INopStartup
{
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        // Replace the default service with your custom implementation
        services.AddScoped<IProductService, CustomProductService>();
    }

    public int Order => 3000; // Higher than core registration
}

Plugin Discovery

nopCommerce discovers plugins at application startup:

csharp
// 1. Scans /Plugins directory for plugin.json files
// 2. Loads plugin assemblies dynamically
// 3. Registers plugin services via DI
// 4. Initializes plugin databases (if any)

plugin.json Structure

json
{
  "Group": "Payment methods",
  "FriendlyName": "My Payment Plugin",
  "SystemName": "MyCompany.MyPaymentPlugin",
  "Version": "1.0.0",
  "SupportedVersions": ["4.90"],
  "Author": "Your Company",
  "DisplayOrder": 1,
  "FileName": "MyCompany.MyPaymentPlugin.dll",
  "Description": "A custom payment plugin"
}

Dependency Injection

Plugins register services using the INopStartup interface:

csharp
public class PluginStartup : INopStartup
{
    public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
    {
        // Register your services
        services.AddScoped<IMyService, MyService>();
    }

    public void Configure(IApplicationBuilder application)
    {
        // Configure middleware if needed
    }

    // Lower numbers execute first
    public int Order => 3000;
}

Service Lifetimes

  • AddScoped - Created once per request (most common)
  • AddTransient - Created each time requested
  • AddSingleton - Single instance for application lifetime

Plugin Base Classes

BasePlugin

The foundation for all plugins:

csharp
public class MyPlugin : BasePlugin
{
    public override string GetConfigurationPageUrl()
    {
        return "/Admin/MyPlugin/Configure";
    }
    
    public override async Task InstallAsync()
    {
        // Add settings, locale resources, etc.
        await base.InstallAsync();
    }
    
    public override async Task UninstallAsync()
    {
        // Remove settings, locale resources, etc.
        await base.UninstallAsync();
    }
}

Specialized Base Classes

Base ClassPurpose
BasePluginStandard plugins
IPaymentMethodPayment processing
IShippingRateComputationMethodShipping calculation
ITaxProviderTax calculation
IWidgetPluginWidget rendering
IExternalAuthenticationMethodOAuth/External auth

Event System

nopCommerce uses a publish-subscribe event model:

csharp
// Subscribe to events
public class MyEventConsumer : IConsumer<OrderPlacedEvent>
{
    public async Task HandleEventAsync(OrderPlacedEvent eventMessage)
    {
        var order = eventMessage.Order;
        // Process the order event
    }
}

// Events are published automatically for CRUD operations
// Or manually via IEventPublisher

Widget Zones

Plugins can render content in predefined zones:

csharp
public class MyWidgetPlugin : BasePlugin, IWidgetPlugin
{
    public Task<IList<string>> GetWidgetZonesAsync()
    {
        return Task.FromResult<IList<string>>(new List<string>
        {
            PublicWidgetZones.HomepageTop,
            PublicWidgetZones.ProductDetailsTop
        });
    }

    public Type GetWidgetViewComponent(string widgetZone)
    {
        return typeof(MyWidgetViewComponent);
    }
}

Next Steps

Released under the nopCommerce Public License.