Home / Blog

The Complete Guide to nopCommerce Plugin Development

Carlos Casalicchio · June 23, 2026 ·7 min read

Why nopCommerce Plugins Matter

nopCommerce powers over 60,000 online stores worldwide. It's the leading open-source e-commerce platform on .NET — and its plugin architecture is the engine behind that ecosystem. Every payment gateway integration, every shipping carrier, every widget, and every custom feature lives as a plugin.

If you understand how to build nopCommerce plugins, you can extend one of the most capable e-commerce platforms on the market — and build a business doing it. This guide covers everything from your first plugin scaffold to production deployment patterns.

We've been building nopCommerce plugins since 2009, with 38+ plugins in our catalog — including 12 Brazilian payment gateway integrations and the only complete PIX payment solution on the platform. This guide reflects what we've learned building plugins that process millions of transactions.

Plugin Architecture: The Foundation

The Plugin Interface Hierarchy

Every nopCommerce plugin starts with the IPlugin interface in Nop.Services.Plugins. Rather than making you implement everything from scratch, nopCommerce provides BasePlugin — an abstract class that handles the boilerplate — and a set of specialized interfaces for common plugin types:

Interface Purpose Example
IPaymentMethod Payment processing (authorize, capture, refund, void) PayPal, Stripe, PIX, MercadoPago
IShippingRateComputationMethod Calculate shipping rates from carriers UPS, FedEx, Correios (Brazil)
ITaxProvider Calculate tax rates by jurisdiction Avalara, TaxJar
IWidgetPlugin Render UI widgets in specific zones Live chat, newsletter popup, Trello cards
IExchangeRateProvider Fetch currency exchange rates ECB, Central Bank of Brazil
IDiscountRequirementRule Custom discount logic "Customer from Brazil gets 10% off"
IExternalAuthenticationMethod Social login and SSO Google, Facebook, Azure AD
IMultiFactorAuthenticationMethod MFA plugins (since 4.40) Google Authenticator, SMS OTP
IPickupPointProvider In-store pickup locations Local store network, locker systems
IMiscPlugin General-purpose extensions DNS management, analytics, integrations

Choose the most specific interface for your use case. IPaymentMethod gives you the full payment lifecycle — ProcessPaymentAsync, PostProcessPaymentAsync, RefundAsync, VoidAsync, CaptureAsync — while IMiscPlugin is the escape hatch for anything that doesn't fit a standard category.

Project Structure: Step by Step

1. Create the Class Library Project

Create a new Class Library project targeting .NET 9. The recommended naming convention is Nop.Plugin.{Group}.{Name}:

Nop.Plugin.Payments.PayPalCommerce
Nop.Plugin.Shipping.FedEx
Nop.Plugin.Widgets.WhatsApp
Nop.Plugin.Misc.GoDaddy

Place the project in the \Plugins directory at the solution root (not inside \Nop.Web\Plugins, which is the deployment target). Set the .csproj output path to $(SolutionDir)\Presentation\Nop.Web\Plugins\{PluginOutputDirectory} and enable CopyLocalLockFileAssemblies to ensure NuGet dependencies are copied to output.

2. Create plugin.json

Every plugin requires a plugin.json file with metadata:

{
  "Group": "Payment methods",
  "FriendlyName": "My Payment Gateway",
  "SystemName": "Payments.MyGateway",
  "Version": "1.00",
  "SupportedVersions": [ "4.90" ],
  "Author": "SplatDev",
  "DisplayOrder": 1,
  "FileName": "Nop.Plugin.Payments.MyGateway.dll",
  "Description": "Process payments via My Gateway"
}

The SystemName must match your assembly name. SupportedVersions determines which nopCommerce versions can install the plugin. If you upgrade nopCommerce and a plugin breaks, check this field first — many plugins only need their supported versions updated.

3. Implement the Plugin Class

Create a class that implements the appropriate plugin interface and derives from BasePlugin:

public class MyPaymentProcessor : BasePlugin, IPaymentMethod
{
    public override Task InstallAsync()
    {
        // Insert settings, locale resources, or database tables
        return base.InstallAsync();
    }

    public override Task UninstallAsync()
    {
        // Clean up settings, locale resources, or database tables
        return base.UninstallAsync();
    }

    public override Task UpdateAsync(string currentVersion, string targetVersion)
    {
        // Handle version migrations
        return base.UpdateAsync(currentVersion, targetVersion);
    }

    public Task<ProcessPaymentResult> ProcessPaymentAsync(ProcessPaymentRequest request)
    {
        // Your payment logic here
    }

    public override string GetConfigurationPageUrl()
    {
        return _nopUrlHelper.RouteUrl(MyDefaults.Route.Configuration);
    }
}

The InstallAsync, UninstallAsync, and UpdateAsync overrides are your lifecycle hooks. Always call the base implementation — base.InstallAsync() handles registration in the plugin system.

4. Add Configuration (Admin UI)

For plugins with settings, create an MVC controller, a model, and a Razor view:

  1. Controllers/{Name}Controller.cs — decorated with [AuthorizeAdmin] and [Area(AreaNames.ADMIN)]. Inherit from BasePluginController.
  2. Models/ConfigurationModel.cs — a plain class with your settings properties and [NopResourceDisplayName] attributes for localization.
  3. Views/Configure.cshtml — uses the _ConfigurePlugin layout. Also include _ViewImports.cshtml (copy from an existing plugin).

Register the configuration route in a RouteProvider.cs implementing IRouteProvider:

public class RouteProvider : IRouteProvider
{
    public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
    {
        endpointRouteBuilder.MapControllerRoute(
            name: MyDefaults.Route.Configuration,
            pattern: "Admin/MyGateway/Configure",
            defaults: new { controller = "MyGateway", action = "Configure", area = AreaNames.ADMIN });
    }

    public int Priority => 0;
}

Building a Payment Plugin: The Real-World Pattern

Payment plugins are the most common — and most complex — plugin type. Here's the production pattern we use at SplatDev:

Core Components

  1. Settings class: A class implementing ISettings with properties for API keys, URLs, modes (sandbox/production), and feature toggles. Store secrets in ISettingService — never hard-code credentials.
  2. Defaults class: A static class with constants — route names, system name, configuration URLs, API endpoints. Keeps magic strings out of your logic.
  3. Service class: Your actual integration logic — HTTP calls to the payment gateway, response parsing, webhook handling. Inject via DI and keep it testable (interface + implementation).
  4. Event consumers: Implement IConsumer<OrderPlacedEvent> and similar interfaces to react to store events — send transaction confirmations, trigger post-payment workflows.

Payment Flow: The Methods You Must Implement

Method When Called What It Should Do
ProcessPaymentAsync Customer clicks "Pay" Authorize or capture the payment. Return success/failure and transaction ID.
PostProcessPaymentAsync After payment processing completes Redirect to external gateway (PayPal, PagSeguro) or show a confirmation page.
RefundAsync Admin initiates refund Process the refund through the gateway. Return success/failure.
VoidAsync Admin voids an authorization Void the pending authorization. Only applicable if payment wasn't yet captured.
CaptureAsync Admin captures an authorized payment Capture a previously authorized amount. Common for authorization-then-capture flows.
GetPaymentMethodDescriptionAsync Checkout page renders Return the display name, description, and logo for the payment method.
GetAdditionalHandlingFeeAsync Order total calculation Return any extra fee the payment method charges, or zero.

Brazilian Payment Plugin Example: PIX

Brazil's PIX instant payment system requires a specific flow: generate a QR code with a payload (EMV string), display it to the customer, and listen for a webhook confirmation when the payment is received. Here's the pattern:

  1. ProcessPaymentAsync: Call the PSP (PagSeguro, MercadoPago, etc.) to create a PIX charge. Store the transaction ID (txid) and the EMV payload.
  2. PostProcessPaymentAsync: Render the QR code (generated from the EMV payload) and the "copy-paste" code. Set the payment status to Pending.
  3. Webhook handler: The PSP sends a webhook when PIX confirms payment. Update the order status and trigger OrderPaidEvent.

This is the pattern behind all 12 of SplatDev's Brazilian payment plugins — processing PIX, boleto, and installment-based credit card payments through PagSeguro, MercadoPago, Cielo, Banco Inter, Pagar.me, Rede, GetNet, PicPay, NuPay, Braspag, and InfinityPay.

Widgets: Adding UI Without Touching Themes

Widget plugins implement IWidgetPlugin and must specify which widget zones they render in:

public Task<IList<string>> GetWidgetZonesAsync()
{
    return Task.FromResult<IList<string>>(new List<string>
    {
        PublicWidgetZones.HomepageBeforeBestSellers,
        PublicWidgetZones.ProductDetailsAfterPictures
    });
}

Then implement GetPublicViewComponentAsync to return the widget's HTML. The widget system calls this method for each zone you've registered.

Common widget patterns: newsletter signup popups, live chat buttons, trust badges, cookie consent banners, WhatsApp floating buttons, recently viewed products, and social proof notifications.

Database Access: IRepository<T> and Migrations

nopCommerce uses a repository pattern via IRepository<T> for data access. For plugins that need custom tables:

  1. Define your entity class (must have an Id property).
  2. Create an ObjectContext that registers your entity with Entity Framework Core.
  3. In InstallAsync, use _dbContext.InstallAsync() to create the tables.
  4. In UninstallAsync, use _dbContext.UninstallAsync() to drop them.

For schema migrations between plugin versions, consider adding a MigrationManager to your UpdateAsync override that checks the current schema version and applies necessary ALTER TABLE statements. This is simpler than FluentMigrator for plugins and keeps your migration logic in one place.

Testing: Treat Your Plugin Like a Product

At SplatDev, we treat every plugin as a product with its own test suite:

  • Unit tests (xUnit): Test your service layer in isolation. Mock ISettingService, IRepository<T>, and any HTTP clients. Your payment logic, tax calculations, and shipping rate computations should have 100% unit test coverage on the business logic.
  • Integration tests: Spin up a test nopCommerce instance and verify your plugin installs, configures, and processes through the full lifecycle. Use sandbox/test credentials for payment gateways.
  • Localization tests: Verify all locale resources exist for every language you claim to support. Missing resource strings are the most common plugin bug.
  • StyleCop compliance: nopCommerce expects StyleCop compliance. Run it in your CI pipeline — it catches subtle issues before code review.

Common Pitfalls (And How to Avoid Them)

1. Not Calling base.InstallAsync()

If you override InstallAsync without calling the base, your plugin won't register with the plugin system and won't appear in the admin. Always include await base.InstallAsync() at the end of your override.

2. Hard-Coding Credentials

Store API keys, tokens, and secrets in ISettingService — never hard-code them in plugin.json or as class constants. The settings page should allow admins to configure these values, and they should be persisted encrypted when appropriate.

3. Ignoring Version Compatibility

nopCommerce versions break plugins. The SupportedVersions field in plugin.json isn't decorative — nopCommerce checks it before loading your plugin. When upgrading nopCommerce, test your plugin against the new version and update this field. Don't just bump the number without testing.

4. Blocking the Request Thread

All plugin methods are async for a reason. If your payment processor calls an external API, that call should be async. Blocking the thread with .Result or .Wait() will degrade performance under load and can cause deadlocks.

5. No Event Consumers

Plugins that only provide a configuration page are missing the point. The real power of nopCommerce plugins comes from event consumers — reacting to store events like OrderPlacedEvent, CustomerRegisteredEvent, or ShipmentDeliveredEvent. Wire your plugin into the store's business events, not just the admin UI.

nopCommerce 4.90: What's New for Plugin Developers

nopCommerce 4.90 targets .NET 9 and brings several improvements for plugin developers:

  • .NET 9 runtime: Your plugins run on the fastest .NET yet — benefiting from JIT improvements, GC dynamic adaptation, and loop optimizations automatically.
  • C# 13 support: Use params collections, ref struct interfaces, and partial properties in your plugin code.
  • Updated NuGet dependencies: Underlying packages (Autofac, FluentValidation, etc.) have been updated. Test your plugin's dependency tree.
  • IMultiFactorAuthenticationMethod: MFA plugins are now a first-class concept with their own interface.
  • Improved plugin template: The official nopCommerce plugin Visual Studio template has been updated for 4.90 — use it as your starting point.

Where to Go From Here

nopCommerce plugin development is a career-building skill. The nopCommerce marketplace is active and growing, and well-built plugins generate recurring revenue through updates and support. At SplatDev, plugins are our core business — our Store at store.splatdev.com carries 38+ plugins, and we've been a Gold Solutions Partner for years.

If you're building an e-commerce project on nopCommerce, you don't need to build everything from scratch. Our catalog covers Brazilian payments, shipping (Correios, JadLog, Braspress), ERP integrations, and custom functionality. If you need something we don't have, we build custom plugins too.

Ready to build or extend your nopCommerce store? Our team has been doing this since 2009 — longer than almost anyone in the ecosystem.

Work With Our nopCommerce Team

Or browse our plugin catalog: store.splatdev.com

Want more articles like this?

Subscribe for updates on .NET, Umbraco, nopCommerce, and software engineering.

Get in touch →