Optimizely CMS: What Actually Happens When You Click Save a Rich Text Block?
If you work with enough Optimizely CMS solutions, you eventually run into the same question from editors, developers, architects, and security reviewers: what exactly happens inside the rich-text editor?
Most of the time, the question appears in a practical form.
- Can editors paste content from Word safely?
- Why does one
XhtmlStringproperty behave differently from another? - Where does TinyMCE get its toolbar and plugins from?
- Is the HTML filtered only in the browser, or also on the server?
- How risky is it to allow extra elements like
iframe?
The short answer is that Optimizely's TinyMCE integration is not just a text area with buttons. It is a layered system with configuration, editor descriptors, client-side filtering, plugin behaviour, and the CMS save pipeline all working together.
The longer answer is more interesting.
TL;DR
In Optimizely CMS, TinyMCE is the editor used for XhtmlString properties.
Its behaviour is built from three places: global defaults, code-based configuration, and optional property-specific settings from the CMS admin UI.
The content is filtered more than once. TinyMCE validates markup in the browser, paste handling removes risky content, some plugins encode user-controlled values, and Optimizely CMS still validates the value again when it is saved.
That layered approach is why the editor is flexible without being completely unguarded, but it also means customisation should be done carefully.
Why this matters
Rich-text editing looks simple from the outside. An editor types text, inserts a link, maybe pastes from Word, and clicks publish.
From the implementation side, that same workflow touches UI configuration, HTML rules, CMS metadata, drag-and-drop handling, content references, file picking, image upload, validation, and persistence.
If you are debugging an editor issue, reviewing security posture, or planning a custom TinyMCE setup, it helps to understand where each responsibility actually lives.
That is the difference between making a safe change and making a change that quietly creates new problems.
First: where TinyMCE comes from
In Optimizely CMS, TinyMCE is typically registered through the EPiServer.Cms.TinyMce package.
At startup, the application calls AddTinyMce() on the service collection. That is the point where the package registers its protected module and prepares a default TinyMCE configuration.
Those defaults usually include things editors expect from a normal rich-text experience:
- Standard formatting tools.
- Lists and links.
- Image support.
- Episerver-specific plugins.
- A default toolbar.
- Default sizing and editor behaviour.
That alone does not explain why TinyMCE appears on specific properties, though.
The next important piece is the editor descriptor.
Why XhtmlString opens TinyMCE
In Optimizely, TinyMCE is associated with XhtmlString properties through an editor descriptor.
That means whenever the CMS renders a property of that type, it knows which client-side editor widget to load and which settings to pass into it.
This part is easy to overlook, but it matters a lot because it is where configuration begins to become contextual rather than global.
In practice, the editor can pull settings from three levels, in roughly this order:
- Property-specific settings stored in the CMS admin UI.
- Code-based configuration for a content type or property.
- Global TinyMCE defaults.
That is why two rich-text fields on the same site do not always behave the same way.
One property may get a minimal toolbar from admin settings. Another may get extra plugins from code. A third may simply inherit the global default configuration.
If you have ever wondered why a toolbar button appears in one editor but not another, this is usually the reason.
What happens when an editor opens the field
Once the CMS has resolved the settings, those settings are sent to the browser and used to initialise the editor widget.
From there, the flow is fairly sensible:
- The widget starts with the settings resolved on the server.
- It merges in editor defaults such as language, readonly mode, upload handlers, and file picker behaviour.
- It can optionally run a custom initialisation module if the solution has registered one.
- It calls
tinymce.init(...)with the merged configuration.
That initialisation step is also where a lot of custom project behaviour gets attached.
For example, the editor may load site CSS into the iframe so content looks closer to the real website. It may wire in Optimizely dialogs for links and media. It may enable block creation, drag-and-drop, or image upload. It may even apply dynamic settings based on the current content item.
In other words, TinyMCE is not dropped into Optimizely as an isolated editor. It is integrated into the CMS shell, the content model, and the asset system.
The security story is layered, not magical
This is the part people usually care about most.
When someone hears "rich-text editor security", they usually picture a single sanitisation step somewhere on save.
That is not really how it works.
The safer mental model is this: the content passes through several defensive layers, and each layer catches a different class of problem.
If you want the simplest possible example, use <script>.
When someone pastes or tries to introduce <script> into a normal Optimizely TinyMCE field, the system does not usually preserve it and quietly carry it all the way into published content.
Instead, different layers get a chance to remove or reject it before it becomes a lasting part of the saved value.
Layer 1: TinyMCE schema validation
TinyMCE has its own schema rules for what elements, attributes, and nesting are allowed.
That means the editor can reject or strip unsupported markup before the content even leaves the browser.
This affects things like:
- Which HTML elements are allowed.
- Which attributes are valid on those elements.
- Which elements are allowed inside other elements.
- Which inline styles or CSS classes may survive.
By default, this is a useful first line of defence. It stops the editor from behaving like a completely open HTML textarea.
In practical terms, if markup contains an element TinyMCE does not allow, that element is typically stripped during parsing or when the editor serialises the content back into HTML.
So if someone somehow gets <script>alert('x')</script> into the editor input, the normal expectation is not that TinyMCE politely stores it for later. The normal expectation is that the unsupported element is removed as part of the editor's validation rules.
It is also why adding custom settings such as extended_valid_elements should be treated with respect. The moment you allow more markup, you are not just enabling a feature. You are widening the input surface.
Layer 2: paste handling, especially from Word
Paste is where rich-text editors often get messy.
Word, Google Docs, and similar tools bring along a lot of extra markup, inline styles, and historical baggage. If the editor accepted all of that blindly, the saved HTML would become ugly very quickly.
The TinyMCE integration includes paste processing that specifically looks for Word and Google Docs patterns and cleans the result.
That cleaning is not only cosmetic.
It is also one of the places where people misunderstand what "removed" means.
Usually it does not mean the editor pops up a dramatic error saying, "I found a <script> tag and destroyed it." It more often means the disallowed or dangerous markup never survives the paste/import pipeline into the final saved HTML.
It also helps block dangerous input such as:
javascript:orvbscript:URLs.- SVG data URIs when those are not allowed.
- Old CSS tricks such as
expression()andbehavior. - Other noisy or risky formatting carried in pasted HTML.
The source document may have started with risky or irrelevant markup. The cleaned result is what continues through the editor.
So if someone pastes content containing <script>, <iframe>, <object>, or similar markup from an external source, the important point is not just that those elements are "unsupported." The important point is that the paste cleaning and validation stages are designed so those elements do not survive into ordinary editor content by default.
This is one of those features that quietly saves teams from a surprising amount of support pain.
When it works, nobody notices. When it does not exist, people notice later and in production.
Layer 3: plugin-level encoding
Some editor features generate markup programmatically instead of relying only on whatever the user typed or pasted.
For example, a plugin may insert a content block placeholder, link text, or personalised content markup.
Good integrations encode user-controlled values before inserting them into the editor HTML. That reduces the chance of a seemingly harmless display value becoming an injection problem.
This is not the whole security model, but it is an important detail because integrations often become risky at the seams between user input and generated markup.
Layer 4: Optimizely validates again on save
This is the part I usually want teams to remember.
Even after the editor has already filtered and serialised the HTML, Optimizely CMS still performs server-side validation when the XhtmlString value is saved.
That matters because client-side rules are useful, but they are not enough on their own.
The CMS core still parses the value, resolves internal content references, applies its own content handling rules, and validates the structure as part of normal persistence.
So the system is not trusting the browser as the final authority.
That matters for the same <script> example.
If a project weakens the client-side rules, or if content somehow reaches the server in a shape the editor should normally have filtered out, the server-side save pipeline still gets a chance to inspect the value before it is persisted as CMS content.
That does not mean teams should be casual about editor configuration. It means the default model is layered on purpose: browser-side filtering first, server-side validation second, instead of blind trust in whichever HTML happened to arrive.
That is a much healthier design.
What embedded blocks and personalised content are really doing
Two features often make rich-text content look more mysterious than it really is: embedded content blocks and personalised sections.
In both cases, the editor is not simply storing a magical object. It is storing structured HTML markers that the CMS understands.
For embedded content blocks, the markup includes things like a content reference and a fragment class. The editor treats those wrappers as non-editable so users cannot casually break the structure while typing around them.
For personalised content, the wrapper structure also separates the editable area from the non-editable control markup.
That design is practical. It gives editors a visual experience inside the rich-text field while still preserving a predictable structure the CMS can recognise later.
File picking and image upload are also part of the story
When people talk about editor security, they often focus only on raw HTML.
But file selection and image upload matter too.
In the Optimizely TinyMCE integration, the file picker is typically wired into the CMS content selector rather than letting users browse arbitrary file paths. Image upload is also routed through the CMS media system, usually into the local asset folder for the content.
That is a better model than treating the editor as a generic upload box.
It keeps the content experience connected to the CMS asset model, permissions, and media handling instead of creating a side channel that is harder to govern.
What actually happens when the editor saves
From the editor's point of view, saving is not especially dramatic.
TinyMCE serialises the current DOM into an HTML string. The widget updates its value. The CMS property system picks that up and sends it through the normal content save pipeline.
In practice, updates are commonly pushed on events like:
- Blur, when the editor loses focus.
- Change, often with some debouncing.
Before that value goes through, there may also be editor-side checks such as warning about missing image alt text.
The important point is that the TinyMCE package does not invent an entirely separate persistence model for content. It plugs into the normal Optimizely content system.
That is good news for supportability. It means the editor is part of the CMS pipeline, not a bypass around it.
Where customisation is useful, and where it becomes risky
Optimizely gives teams a fair amount of control over TinyMCE.
That is useful. It is also where restraint helps.
Reasonable customisations include:
- Adjusting the toolbar for different property types.
- Loading editor-specific CSS.
- Adding carefully chosen plugins.
- Using settings transforms to tailor behaviour by content type.
- Creating small, controlled runtime adjustments in an initialisation module.
The riskier end of customisation usually starts when teams begin loosening HTML restrictions without a clear need.
For example, enabling extra embedded media support or adding broad extended_valid_elements rules may be completely valid for a real business requirement. But it should be treated like a deliberate platform decision, not a casual convenience tweak.
That is the point where the answer to "what happens to <script>?" can change from "it gets stripped by default" to "it depends what we explicitly allowed, and now we own that decision."
If you allow more HTML, you should know exactly why, where, and for whom.
A practical takeaway for teams
If you support Optimizely solutions, here is the simplest useful mental model:
- TinyMCE is the editor UI.
- The editor descriptor decides where and how it is used.
- Configuration can come from admin settings, code, or defaults.
- Filtering happens both in the browser and on the server.
- Integrations like drag-and-drop, links, blocks, and uploads are part of the CMS contract, not separate hacks.
Once you understand that, several decisions become easier.
You can debug configuration issues faster. You can explain editor behaviour more clearly to content teams. You can review security tradeoffs with less hand-waving. And you can customise the editor without accidentally turning it into an ungoverned HTML playground.
Final thought
Rich-text editors always look simpler than they really are.
In Optimizely CMS, TinyMCE is not just a text box with a toolbar. It is a carefully connected part of the editing platform, with multiple layers trying to keep content usable, structured, and reasonably safe.
That does not mean the defaults are perfect or that every customisation is safe. It does mean the integration has more architecture behind it than many teams realise.
And that is usually the key point: if you understand the architecture, your customisations get better, your troubleshooting gets faster, and your security conversations become much more concrete.