For Optimizely and .NET teams

Optimizely CMS, DXP, AI editor tools, and technical SEO

Practical solutions, open-source tools, and field-tested code to help you solve real Optimizely challenges and ship with confidence.

Latest posts

All posts →

Monday, 5 October 2026

Migrating from Search & Navigation (Find) to Optimizely Graph in CMS 13

If you are planning an upgrade to Optimizely CMS 13, one of the biggest architectural shifts is that Optimizely Search & Navigation (EPiServer.Find) is completely unsupported. It is not just deprecated; the packages will not run on CMS 13. Every site moving to CMS 13 must replace Find with Optimizely Graph.

The good news is that Optimizely provides a dedicated C# query package for CMS 13 (Optimizely.Graph.Cms.Query) that offers a fluent API structured very similarly to the old Find client. However, there are significant changes under the hood, particularly around package names, configuration keys, dependency injection, and a move from synchronous to asynchronous query execution.

Here is a step-by-step walkthrough to help you migrate your codebase cleanly.

Package Replacements

First, remove all legacy Find and related packages from your project files:

<!-- Remove legacy Find packages -->
<PackageReference Remove="EPiServer.Find" />
<PackageReference Remove="EPiServer.Find.Cms" />
<PackageReference Remove="EPiServer.Find.Framework" />
<PackageReference Remove="Wangkanai.Detection" />

Next, install the Optimizely Graph packages for CMS 13:

dotnet add package Optimizely.Graph.Cms.Query
dotnet add package Optimizely.Graph.AspNetCore

Optimizely.Graph.Cms.Query provides the strongly-typed LINQ-style query builder, while Optimizely.Graph.AspNetCore handles middleware and client lifecycle integration.

Configuration Changes in appsettings.json

Remove the legacy EPiServer:Find section from appsettings.json:

// Remove this block
"EPiServer": {
  "Find": {
    "ServiceUrl": "https://...",
    "DefaultIndex": "your_index_name"
  }
}

Replace it with the new Optimizely:Graph section:

"Optimizely": {
  "Graph": {
    "GatewayAddress": "https://cg.optimizely.com",
    "AppKey": "YOUR_APP_KEY",
    "Secret": "YOUR_SECRET_KEY",
    "SingleKey": "YOUR_SINGLE_KEY"
  }
}

On local environments, populate these keys from your Graph portal. In Optimizely DXP, these environment variables are provisioned automatically at deployment time.

Service Registration and Middleware

In your Startup.cs or Program.cs, swap the service registrations and middleware:

// Remove legacy Find calls:
// services.AddFind();
// services.AddDetection();
// app.UseDetection();

// Add Optimizely Graph:
services.AddContentGraph();
services.AddGraphContentClient();

// Add Graph tracking middleware
app.UseGraphTrackingScripts();

Namespace Updates

Find queries used namespaces scattered across EPiServer.Find.*. In CMS 13, the majority map directly to Optimizely.Graph.Cms.Query:

Legacy Find Namespace Optimizely Graph Replacement
EPiServer.Find Optimizely.Graph.Cms.Query
EPiServer.Find.Cms Optimizely.Graph.Cms.Query
EPiServer.Find.Api Optimizely.Graph.Cms.Query
EPiServer.Find.Framework Optimizely.Graph.Cms.Query

You can remove older references such as EPiServer.Find.Framework.Statistics and EPiServer.Find.Helpers.Text as they are no longer used.

Query Rewriting: Client and Execution Model

The single most impactful code change is how queries execute. Find allowed synchronous calls like .GetContentResult() or .GetResult(). Optimizely Graph is strictly asynchronous and relies on dependency injection rather than static singleton access.

1. Client Dependency Injection

Instead of SearchClient.Instance or injecting IClient, inject IGraphContentClient:

public class SearchPageController : PageController<SearchPage>
{
    private readonly IGraphContentClient _graphClient;

    public SearchPageController(IGraphContentClient graphClient)
    {
        _graphClient = graphClient;
    }

    // ...
}

2. Querying Content with GetAsContentAsync

In Find, fetching full content items typically looked like this:

// Legacy Find (Synchronous)
var results = SearchClient.Instance.Search<ArticlePage>()
    .For(query)
    .InField(x => x.Title)
    .Filter(x => x.Category.Match("News"))
    .Take(10)
    .GetContentResult();

In Optimizely Graph for CMS 13, use QueryContent<T>() and GetAsContentAsync():

// Optimizely Graph (Async)
var results = await _graphClient.QueryContent<ArticlePage>()
    .SearchFor(query)
    .UsingFullText()
    .Where(x => x.Category == "News")
    .Limit(10)
    .GetAsContentAsync();

GetAsContentAsync() returns an IGetAsContentResult<T> that implements IEnumerable<T>, delivering fully resolved CMS content models without requiring extra mapping layers.

3. Dynamic Filtering with BuildFilter

In Find, dynamic search criteria were typically composed by chaining .Filter() calls. In the Graph C# SDK, use BuildFilter<T>() to assemble conditional logic dynamically:

var filterBuilder = _graphClient.BuildFilter<ArticlePage>()
    .And(x => x.Name.FilterStartsWith("Tech"));

if (!string.IsNullOrEmpty(selectedAuthor))
{
    filterBuilder = filterBuilder.And(x => x.Author.Eq(selectedAuthor));
}

var results = await _graphClient.QueryContent<ArticlePage>()
    .Where(filterBuilder)
    .GetAsContentAsync();

4. Faceted Navigation

Facets work differently in Graph. When users filter by an author, you often want the facet counts for all other authors to remain visible so users can switch options easily. The Facet() extension accepts a filters parameter directly for this purpose:

var results = await _graphClient.QueryContent<ArticlePage>()
    .SearchFor(query)
    .UsingFullText()
    .Facet(x => x.Author, filters: [selectedAuthor])
    .GetAsContentAsync();

var authorFacet = results.Facets?.GetFacet(x => x.Author);

Important

  • Do not mix the platform upgrade with the search migration. Upgrade your project to CMS 13 first, verify that it compiles and starts, and then execute the Find to Graph migration on a dedicated Git branch.
  • Audit all synchronous helper methods, extensions, and custom view models. Because Graph requires await for execution, synchronous wrappers (such as .GetAwaiter().GetResult()) can cause thread pool starvation under production loads on ASP.NET Core. Convert calling methods to async up the stack.
  • Remember that CMS 13 introduces a breaking schema change in Graph compared to CMS 12. If you are already testing Graph on CMS 12, provision a separate Graph instance for your CMS 13 deployment slot to avoid corrupting your production index. Read more about this here.
  • Property indexing modes matter for query performance. Mark fields that are only displayed in results (and never searched or filtered) with the [GraphProperty(PropertyIndexingMode.OutputOnly)] attribute to minimize Graph indexing overhead.

Links

October 05, 2026 →

Friday, 2 October 2026

From robots.txt to GEO Analytics: A Year On

In September 2025, I wrote about updating your robots.txt file to explicitly allow AI bots like GPTBot, ClaudeBot, PerplexityBot, and OAI-SearchBot. Back then, the main challenge was simply granting access: deciding whether to allow training crawlers, search indexing bots, or a mix of both.

A year later, the conversation has shifted. Allowing bots in robots.txt is only table stakes. The real question today is: once you let them in, how often do they visit, what are they reading, and does that traffic convert into actual user referrals? This discipline is known as Generative Engine Optimization (GEO), and Optimizely CMS 13 now builds native tools to measure and optimize for it.

The Evolution from Access to Measurement

Allowing an AI user-agent in robots.txt tells an LLM crawler it is legally and technically welcome. What it does not tell you is whether the crawler understood your content structure, indexed your canonical answers, or simply scrapped your raw HTML without citing your brand.

Here is what the shift looks like in practice:

  • 2025 (Permission): Defining user-agent rules in robots.txt to prevent AI engines from silently bypassing your site.
  • 2026 (Visibility & Analytics): Tracking crawl frequency, measuring referral rates, and observing which specific pages AI platforms extract data from.
  • 2026 (Optimization): Structuring content specifically for AI ingestion using dedicated schema, Q&A fields, and entity models.

Tracking AI Crawlers with GEO Analytics & Agent Visibility

In Optimizely Reporting and Optimizely Analytics, the platform tracks incoming bot traffic categorized specifically by AI engine rather than burying them inside generic organic traffic numbers.

The core metric introduced here is the Crawl-to-Refer Ratio:

Metric State What It Means Action Required
High Crawl-to-Refer Ratio Bots crawl your pages frequently, but AI platforms rarely cite your site in user answers. Improve content structure, add clear Q&A summaries, and use explicit schema so AI models can easily attribute answers.
Low Crawl-to-Refer Ratio Visits are proportional to references and citations in generative responses. Identify your top-performing content templates and replicate their layout across other sections.

Note: While earlier preview documentation referred to the "GEO Analytics dashboard", newer environments direct teams to the Agent Visibility dashboard inside Optimizely Analytics. This view enriches crawler hits with request intent and Optimizely Opal / Mark facet insights.

Native AI Optimization Features in CMS 13

Beyond tracking bots, Optimizely CMS 13 introduces dedicated agents and metadata models to help content rank in generative engine answers:

1. Schema and Answers Agent

Generative search engines prefer extractable, atomic facts over sprawling multi-paragraph layouts. The Schema and Answers Agent automatically evaluates content relationships and suggests structured metadata, FAQ schemas, and direct Q&A blocks so LLMs can cleanly parse answers.

2. GEO Recommendations Agent

Similar to traditional on-page SEO checkers, this agent runs checks against your draft content in Visual Builder, evaluating your pages against known generative search visibility signals before publishing.

3. Optimizely Graph Metadata Integration

Because Optimizely Graph indexes the composition model of your pages and blocks, it surfaces structured metadata contracts (such as _itemMetadata and _assetMetadata) directly. When AI aggregators and agents query your GraphQL endpoint or rendered pages, the data schema is unambiguous.

Updating Your Strategy: What to Do Next

  1. Audit your current robots.txt: Verify that recent AI crawlers are still permitted. If you used the balanced template from last year (allowing search bots while blocking training crawlers), verify that your directives reflect current user-agent names.
  2. Review the Agent Visibility Dashboard: Check which pages have the highest crawl volume from OpenAI and Perplexity. Look for high crawl counts paired with low citation referrals.
  3. Add Structured Q&A Blocks: Update key commercial and technical documentation pages with concise summary definitions and FAQ schema markup.
  4. Leverage SEOBOOST add-on: If you are managing technical metadata, canonical tags, and robots directives on Optimizely CMS, use automated metadata tooling so headers and crawler rules stay aligned across multi-site instances.

Important

  • Do not rely entirely on robots.txt for compliance. Some rogue or emerging AI scrapers ignore standard robots directives. For strict enforcement, pair your robots.txt file with Web Application Firewall (WAF) bot rules on Optimizely DXP or Cloudflare.
  • The legacy GEO Analytics dashboard was grandfathered for early access instances. Modern CMS 13 setups use the Agent Visibility dashboard in Optimizely Analytics for richer crawler telemetry.
  • Generative search engines value freshness and canonical accuracy. If your content updates frequently, ensure your XML sitemap and Optimizely Graph index sync without delay so bots do not reference stale answers.

Links

October 02, 2026 →

Thursday, 1 October 2026

Why Your CMS 12 and CMS 13 Sites Can't Share a Graph Instance

If you're planning your Optimizely CMS 13 upgrade and you already run Optimizely Graph on your CMS 12 site, there's one detail that catches almost everyone off guard: you cannot point your CMS 13 environment at the same Graph instance your live CMS 12 site is using. Not temporarily, not just for testing. CMS 13 ships a breaking change to the Graph schema, and a CMS 12 site and a CMS 13 site cannot share a Graph instance without migrating the schema first.

This isn't an optional step you can defer. Graph is required to use CMS 13's full capabilities, including Content Manager and Experiences, so for any site already running Graph on CMS 12, migrating it is part of the CMS 13 upgrade itself, not a follow-up task.

Why This Happens

CMS 13 changes what Graph indexes and how it's shaped. Experiences, Sections, and Visual Builder's composition model change the object graph that gets synced from your CMS content tree, so the schema CMS 13 expects Graph to serve is different from the one CMS 12 populates. Point a CMS 13 site at a CMS 12 Graph instance (or vice versa) and queries either fail outright or quietly return the wrong shape of data.

That's why the upgrade guidance is blunt about it: treat the Graph migration as a required part of moving to CMS 13, not a nice-to-have cleanup step you can do later.

Two Ways to Handle the Migration

You have two supported paths. Which one you pick depends mostly on whether your site is live and how much downtime you can tolerate.

Option 1: Provision a New Graph Instance (Recommended, Zero Downtime)

Your live CMS 12 site keeps using its existing Graph instance while your CMS 13 deployment slot uses a brand new one. Graph never goes down for either version.

  1. Log in to the DXP Management Portal at https://paasportal.episerver.net.
  2. Open your organization and project, then go to the API tab and open Optimizely Graph Service Configurations. Click Prepare for CMS 13 to provision a separate Graph instance.
  3. Deploy CMS 13 through a deployment slot. The slot uses the new Graph credentials while production keeps using the old ones.
  4. Validate the slot against the CMS 13 schema without touching your live CMS 12 Graph data.
  5. Swap the slot into production. Production automatically picks up the new CMS 13 Graph credentials.

The old CMS 12 Graph instance isn't deleted immediately. It's retired in two stages so anything still pointing at the old keys has time to surface:

  • Stage 1 - Key rotation (soft delete): runs once all linked front-end environments are confirmed on the new key and a grace period (default seven days) has elapsed since that confirmation — or, if there are no linked front-end environments, seven days after go-live. This disables the old keys, so anything still using them starts failing visibly.
  • Stage 2 - Hard delete: the old Graph instance is removed a further 30 days after Stage 1.

That visible failure in Stage 1 is a feature, not a bug. It's your forcing function for finding every forgotten front end, webhook, or scheduled job that still has the old single key hardcoded somewhere.

Option 2: Smooth Rebuild on Your Existing Instance (Downtime Required)

If you'd rather keep your existing Graph instance and credentials (so external consumers never need to update anything), you can migrate the schema in place instead. This requires taking the site offline.

  1. Put the site behind a static maintenance page.
  2. In CMS, go to Settings > Smooth Rebuild, open Smooth Rebuild Settings, and click Start.
  3. Graph creates a new deployment slot (green) alongside the current one (blue) and starts syncing content into it.
  4. Test the green slot before promoting it. Use the Optimizely Graph playground (GraphiQL) with the New toggle enabled, or add the cg-query-new: true header to your GraphQL requests to target it directly.
  5. Deploy CMS 13 through a deployment slot once the rebuild has finished syncing.
  6. Promote the green slot to live.
cg-query-new: true

Because the CMS 13 schema change is breaking, you can't use Graph's normal zero-downtime read-only or read-write slot modes here. The site has to be fully offline behind the maintenance page for the duration of the rebuild.

Important

  • If you haven't implemented Optimizely Graph on your CMS 12 project at all, none of this applies to you. Follow the standard DXP upgrade path through Integration, Preproduction, and Production.
  • Run whichever path you choose in Integration and Preproduction first. You need to know how long the sync or rebuild actually takes before you size a Production maintenance window around it.
  • Audit anything with a hardcoded Graph single key (Next.js front ends, webhooks, scheduled jobs) before you start. Key rotation will break them loudly, which is useful, but you'd rather know in advance than get paged for it.
  • Local development config (GatewayAddress, AppKey, Secret, SingleKey in appsettings.json or web.config) is separate from what DXP auto-provisions on deployment. Don't carry your CMS 12 Graph configuration into your CMS 13 deployment and assume it'll just work.

Which Option Should You Pick?

Situation Go with
Site is live in production and downtime isn't acceptable Option 1: new Graph instance
Site isn't live yet, or a maintenance window is acceptable Option 2: smooth rebuild
Several external consumers have the single key hardcoded Option 1: forced key rotation surfaces every stale config for you
You want zero changes for external Graph consumers Option 2: credentials never change

Either way, don't treat this as a side detail of the CMS 13 upgrade. It's the part of the migration most likely to take down search, navigation, or personalization on your site if you discover it mid-deployment instead of during planning.

Links

October 01, 2026 →

Thursday, 13 August 2026

Optimizely XML Resource Builder: A Visual Studio Extension for Language Resource Files

Anyone who has worked on a larger Optimizely solution will know the feeling. You add or update a page type, block type, or shared base class, and then need to keep the language resource XML in sync. It is not difficult work, but it is repetitive, easy to miss, and becomes more painful as the number of content types grows.

This started as a small pet project for my own convenience. I wanted a quick way to generate a starting point for Optimizely language resource files directly from the content type code already in a project. Once it was useful in day-to-day work, I thought it could also benefit the wider Optimizely community if I packaged it properly and made it publicly available.

That is where Optimizely XML Resource Builder came from.

What it does

Optimizely XML Resource Builder is a Visual Studio extension that scans the project you select and generates an Optimizely language resource file from its C# content type definitions.

It reads content type attributes such as ContentType and SiteContentType, then creates the XML entries for the content type name, description, properties, captions, and help text.

The default output is:

Resources\Translations\ContentTypes-en.xml

You can change both the language code and output folder from the extension dialog.

Why I built it

Language resource files are useful for keeping the Optimizely editor UI clean and consistent, especially when you are working across several languages. The challenge is that the information is usually already present in code:

[ContentType(DisplayName = "Article Page")]
public class ArticlePage : SitePageData
{
    [Display(Name = "Heading", Description = "The main page heading")]
    public virtual string Heading { get; set; }
}

Copying those values into XML by hand adds another maintenance step. The extension uses the information already available in the attributes and gives you a generated file to review, commit, and translate.

Key features

  • Project-level command: right-click an Optimizely project in Solution Explorer to generate the resource XML.
  • Flexible content type detection: scan for SiteContentType, ContentType, or any additional attributes you configure.
  • Configurable generation dialog: choose a comma-separated attribute list, language code, excluded property prefixes, and output folder before generating the file.
  • Caption casing control: keep the original display-name case or convert captions to sentence case while preserving uppercase acronyms.
  • Overwrite control: choose whether to replace the existing XML file or safely add only missing entries.
  • Useful XML generated from code: create content type names, descriptions, property captions, and help text from the attributes already defined in C#.
  • Shared base-class support: find properties on custom base classes and add them under icontentdata.
  • Safe updates: add only missing XML entries by default, preserving existing translations and custom values. Enable overwrite only when you want a fresh file.
  • Solution-specific preferences: save settings per solution, rather than using one global set of values across every project.

How to use it

  1. Install the extension.
  2. Open an Optimizely solution in Visual Studio.
  3. In Solution Explorer, right-click the project that contains the content type classes.
  4. Select Build Optimizely XML Resource File.
  5. Set the attribute list, language code, output folder, and any exclusions.
  6. Select OK.

If no matching content types are found, the extension shows a warning and does not create an empty XML file.

Example output

<contenttypes>
  <icontentdata>
    <properties>
      <metadescription>
        <caption>Meta description</caption>
      </metadescription>
    </properties>
  </icontentdata>
  <articlepage>
    <name>Article Page</name>
    <properties>
      <heading>
        <caption>Heading</caption>
        <help>The main page heading</help>
      </heading>
    </properties>
  </articlepage>
</contenttypes>

Compatibility

The extension supports Visual Studio 2022 and later.

Links

I hope it saves other Optimizely developers the same repetitive work it saves me. If you try it, feedback and contributions are very welcome.

August 13, 2026 →

Tuesday, 21 July 2026

Advanced Task Manager Gets a Big Update for Optimizely CMS

Optimizely's content approval workflow works well when you are approving one item at a time. The problem starts when you are managing a large queue across pages, blocks, and media. Once the list grows, finding the right items, approving them in bulk, or publishing them in a controlled way becomes much more time-consuming than it should be.

This update focuses on solving those real editorial workflow problems. Every feature below came from day-to-day use of the tool in larger Optimizely solutions where too many clicks, too much scrolling, or missing workflow controls were slowing editors down.

Related posts: CMS 12 release post | Legacy CMS 11 post

Advanced filtering

The task list now includes a proper filter bar, making it easier to work with larger approval queues.

  • Status - switch between In Review and Ready to Publish.
  • Type - filter Pages, Blocks, or Assets/Media.
  • Site - narrow the list to one site in multi-site solutions.
  • Content search - search by content ID or part of the content name.

Each filter updates the URL, so filtered views can be bookmarked or shared with other editors.

Note: Active filters appear as removable badges below the filter bar, and a Clear all option removes everything at once.

Select all across pages

The existing table checkbox used to select only the current page of results. This update adds support for selecting all matching items across all pages.

If more results exist than are visible on the current page, a banner appears and lets the editor load every matching approval ID into the current selection. That means bulk approval can now cover the full filtered result set in one action.

This selection respects the active filters, so editors can safely approve only the tasks they intended to include.

Scheduled publishing

Publishing immediately after approval is still supported, but it is no longer the only option.

When Publish selected content after approval is enabled, editors can now choose to schedule publishing for a specific future date and time. The selected datetime is passed into Optimizely's normal publishing pipeline using IVersionable.StartPublish, so standard scheduled publishing behavior is used without any custom publishing job.

Approve blocks and media used by a page

This is one of the most useful additions in the release.

Sometimes a page is ready to go live, but the blocks or media it references are still waiting in their own approval queues. Instead of finding those items one by one, editors can now use Approve Page Dependencies to approve pending block and media dependencies for a selected page in one action.

  1. Open Approve Page Dependencies from the filter bar.
  2. Select a page from the hierarchy tree, or enter the content ID directly.
  3. Choose one or more language branches.
  4. Optionally enter an approval comment.
  5. Optionally publish approved dependencies immediately.
  6. Run the action and review the result count.

Behind the scenes, the tool inspects ContentArea and ContentReference properties on the selected page, gathers referenced blocks and media, and approves the items that still have pending approval steps.

Note: This action approves only the dependencies referenced by the page. It does not approve the page itself.

Task ordering

The task list columns are now sortable. Editors can sort by content name, content type, task type, submission date, who started the review, or deadline if the deadline property is enabled.

This sounds small, but it makes a big difference when triaging long queues and trying to prioritize urgent work.

Site column for multi-site solutions

In multi-site installations, the list now includes a Site column so editors can immediately see which site each task belongs to. In single-site environments, the column stays hidden.

This works especially well with the Site filter when approval queues need to be reviewed site by site.

Version availability

As of July 24, 2026, these features are available in:

  • Version 4.1.0 for Optimizely CMS 13 / .NET 10
  • Version 3.1.0 for the CMS 12 branch

Change Approval support is currently available only in the CMS 12 branch, because EPiServer.ChangeApproval does not yet have a CMS 13-compatible release.

Install or update

dotnet add package AdvancedTaskManager

Links

GitHub repository
NuGet package

Conclusion

This release makes Advanced Task Manager much more useful for teams handling larger approval volumes across pages, blocks, media, sites, and languages. The focus here is less about adding features for their own sake and more about making approval workflows faster, clearer, and easier to control in real Optimizely projects.

If you run into issues or want to suggest further improvements, the source code, changelog, and issue tracker are available on GitHub.

July 21, 2026 →

Friday, 10 July 2026

Optimizely DXP: Every Supported Culture, One Searchable Page

Quick one for anyone building multi-language sites on Optimizely DXP. I put together a reference tool listing all 806 supported cultures. More usefully, it shows which ones actually work with Azure Translator's 1-click auto-translate, and which get full NLP treatment (stemming, tokenization, decompounding) from Optimizely Graph.

If you've ever added a new market and then spent twenty minutes digging through docs to figure out whether editors will get auto-translate for that locale, or whether search will actually work well in that language, this is for that exact moment. Search or filter by culture name or code, grab the exact culture code you need for config, and see right away which capabilities you're getting.

806 cultures. 33 with Azure Translator support. Full Graph NLP coverage mapped out. All in one table.

Check it out here: Optimizely DXP – Supported Cultures / Languages

July 10, 2026 →

Tuesday, 3 March 2026

OpenAI-Driven AI Assistant for TinyMCE in Optimizely CMS 12

The Tiny.AI add-on enhances Optimizely CMS 12 by seamlessly integrating OpenAI directly into the TinyMCE editor. It empowers editors to rewrite, improve, summarize, expand, or translate selected content without leaving the CMS. Instead of copying content into external AI tools and risking formatting issues, Tiny.AI processes HTML safely and returns clean, CMS-ready output.

Installation

The command below will install the add-on in your Optimizely project.

dotnet add package A2Z.Optimizely.Tiny.AI

Configuration

Add your OpenAI configuration inside appsettings.json. The model value below is an example and can be updated to a supported model that fits your setup.

{
  "OpenAI": {
    "ApiKey": "YOUR_API_KEY",
    "Model": "gpt-4o-mini"
  }
}

The module automatically registers required services, the API controller, and the TinyMCE plugin.

Service Registration

The package registers the OpenAI service, circuit breaker, and authorization services automatically through an initialization module.

[ModuleDependency(typeof(InitializationModule))]
public class AiInitialization : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.AddHttpClient<IOpenAiService, OpenAiService>();
        context.Services.AddSingleton<EditorAuthorizationService>();
        context.Services.AddSingleton<SimpleCircuitBreaker>();
    }
}

Usage

Once installed and configured, editors will see a new AI Assistant button inside the TinyMCE toolbar.

TinyAI Icon

How it works:

  1. Select content inside the TinyMCE editor.
  2. Click the AI Assistant button.

  3. Choose an action (Rewrite, Improve, Summarize, Expand, Translate).

  4. Optionally specify a language.
  5. Apply changes and the HTML is replaced instantly.

The AI processes the selected HTML and returns valid HTML only, preserving links and formatting.

Security

Access to Tiny.AI is restricted to users in the following roles:

  • CmsEditors
  • Administrators

Authorization is validated server-side to ensure only permitted users can invoke AI functionality.

Resilience & Performance

Tiny.AI is built for production environments and includes:

  • Retry logic for transient API failures
  • Exponential backoff for rate limiting (429 responses)
  • Circuit breaker protection after repeated failures
  • Structured logging for monitoring
  • Token usage tracking
  • Estimated cost calculation per request

If multiple consecutive failures occur, the circuit breaker temporarily disables AI calls to protect system stability.

Cost Transparency

Each request returns:

  • Prompt tokens
  • Completion tokens
  • Total tokens
  • Estimated cost

This provides visibility into AI usage and allows teams to monitor spending effectively.

API Endpoint

POST /api/editor/ai/action

Example request:

{
  "action": "rewrite",
  "html": "<p>Some content</p>",
  "language": "en"
}

Example response:

{
  "html": "<p>Rewritten content...</p>",
  "tokens": 512,
  "cost": 0.0003
}

Compatibility

Tiny.AI currently supports Optimizely CMS 12 only. Check the GitHub repository or package page for the latest updates and version support.

Why Tiny.AI?

AI is becoming an essential productivity tool for content teams. Tiny.AI integrates OpenAI directly into Optimizely CMS workflows while maintaining security, HTML integrity, and operational resilience.

It eliminates the need for external tools and keeps the editorial workflow fast, clean, and fully integrated.

You can access the code and documentation for Tiny.AI on its GitHub repository.

March 03, 2026 →