XhtmlString editor silently dies when `EPiServer.CMS.UI` leaves are explicitly pinned + .NET SDK 10

Vote:
 

TL;DR

If your CMS 12 project's .csproj explicitly references EPiServer.CMS.UI.Core, EPiServer.CMS.UI.Admin, and EPiServer.CMS.UI.Settings, and your build host uses .NET SDK 10.x, TinyMce will silently stop working. No errors at startup, but the rich-text editor on XhtmlString properties doesn't render, and EditorDescriptors defined in EPiServer.Cms.TinyMce.dll (including XhtmlStringEditorDescriptor) silently disappear from the IoC container.
 
Easiest fix: drop a global.json pinning .NET SDK to 8.0.x at the solution root.

Better long-term fix: remove redundant explicit <PackageReference> entries for the EPiServer.CMS.UI.* leaves if not explicitly required.

How you probably got here

The trigger is having all three of EPiServer.CMS.UI's declared leaf dependencies (UI.Core, UI.Admin, UI.Settings) as explicit top-level <PackageReference> entries. The common path to it is upgrade tooling - anything that runs dotnet list package --outdated --include-transitive and then dotnet add package on each result. That kind of script pins every transitive to a top-level reference, which is what trips the bug.
 
If you've ever blanket-promoted transitives during an upgrade, check your .csproj. If you see all of the above 3 packages pinned alongside EPiServer.CMS.UI, that's the trap.

How to spot it

Drop this in Program.cs after var app = builder.Build();:

var scanner = app.ApplicationServices
    .GetRequiredService<EPiServer.Framework.TypeScanner.ITypeScannerLookup>();
var count = scanner.AllTypes
    .Count(t => t.Assembly.GetName().Name == "EPiServer.Cms.TinyMce");
System.Diagnostics.Debug.WriteLine($"TinyMce types in scanner: {count}");

A healthy project reports 4. An affected project reports 0.

You can also open bin\Debug\net8.0\<YourApp>.deps.json and search for EPiServer.CMS.TinyMce/. On an affected build the entry has only a runtime block - no dependencies field at all. On a healthy build there's a dependencies entry, e.g. { "EPiServer.CMS.UI": "12.34.4" }.

The fix

Pick whichever fits better.

Option A - pin the .NET SDK to 8.0.x


Drop a global.json next to your .sln:
 
{
  "sdk": {
    "version": "8.0.412",
    "rollForward": "latestFeature"
  }
}

Substitute whatever 8.0.x version dotnet --list-sdks lists on your machine. Confirm with dotnet --version from that folder (should print 8.0.x). Then:
 
Remove-Item -Recurse -Force bin, obj -ErrorAction SilentlyContinue
dotnet restore
dotnet build

Option B - un-pin the redundant leaves

Open your .csproj and remove the <PackageReference> lines for at least one of:
  • EPiServer.CMS.UI.Core
  • EPiServer.CMS.UI.Admin
  • EPiServer.CMS.UI.Settings

They'll come through transitively from EPiServer.CMS (or EPiServer.CMS.UI) with no behaviour change. Removing any one of the three is enough to restore the dependency edge in .deps.json. The "all three explicit" combination is what makes NuGet/SDK consider EPiServer.CMS.UI redundant and prune it.

If you don't have a specific reason to pin every leaf, just remove all three explicit lines. Same for EPiServer.CMS.UI, EPiServer.CMS.UI.AspNetIdentity, and EPiServer.CMS.UI.VisitorGroups - they'll all come through transitively too.

What's actually happening (short version)


EPiServer.CMS.UI's nuspec declares three direct dependencies - UI.Core, UI.Admin, UI.Settings. When all three are also top-level PackageReferences in your project, NuGet treats EPiServer.CMS.UI as redundant (its only contribution to the graph is already in your direct references). Under .NET SDK 10.x the SDK then prunes it from .deps.json. When the SDK prunes a package, every inbound dependency edge to it is also stripped.

EPiServer.CMS.TinyMce declares exactly one nuspec dependency, and it's on EPiServer.CMS.UI. So when CMS.UI is pruned, TinyMce ends up in .deps.json with no dependencies field.

Optimizely's runtime type scanner (EPiServer.Hosting.Internal.AssemblyScanner.GetScannableAssemblies()) builds its scannable-assembly list by walking each library's dependency chain looking for a path back to EPiServer.Framework. With no edges, the walk for TinyMce fails immediately, and the whole assembly is excluded from scanning. No types from it reach the IoC container.

Under SDK 8.0.x the same packaging is handled differently and the dependency edge survives, which is why pinning the SDK fixes it without changing any package references.

Credit and prior art

Francisco Quintanilla published a great post in December 2025 spotting the same symptom and identifying the SDK-version dependency: https://powerbuilder.home.blog/2025/12/08/fixing-tinymce-initialization-failures-in-optimizely-cms-a-hidden-pipeline-issue-with-net-sdk-versions/.
 
His global.json workaround is what's recommended above; this post explains which .csproj states are vulnerable.

Open question for Optimizely

I've raised a query with Optimizely support to ask whether this behaviour is expected, whether the proposed workaround is recommended and if there is anything that can be done Opti-side to mitigate such a workaround being required.

I will update this thread once I have a response. If anyone has prior knowledge, has spoken to Optimizely about this, or has hit the same trap and reached a different conclusion, I'd be very interested to hear it.

Affected versions, summarised

.NET SDK 10.0.x 
EPiServer.CMS.TinyMce - Any version whose only nuspec dep is on EPiServer.CMS.UI
.csproj state - All three of UI.Core, UI.Admin, UI.Settings as explicit top-level <PackageReference> 

All three must be true to hit the bug. Remove any one and you're safe.

Hope this saves someone some time!
#342656
Edited, May 26, 2026 22:02
Vote:
 

An update on this:

Optimizely responded on the ticket and advised that, as of 12.34.4, .NET is intended to be fully supported, as per World blog post: https://world.optimizely.com/blogs/bien-nguyen/dates/2026/5/optimizely-cms-12-now-fully-supports-.net-10

I have provided full reproduction steps for an empty project, as well as within Alloy.

I am providing those reproduction steps below for information.

Please see below basic reproduction steps, showing the issue with the dependency pruning only via .deps.json:

dotnet new web -n MinRepro -f net8.0
dotnet add package EPiServer.CMS --version 12.34.4
Check .NET version in current folder: dotnet --version = 10.0.202
dotnet restore
dotnet build
Dependency present in .deps.json:
      "EPiServer.CMS.TinyMce/4.8.3": {
      "dependencies": {
        "EPiServer.CMS.UI": "12.34.4"
      },
      "runtime": {
        "lib/net6.0/EPiServer.Cms.TinyMce.SpellChecker.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/EPiServer.Cms.TinyMce.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/WeCantSpell.Hunspell.dll": {
          "assemblyVersion": "0.0.0.0",
          "fileVersion": "0.0.0.0"
        }
      }
      },
Clear bin/obj folders: Remove-Item -Recurse -Force bin, obj -ErrorAction SilentlyContinue
Update csproj to pin EPiServer.CMS.UI dependencies:
   <PackageReference Include="EPiServer.CMS" Version="12.34.4" />
  <PackageReference Include="EPiServer.CMS.UI.Admin" Version="12.34.4" />
  <PackageReference Include="EPiServer.CMS.UI.Core" Version="12.34.4" />
  <PackageReference Include="EPiServer.CMS.UI.Settings" Version="12.34.4" />
dotnet restore
dotnet build
Dependency missing in .deps.json:
      "EPiServer.CMS.TinyMce/4.8.3": {
      "runtime": {
        "lib/net6.0/EPiServer.Cms.TinyMce.SpellChecker.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/EPiServer.Cms.TinyMce.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/WeCantSpell.Hunspell.dll": {
          "assemblyVersion": "0.0.0.0",
          "fileVersion": "0.0.0.0"
        }
      }
      },
Pin v8 of .NET via global.json:
{
"sdk": {
  "version": "8.0.412",
  "rollForward": "latestFeature"
}
}
dotnet --version = 8.0.421
Clear bin/obj folders: Remove-Item -Recurse -Force bin, obj -ErrorAction SilentlyContinue
dotnet restore
dotnet build
Dependency present in .deps.json:
      "EPiServer.CMS.TinyMce/4.8.3": {
      "dependencies": {
        "EPiServer.CMS.UI": "12.34.4"
        },

      "runtime": {
        "lib/net6.0/EPiServer.Cms.TinyMce.SpellChecker.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/EPiServer.Cms.TinyMce.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/WeCantSpell.Hunspell.dll": {
          "assemblyVersion": "0.0.0.0",
          "fileVersion": "0.0.0.0"
        }
      }
      },

Whilst these steps allow for showing the impact on the .deps.json file, I have also put together steps for reproducing via Alloy, showing the impact on the RTE.

dotnet new install EPiServer.Templates@1.7.2 --force
dotnet new epi-alloy-mvc
dotnet add package EPiServer.CMS --version 12.34.4
dotnet run

RTE works:

Clear bin/obj folders: Remove-Item -Recurse -Force bin, obj -ErrorAction SilentlyContinue
 
Update csproj to pin EPiServer.CMS.UI dependencies:
   <PackageReference Include="EPiServer.CMS" Version="12.34.4" />
  <PackageReference Include="EPiServer.CMS.UI.Admin" Version="12.34.4" />
  <PackageReference Include="EPiServer.CMS.UI.Core" Version="12.34.4" />
  <PackageReference Include="EPiServer.CMS.UI.Settings" Version="12.34.4" />
Dependency missing in .deps.json:
      "EPiServer.CMS.TinyMce/4.8.3": {
      "runtime": {
        "lib/net6.0/EPiServer.Cms.TinyMce.SpellChecker.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/EPiServer.Cms.TinyMce.dll": {
          "assemblyVersion": "4.8.3.0",
          "fileVersion": "4.8.3.0"
        },
        "lib/net6.0/WeCantSpell.Hunspell.dll": {
          "assemblyVersion": "0.0.0.0",
          "fileVersion": "0.0.0.0"
        }
      }
      },
 RTE doesn't work:
 
 
Pin v8 of .NET via global.json:
{
  "sdk": {
    "version": "8.0.412",
    "rollForward": "latestFeature"
  }
}

dotnet --version = 8.0.421

Clear bin/obj folders: Remove-Item -Recurse -Force bin, obj -ErrorAction SilentlyContinue

dotnet restore
dotnet build

Dependency present in .deps.json:

      "EPiServer.CMS.TinyMce/4.8.3": {
        "dependencies": {
          "EPiServer.CMS.UI": "12.34.4"
        },
        "runtime": {
          "lib/net6.0/EPiServer.Cms.TinyMce.SpellChecker.dll": {
            "assemblyVersion": "4.8.3.0",
            "fileVersion": "4.8.3.0"
          },
          "lib/net6.0/EPiServer.Cms.TinyMce.dll": {
            "assemblyVersion": "4.8.3.0",
            "fileVersion": "4.8.3.0"
          },
          "lib/net6.0/WeCantSpell.Hunspell.dll": {
            "assemblyVersion": "0.0.0.0",
            "fileVersion": "0.0.0.0"
          }
        }
      },

RTE works:

Hopefully, with these steps, you can see that there's an issue when a solution has the EPiServer.CMS.UI dependencies (EPiServer.CMS.UI.Core, EPiServer.CMS.UI.Admin and EPiServer.CMS.UI.Settings) installed directly and is built using .NET 10.

#342658
May 27, 2026 9:27
Vote:
 

A further update here:

Through conversations with Optimizely support, it appears that the following breaking change in .NET 10 is the root cause of the observed behaviour: https://learn.microsoft.com/en-us/dotnet/core/compatibility/sdk/10.0/deps-json-trimmed-packages

Applying the documented recommended action for disabling the behaviour means that the issue can no longer be reproduced.

Whilst this is a workaround to restore functionality in this scenario, I have asked Optimizely if they will look at making a change within their product to prevent this being required, meaning we can keep this new feature turned on without it having an impact on Optimizely functionality

#342684
May 29, 2026 13:38
Vote:
 

Further update:

Optimizely have confirmed this behaviour is a bug, which can be tracked here: https://world.optimizely.com/support/Bug-list/bug/CMS-52767 

#342700
Edited, Jun 03, 2026 8:17
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.