Skip to content

Develop CMS Plugins

⏱ 30 minutes advanced

Plugins extend the CMS without modifying core code. You might build a plugin to add a custom admin tool, integrate a third-party service, or provide reusable functionality across multiple sites. Plugins package as NuGet packages, making them easy to share and version.

  1. Create a class library project for the plugin
  2. Build a custom admin tool with a controller and view
  3. Register the plugin with the CMS menu system
  4. Package the plugin as a NuGet package

Create a separate class library so the plugin can be distributed independently.

Create the plugin project
bash
# Create a class library for the plugin
dotnet new classlib -n MySite.Plugin.ContentAudit -f net8.0

# Add Optimizely CMS dependencies
cd MySite.Plugin.ContentAudit
dotnet add package Optimizely.CMS.Core
dotnet add package Optimizely.CMS.UI
dotnet add package Microsoft.AspNetCore.Mvc.ViewFeatures

Admin tools appear in the CMS admin section. Start with a controller that inherits from Controller and register it as a menu item.

Admin tool controller
csharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Optimizely.Cms.Core;
using Optimizely.Cms.Core.Repositories;

namespace MySite.Plugin.ContentAudit.Controllers;

[Authorize(Roles = "CmsAdmins")]
public class ContentAuditController : Controller
{
    private readonly IContentRepository _contentRepo;
    private readonly IContentTypeRepository _typeRepo;

    public ContentAuditController(
        IContentRepository contentRepo,
        IContentTypeRepository typeRepo)
    {
        _contentRepo = contentRepo;
        _typeRepo = typeRepo;
    }

    public IActionResult Index()
    {
        var contentTypes = _typeRepo.List();
        var audit = contentTypes.Select(ct => new ContentTypeInfo
        {
            Name = ct.DisplayName ?? ct.Name,
            InstanceCount = CountInstances(ct.ID),
            HasController = HasRegisteredController(ct)
        }).ToList();

        return View(
            "~/Views/ContentAudit/Index.cshtml", audit);
    }

    private int CountInstances(int typeId) =>
        _contentRepo
            .GetDescendents(ContentReference.RootPage)
            .Count();

    private bool HasRegisteredController(
        ContentType ct) => true; // Simplified
}

Use the [MenuItem] attribute so the tool appears in the CMS admin navigation.

Menu provider for the plugin
csharp
using Optimizely.Cms.Shell.Navigation;

namespace MySite.Plugin.ContentAudit;

[MenuProvider]
public class ContentAuditMenuProvider : IMenuProvider
{
    public IEnumerable<MenuItem> GetMenuItems()
    {
        var menuItem = new UrlMenuItem(
            "Content Audit",
            MenuPaths.Global + "/cms/admin/contentaudit",
            "/ContentAudit")
        {
            IsAvailable = request => true,
            SortIndex = 100,
            AuthorizationPolicy =
                CmsPolicyNames.CmsAdmin
        };

        return new[] { menuItem };
    }
}

The tool now appears in the admin menu under a “Content Audit” link.

Admin tool view
html
@model IList<MySite.Plugin.ContentAudit.ContentTypeInfo>

<div class="epi-padding">
    <h1>Content Type Audit</h1>
    <table class="epi-default">
        <thead>
            <tr>
                <th>Content Type</th>
                <th>Instances</th>
                <th>Has Controller</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var ct in Model)
            {
                <tr>
                    <td>@ct.Name</td>
                    <td>@ct.InstanceCount</td>
                    <td>@(ct.HasController ? "Yes" : "No")</td>
                </tr>
            }
        </tbody>
    </table>
</div>

Register services in an initialization module

Section titled “Register services in an initialization module”

If your plugin needs dependency injection, register services during startup.

Plugin initialization module
csharp
using Optimizely.Cms.Framework.Initialization;
using Microsoft.Extensions.DependencyInjection;

namespace MySite.Plugin.ContentAudit;

[InitializableModule]
public class ContentAuditInitialization : IConfigurableModule
{
    public void ConfigureContainer(
        ServiceConfigurationContext context)
    {
        context.Services
            .AddScoped<IAuditService, AuditService>();
    }

    public void Initialize(InitializationEngine context) { }
    public void Uninitialize(InitializationEngine context) { }
}
NuGet package spec
xml
<PropertyGroup>
    <PackageId>MySite.Plugin.ContentAudit</PackageId>
    <Version>1.0.0</Version>
    <Description>Content type audit tool for Optimizely CMS</Description>
    <Authors>Your Team</Authors>
    <PackageTags>optimizely;cms;plugin;audit</PackageTags>
</PropertyGroup>

Run dotnet pack --configuration Release to create the .nupkg file. Publish it to your internal NuGet feed or nuget.org.

IssueCauseFix
Menu item not showingMissing [MenuProvider] attributeAdd the attribute to your menu provider class
View not found at runtimeView not embedded or path wrongUse full path starting with ~/Views/
Authorization errorWrong policy nameUse CmsPolicyNames.CmsAdmin for admin tools