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" }.
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.
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.
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.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.How to spot it
Drop this in Program.cs after var app = builder.Build();:The fix
Pick whichever fits better.Option A - pin the .NET SDK to 8.0.x
Drop a global.json next to your .sln:
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:
Option B - un-pin the redundant leaves
Open your .csproj and remove the <PackageReference> lines for at least one of: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.
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/.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.Affected versions, summarised
.NET SDK 10.0.x