Skip to content

Frontend Integration

Learn how to add public-facing pages and widgets to the customer-side of your nopCommerce store.

Widget Zones

Widgets allow you to render content in predefined areas of the store.

Available Widget Zones

ZoneLocation
HomepageTopTop of homepage
HomepageBottomBottom of homepage
ProductDetailsTopAbove product details
ProductDetailsBottomBelow product details
CategoryDetailsTopTop of category page
ShoppingCartTopAbove shopping cart
CheckoutProgressBeforeBefore checkout steps

Implementing a Widget

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

    public Type GetWidgetViewComponent(string widgetZone)
    {
        return widgetZone switch
        {
            var z when z == PublicWidgetZones.HomepageTop => typeof(HomeBannerViewComponent),
            var z when z == PublicWidgetZones.ProductDetailsBottom => typeof(ProductReviewsViewComponent),
            _ => typeof(DefaultWidgetViewComponent)
        };
    }

    public bool HideInWidgetList => false;
}

Creating View Components

csharp
// Components/HomeBannerViewComponent.cs
using Microsoft.AspNetCore.Mvc;
using Nop.Web.Framework.Components;

public class HomeBannerViewComponent : NopViewComponent
{
    private readonly IMyService _myService;
    private readonly IStoreContext _storeContext;

    public HomeBannerViewComponent(
        IMyService myService,
        IStoreContext storeContext)
    {
        _myService = myService;
        _storeContext = storeContext;
    }

    public async Task<IViewComponentResult> InvokeAsync(string widgetZone, object additionalData)
    {
        var store = await _storeContext.GetCurrentStoreAsync();
        var banners = await _myService.GetActiveBannersAsync(store.Id);
        
        if (!banners.Any())
            return Content(string.Empty);

        var model = new BannerListModel
        {
            Banners = banners.Select(b => new BannerModel
            {
                ImageUrl = b.ImageUrl,
                Title = b.Title,
                Link = b.LinkUrl
            }).ToList()
        };

        return View("~/Plugins/Widgets.MyPlugin/Views/HomeBanner.cshtml", model);
    }
}

Widget View

html
<!-- Views/HomeBanner.cshtml -->
@model BannerListModel

<div class="banner-widget">
    <div class="banner-carousel">
        @foreach (var banner in Model.Banners)
        {
            <div class="banner-item">
                <a href="@banner.Link">
                    <img src="@banner.ImageUrl" alt="@banner.Title" />
                    <div class="banner-overlay">
                        <h2>@banner.Title</h2>
                    </div>
                </a>
            </div>
        }
    </div>
</div>

<style>
    .banner-widget {
        margin-bottom: 30px;
    }
    .banner-item img {
        width: 100%;
        height: auto;
        border-radius: 8px;
    }
    .banner-overlay {
        position: absolute;
        bottom: 20px;
        left: 20px;
        color: white;
        text-shadow: 0 2px 4px rgba(0,0,0,0.5);
    }
</style>

Public Controllers

Creating a Public Controller

csharp
// Controllers/MyPluginPublicController.cs
using Microsoft.AspNetCore.Mvc;
using Nop.Web.Framework.Controllers;

public class MyPluginPublicController : BasePublicController
{
    private readonly IMyService _myService;
    private readonly IWorkContext _workContext;

    public MyPluginPublicController(
        IMyService myService,
        IWorkContext workContext)
    {
        _myService = myService;
        _workContext = workContext;
    }

    public async Task<IActionResult> Index()
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        var items = await _myService.GetCustomerItemsAsync(customer.Id);
        
        return View("~/Plugins/Widgets.MyPlugin/Views/Public/Index.cshtml", items);
    }

    [HttpPost]
    public async Task<IActionResult> Submit(SubmitModel model)
    {
        if (!ModelState.IsValid)
            return View("~/Plugins/Widgets.MyPlugin/Views/Public/Index.cshtml", model);

        await _myService.ProcessSubmissionAsync(model);
        
        return RedirectToAction("ThankYou");
    }

    public IActionResult ThankYou()
    {
        return View("~/Plugins/Widgets.MyPlugin/Views/Public/ThankYou.cshtml");
    }
}

Custom Routes

csharp
// Infrastructure/RouteProvider.cs
using Microsoft.AspNetCore.Routing;
using Nop.Web.Framework.Mvc.Routing;

public class RouteProvider : IRouteProvider
{
    public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
    {
        endpointRouteBuilder.MapControllerRoute(
            name: "MyPlugin.Index",
            pattern: "my-plugin",
            defaults: new { controller = "MyPluginPublic", action = "Index" });

        endpointRouteBuilder.MapControllerRoute(
            name: "MyPlugin.Item",
            pattern: "my-plugin/item/{id:int}",
            defaults: new { controller = "MyPluginPublic", action = "Item" });
    }

    public int Priority => 0;
}

Working with Themes

Respecting Theme Styles

html
<!-- Use nopCommerce CSS classes -->
<div class="page my-plugin-page">
    <div class="page-title">
        <h1>My Plugin</h1>
    </div>
    <div class="page-body">
        <div class="product-grid">
            @foreach (var item in Model.Items)
            {
                <div class="item-box">
                    <div class="product-item">
                        <div class="picture">
                            <img src="@item.ImageUrl" alt="@item.Name" />
                        </div>
                        <div class="details">
                            <h2 class="product-title">@item.Name</h2>
                            <div class="prices">
                                <span class="price">@item.Price</span>
                            </div>
                            <div class="buttons">
                                <button class="button-2 product-box-add-to-cart-button">
                                    Add to cart
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            }
        </div>
    </div>
</div>

Adding Custom CSS

csharp
// Infrastructure/PluginStartup.cs
public void Configure(IApplicationBuilder application)
{
    // Register CSS file
}

// In plugin install
public override async Task InstallAsync()
{
    // Add CSS to <head>
    await base.InstallAsync();
}

Including in Layout

html
<!-- Reference in your view -->
@section head {
    <link href="~/Plugins/Widgets.MyPlugin/Content/styles.css" rel="stylesheet" />
}

@section scripts {
    <script src="~/Plugins/Widgets.MyPlugin/Content/scripts.js"></script>
}

AJAX Interactions

javascript
// Content/scripts.js
$(document).ready(function() {
    $('.add-to-wishlist').on('click', function(e) {
        e.preventDefault();
        var productId = $(this).data('product-id');
        
        $.ajax({
            url: '/MyPluginPublic/AddToWishlist',
            type: 'POST',
            data: { productId: productId },
            success: function(response) {
                if (response.success) {
                    displayNotification(response.message, 'success');
                } else {
                    displayNotification(response.message, 'error');
                }
            }
        });
    });
});

Best Practices

  1. Use existing CSS classes - Match the store theme
  2. Support mobile - Ensure responsive design
  3. Lazy load content - Don't block page rendering
  4. Respect customer privacy - Check permissions
  5. Handle errors gracefully - Show user-friendly messages

Next Steps

Released under the nopCommerce Public License.