<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by pjangid</title><link href="http://world.optimizely.com" /><updated>2026-03-07T16:46:02.0000000Z</updated><id>https://world.optimizely.com/blogs/pjangid/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Lessons from Building Production-Ready Opal Tools</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2026/3/lessons-from-building-production-ready-opal-tools/" /><id>&lt;p&gt;AI tools are becoming a normal part of modern digital platforms. With&amp;nbsp;&lt;strong&gt;Optimizely Opal&lt;/strong&gt;, teams can build tools that automate real tasks across the &lt;strong&gt;Optimizely&lt;/strong&gt; platform.&lt;/p&gt;
&lt;p&gt;Creating a basic Opal tool is fairly straightforward. You define a tool, connect it to an API or some business logic, and allow an agent to call it. Many tutorials stop there.&lt;/p&gt;
&lt;p&gt;But in real projects, things are rarely that simple.&lt;/p&gt;
&lt;p&gt;External APIs fail. Data might be incomplete. Requests might take longer than expected. If these situations are not handled properly, your Opal workflows can break or produce incorrect results.&lt;/p&gt;
&lt;p&gt;In this article, we will look at a few important practices that help you build &lt;strong&gt;production-ready Opal tools&lt;/strong&gt;. These include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Proper error handling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Logging and observability&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Structured responses&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Performance considerations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Security practices&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the same things we normally think about when building enterprise applications.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Understanding How an Opal Tool Works&lt;/h1&gt;
&lt;p&gt;Before going deeper, it helps to understand the typical flow of an Opal tool.&lt;/p&gt;
&lt;p&gt;A simplified flow usually looks like this:&lt;/p&gt;
&lt;pre&gt;User Request
     &amp;darr;
Opal Agent
     &amp;darr;
Opal Tool
     &amp;darr;
External Service or API
     &amp;darr;
Response returned to the Agent&lt;br /&gt;
&lt;/pre&gt;
&lt;p&gt;For example, imagine a marketer asking Opal:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Check if the price updates for the latest products are correctly applied.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The agent might call a &lt;strong&gt;Price Validation Tool&lt;/strong&gt;, which then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Reads the expected price data&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Calls a product API&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compares the values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Returns the result&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This looks simple, but many things can go wrong along the way. That&amp;rsquo;s why good design is important.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Handling Errors Properly&lt;/h1&gt;
&lt;p&gt;One of the most common issues in production systems is &lt;strong&gt;unhandled errors&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say your tool calls a product API. What happens if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the API is temporarily unavailable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the response takes too long&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the product does not exist&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the returned data is incomplete&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your tool simply crashes or returns a generic error, the agent will not know what to do.&lt;/p&gt;
&lt;p&gt;A better approach is to return &lt;strong&gt;clear and structured error information&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Example response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;status&quot;: &quot;error&quot;,
  &quot;errorType&quot;: &quot;API_TIMEOUT&quot;,
  &quot;message&quot;: &quot;The product service did not respond within 5 seconds.&quot;,
  &quot;suggestedAction&quot;: &quot;Retry the request.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This helps the agent understand what happened and possibly retry or suggest another action.&lt;/p&gt;
&lt;h3&gt;Example Scenario&lt;/h3&gt;
&lt;p&gt;Imagine a tool that checks whether a product exists in the system.&lt;/p&gt;
&lt;p&gt;Instead of returning something vague like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Error occurred
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Return something useful:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;productId&quot;: &quot;P12345&quot;,
  &quot;status&quot;: &quot;not_found&quot;,
  &quot;message&quot;: &quot;Product does not exist in the catalog.&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes troubleshooting much easier.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Returning Structured Data&lt;/h1&gt;
&lt;p&gt;AI agents work much better when the tool returns &lt;strong&gt;structured data instead of plain text&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, consider a tool that verifies product prices.&lt;/p&gt;
&lt;p&gt;Bad example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The price seems different from what we expected.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Better example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;sku&quot;: &quot;SKU-234&quot;,
  &quot;expectedPrice&quot;: 19.99,
  &quot;currentPrice&quot;: 21.49,
  &quot;status&quot;: &quot;price_mismatch&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This format allows the agent to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;detect mismatches automatically&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;trigger additional checks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;generate useful reports.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Structured responses also make it easier to reuse the tool in different workflows.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Logging and Observability&lt;/h1&gt;
&lt;p&gt;When something goes wrong in an AI workflow, debugging can be difficult if you do not have good logs.&lt;/p&gt;
&lt;p&gt;Logging helps you answer questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Which tool was executed?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What input was provided?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How long did it take?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Did the API call succeed?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A simple logging format might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Timestamp: 2026-03-07 10:12:04
Agent: ProductValidationAgent
Tool: PriceValidationTool
ExecutionTime: 2.4 seconds
Status: Success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If an error occurs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Timestamp: 2026-03-07 10:15:10
Agent: ProductValidationAgent
Tool: PriceValidationTool
ExecutionTime: 5.1 seconds
Status: Failed
Error: API Timeout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This information becomes extremely helpful when diagnosing problems in production.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Example: SKU Price Validation Tool&lt;/h1&gt;
&lt;p&gt;Let&amp;rsquo;s look at a simple but practical example.&lt;/p&gt;
&lt;p&gt;Suppose a team updates product prices in bulk and wants to verify that the updates were applied correctly.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;Price Validation Tool&lt;/strong&gt; could follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Receive a SKU number&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fetch the expected price from a spreadsheet or internal system&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Call the product API&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compare the values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Return the result&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example response:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;sku&quot;: &quot;SKU-987&quot;,
  &quot;expectedPrice&quot;: 24.99,
  &quot;systemPrice&quot;: 24.99,
  &quot;status&quot;: &quot;verified&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is a mismatch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;sku&quot;: &quot;SKU-987&quot;,
  &quot;expectedPrice&quot;: 24.99,
  &quot;systemPrice&quot;: 26.99,
  &quot;status&quot;: &quot;mismatch&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An Opal agent could then generate a report for the operations team.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Improving Performance&lt;/h1&gt;
&lt;p&gt;When AI agents run workflows, they may call multiple tools in sequence.&lt;/p&gt;
&lt;p&gt;If each tool takes several seconds, the entire workflow could become slow.&lt;/p&gt;
&lt;p&gt;Here are a few simple ways to improve performance.&lt;/p&gt;
&lt;h3&gt;Use caching&lt;/h3&gt;
&lt;p&gt;If the same data is requested multiple times, cache it temporarily instead of calling the API repeatedly.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;p&gt;If the tool checks the same product data multiple times during a workflow, store the response for a few minutes.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Reduce unnecessary API calls&lt;/h3&gt;
&lt;p&gt;If possible, fetch multiple items in a single request.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Call API for Product A
Call API for Product B
Call API for Product C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Call API once for Products A, B, and C
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h3&gt;Run independent tools in parallel&lt;/h3&gt;
&lt;p&gt;Some checks can happen at the same time.&lt;/p&gt;
&lt;p&gt;Example product validation workflow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Product Validation Workflow
 ├─ Price Validation Tool
 ├─ Inventory Check Tool
 └─ Search Index Check Tool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since these checks are independent, they can run in parallel, reducing overall execution time.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Security Considerations&lt;/h1&gt;
&lt;p&gt;Security is always important when tools interact with external systems.&lt;/p&gt;
&lt;p&gt;A few basic practices can help avoid common problems.&lt;/p&gt;
&lt;h3&gt;Protect API credentials&lt;/h3&gt;
&lt;p&gt;Never hardcode credentials in the tool code. Instead, use environment variables or secure configuration systems.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Validate inputs&lt;/h3&gt;
&lt;p&gt;Agents may pass unexpected inputs. Always validate parameters before calling external services.&lt;/p&gt;
&lt;p&gt;Example validation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Check if SKU format is valid&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure price values are numeric&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prevent empty inputs&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;Limit tool permissions&lt;/h3&gt;
&lt;p&gt;Each tool should only access the resources it needs.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;content validation tool&lt;/strong&gt; should not modify content.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;reporting tool&lt;/strong&gt; should not update product data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This reduces the risk of accidental changes.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Monitoring and Continuous Improvement&lt;/h1&gt;
&lt;p&gt;Once tools are running in production, it is useful to track their performance over time.&lt;/p&gt;
&lt;p&gt;Some helpful metrics include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;tool execution time&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;success vs failure rate&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;number of API errors&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;most frequently used tools&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These metrics can highlight areas where improvements are needed.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;If a tool frequently fails due to timeouts, you might need to improve the API performance or add retry logic.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Final Thoughts&lt;/h1&gt;
&lt;p&gt;As AI becomes more integrated into enterprise platforms, the quality of the underlying tools becomes increasingly important.&lt;/p&gt;
&lt;p&gt;A well-designed tool should not only complete its task but also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;handle failures gracefully&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;provide clear responses&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;log useful information&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;perform efficiently&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;follow security best practices&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By applying these practices while building tools for &lt;strong&gt;Optimizely Opal&lt;/strong&gt;, teams can create AI workflows that are reliable enough for real business operations.&lt;/p&gt;
&lt;p&gt;Feel free to share your thought as an outcome of yout experience with implementing the &lt;strong&gt;Optimizely Opal Tools.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2026-03-07T16:46:02.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Content Flexibility vs Control: A Deep Dive into Optimizely Content Modeling</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2025/12/contentarea-vs-ilistcontentreference-in-optimizely-cms/" /><id>&lt;h3&gt;Choosing the Right Property Type for Better Editor Experience and Performance&lt;/h3&gt;
&lt;p&gt;When modeling content in &lt;strong&gt;Optimizely CMS&lt;/strong&gt;, one of the most common design decisions developers face is choosing between &lt;strong&gt;&lt;code&gt;ContentArea&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At first glance, both allow you to reference multiple content items. However, they serve &lt;strong&gt;very different purposes&lt;/strong&gt;, impact &lt;strong&gt;editor experience&lt;/strong&gt;, and influence &lt;strong&gt;long-term maintainability&lt;/strong&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;This article breaks down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Key differences&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Benefits and limitations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best practices&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When to use one over the other&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Understanding the Two Approaches&lt;/h2&gt;
&lt;h3&gt;ContentArea&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ContentArea&lt;/code&gt; is a &lt;strong&gt;first-class CMS concept&lt;/strong&gt; designed for flexible, editor-driven content composition.&lt;/p&gt;
&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;
&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;
&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;
&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow-y-auto p-4&quot;&gt;&lt;code class=&quot;whitespace-pre! language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; ContentArea MainContent { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }
&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It allows editors to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create blocks inline&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Drag &amp;amp; drop blocks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reorder content&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply visitor groups&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mix different block types&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt; is a &lt;strong&gt;simple reference list&lt;/strong&gt; that points to existing content items.&lt;/p&gt;
&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;
&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;
&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;
&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow-y-auto p-4&quot;&gt;&lt;code class=&quot;whitespace-pre! language-csharp&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; IList&amp;lt;ContentReference&amp;gt; OtherLinks { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }
&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;It is more &lt;strong&gt;structured and controlled&lt;/strong&gt;, but offers fewer editor features.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Editor Experience Comparison&lt;/h2&gt;
&lt;div class=&quot;TyagGW_tableContainer&quot;&gt;
&lt;div class=&quot;group TyagGW_tableWrapper flex w-fit flex-col-reverse&quot;&gt;
&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ContentArea&lt;/th&gt;
&lt;th&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create new block&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drag &amp;amp; drop&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reorder items&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline editing&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visitor groups&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed content types&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⚠️ Possible but limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured control&lt;/td&gt;
&lt;td&gt;⚠️ Less&lt;/td&gt;
&lt;td&gt;✅ More&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h2&gt;Why &amp;ldquo;Create Block&amp;rdquo; Is Missing for IList&amp;lt;ContentReference&amp;gt;&lt;/h2&gt;
&lt;p&gt;Even with attributes like:&lt;/p&gt;
&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;
&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;
&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;
&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow-y-auto p-4&quot;&gt;&lt;code class=&quot;whitespace-pre! language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;AllowedTypes(typeof(MyBlock))&lt;/span&gt;]
&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Optimizely &lt;strong&gt;will not show a &amp;ldquo;Create new block&amp;rdquo; button&lt;/strong&gt; for &lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt; is treated as a &lt;strong&gt;picker&lt;/strong&gt;, not a container&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It assumes content already exists elsewhere&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It does not support inline content creation by design&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is intentional and aligns with Optimizely&amp;rsquo;s content model philosophy.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;When You SHOULD Use ContentArea&lt;/h2&gt;
&lt;p&gt;Use &lt;strong&gt;ContentArea&lt;/strong&gt; when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Editors need &lt;strong&gt;flexibility&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Content structure may evolve&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Blocks are &lt;strong&gt;page-specific&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inline creation improves productivity&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Visitor group personalization is required&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Typical use cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Page body content&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Landing page sections&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flexible navigation areas&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Campaign-driven layouts&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Best practice&lt;/h3&gt;
&lt;div class=&quot;contain-inline-size rounded-2xl corner-superellipse/1.1 relative bg-token-sidebar-surface-primary&quot;&gt;
&lt;div class=&quot;sticky top-[calc(--spacing(9)+var(--header-height))] @w-xl/main:top-9&quot;&gt;
&lt;div class=&quot;absolute end-0 bottom-0 flex h-9 items-center pe-2&quot;&gt;
&lt;div class=&quot;bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow-y-auto p-4&quot;&gt;&lt;code class=&quot;whitespace-pre! language-csharp&quot;&gt;[&lt;span class=&quot;hljs-meta&quot;&gt;AllowedTypes(typeof(NavigationItemBlock))&lt;/span&gt;]
&lt;span class=&quot;hljs-keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;virtual&lt;/span&gt; ContentArea NavigationItems { &lt;span class=&quot;hljs-keyword&quot;&gt;get&lt;/span&gt;; &lt;span class=&quot;hljs-keyword&quot;&gt;set&lt;/span&gt;; }
&lt;/code&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This gives editors full control while keeping block types constrained.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;When You SHOULD Use IList&amp;lt;ContentReference&amp;gt;&lt;/h2&gt;
&lt;p&gt;Use &lt;strong&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/strong&gt; when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Content should be &lt;strong&gt;centrally managed&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Editors must select from &lt;strong&gt;pre-approved items&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Structure should remain consistent&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want to prevent content sprawl&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Typical use cases&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Global navigation links&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Footer links&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Legal or compliance-driven content&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Shared CTA collections&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Controlled taxonomies&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach encourages &lt;strong&gt;reuse and governance&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Performance Considerations&lt;/h2&gt;
&lt;div class=&quot;TyagGW_tableContainer&quot;&gt;
&lt;div class=&quot;group TyagGW_tableWrapper flex w-fit flex-col-reverse&quot;&gt;
&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;ContentArea&lt;/th&gt;
&lt;th&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rendering&lt;/td&gt;
&lt;td&gt;Slightly heavier&lt;/td&gt;
&lt;td&gt;Lightweight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching predictability&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk loading&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Easier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;For navigation and header/footer elements, &lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt; is often preferred for predictability and performance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;Recommended Hybrid Pattern (Best of Both Worlds)&lt;/h2&gt;
&lt;p&gt;Many mature Optimizely solutions use &lt;strong&gt;both&lt;/strong&gt;, intentionally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ContentArea&lt;/strong&gt; for page-level, flexible content&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/strong&gt; for global, reusable elements&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Header navigation &amp;rarr; &lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mega menu content &amp;rarr; &lt;code&gt;ContentArea&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This balances &lt;strong&gt;editor freedom&lt;/strong&gt; with &lt;strong&gt;architectural control&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Final Recommendation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do not choose based on convenience &amp;mdash; choose based on content ownership and editorial intent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Quick decision guide:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Should editors freely compose content? &amp;rarr;&amp;nbsp;&lt;strong&gt;ContentArea&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Should content be reusable and controlled? &amp;rarr; &lt;strong&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting this decision right early prevents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Editor frustration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Over-engineering&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Content duplication&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Long-term maintenance issues&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;Content modeling is one of the most impactful architectural decisions in Optimizely CMS. Understanding the difference between &lt;code&gt;ContentArea&lt;/code&gt; and &lt;code&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/code&gt; allows you to build solutions that are not only technically sound, but also &lt;strong&gt;editor-friendly and scalable&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2025-12-29T19:35:01.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Boosting Indexing Efficiency: Reindex Pages Directly from Optimizely’s Navigation Pane</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2025/6/adding-html-templates-in-rte-field-tinymce/" /><id>&lt;p&gt;There can be various reasons why you might want to trigger indexing or reindexing of a page/node directly from the navigation pane. In my case, we were working with a large volume of content and needed to test newly introduced custom indexing fields. Waiting for the entire site to be reindexed through the standard indexing job wasn&amp;rsquo;t feasible.&lt;/p&gt;
&lt;p&gt;While there are certainly other ways to handle this, adding a manual reindex trigger proved especially helpful in our upper environments (such as Integration and Preproduction), where faster feedback loops are critical.&lt;/p&gt;
&lt;p&gt;Here&#39;s a quick look at how it appears in the UI:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/731db5e34b81489d9e5a17fa840ae8aa.aspx&quot; width=&quot;461&quot; height=&quot;476&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;To implement this, I used&amp;nbsp;&lt;strong&gt;Dojo&lt;/strong&gt; to create new navigation items and added them to Optimizely&amp;rsquo;s plug-in area within the navigation tree. Here&#39;s the setup from&amp;nbsp;&lt;code&gt;Initializer.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;define([
    &quot;dojo&quot;,
    &#39;dojo/_base/declare&#39;,
    &#39;epi-cms/plugin-area/navigation-tree&#39;,
    &#39;epi/_Module&#39;,
    &#39;optimizelyModules/ReIndexTree/ReIndexTree&#39;,
    &#39;optimizelyModules/ReIndexTree/ReIndexChildren&#39;
], function (
    dojo,
    declare,
    navigationTreePluginArea,
    _Module,
    ReIndexTree,
    ReIndexChildren
) {

    return declare([_Module], {
        initialize: function () {
            this.inherited(arguments);
            navigationTreePluginArea.add(ReIndexTree);
            navigationTreePluginArea.add(ReIndexChildren);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Both &lt;code&gt;ReIndexChildren.js&lt;/code&gt; and &lt;code&gt;ReIndexTree.js&lt;/code&gt; follow a similar pattern, differing mainly in naming and intent. For reference, here&amp;rsquo;s the code for &lt;code&gt;ReIndexChildren.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;define([
    &quot;dojo/topic&quot;,
    &quot;dojo/_base/declare&quot;,
    &quot;epi/dependency&quot;,
    &quot;epi/shell/XhrWrapper&quot;,
    &quot;epi/shell/command/_Command&quot;
], function (topic, declare, dependency, XhrWrapper, _Command) {

    return declare([_Command], {

        label: &quot;Reindex Children&quot;,
        iconClass: &quot;epi-iconReload epi-icon--success&quot;,

        constructor: function () {
            var registry = dependency.resolve(&quot;epi.storeregistry&quot;);
            this.store = registry.get(&quot;epi.cms.contentdata&quot;);
        },

        _execute: function () {
            var contentLinkId = this.model.contentLink;

            // Create an XhrWrapper for making the API call
            var xhr = new XhrWrapper();
            xhr.xhrGet({
                url: &quot;/api/indexing/reindex-tree&quot;,
                handleAs: &quot;json&quot;,

                content: {
                    contentLinkId: contentLinkId,
                    childrenOnly: true
                },

                failOk: true,

                load: function (response) {
                    // Handle the success response (e.g., show a message)
                    console.log(&quot;Reindexing successful:&quot;, response);
                },

                error: function (response) {
                    // Handle the error response (e.g., show an error message)
                    console.error(&quot;Error reindexing:&quot;, error);
                }
            });
        },

        _onModelChange: function () {
            this.set(&quot;canExecute&quot;, true);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In the above implementation, I&amp;rsquo;m calling a custom controller endpoint at&amp;nbsp;&lt;code&gt;/api/indexing/reindex-tree&lt;/code&gt;. This endpoint accepts two parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;childrenOnly&lt;/code&gt;: A flag indicating whether to reindex only the direct children or the entire content tree under the selected node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;contentLinkId&lt;/code&gt;: The ID of the selected node, which is used to identify the starting point for reindexing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach gives editors or developers an easy way to trigger indexing selectively, without needing to reindex the entire site.&lt;/p&gt;
&lt;p&gt;You can find the full code in the GitHub repository (branch) here: &lt;a href=&quot;https://github.com/pjangid/OptimizelyReindexModule/tree/feature/opti-module-reindex-tree&quot;&gt;https://github.com/pjangid/OptimizelyReindexModule/tree/feature/opti-module-reindex-tree&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;As always, there&amp;rsquo;s room for improvement. I&amp;rsquo;d love to hear your thoughts&amp;mdash;please feel free to leave suggestions or feedback in the comments below.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Thanks &amp;amp; Regard,&lt;/p&gt;
&lt;p&gt;Praful&lt;/p&gt;</id><updated>2025-06-11T04:48:36.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Optimizely SaaS vs PaaS: A Comparison from Client and Developer Perspectives</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2025/2/optimizely-saas-vs-paas-a-comparison-from-client-and-developer-perspectives/" /><id>&lt;p&gt;Optimizely, one of the leading digital experience platform. Offering both &lt;strong&gt;Software-as-a-Service (SaaS)&lt;/strong&gt; and &lt;strong&gt;Platform-as-a-Service (PaaS)&lt;/strong&gt; solutions. Organizations choosing between these options must consider factors such as scalability, customization, maintenance, and development flexibility. In this article we are going to talk about the key differences between Optimizely SaaS and PaaS from both &lt;strong&gt;client&lt;/strong&gt; and &lt;strong&gt;developer&lt;/strong&gt; perspectives.&lt;/p&gt;
&lt;h2&gt;Overview of Optimizely SaaS and PaaS&lt;/h2&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Optimizely SaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fully managed cloud-based service.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatic updates and patches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Limited customization but quick deployment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best suited for businesses looking for a low-maintenance, high-availability solution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Optimizely PaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Hosted in a managed cloud environment with more control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Requires ongoing maintenance, including updates and security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Offers greater flexibility for customization and integration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best suited for businesses needing tailored solutions with specific functionalities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Comparison from a Client&amp;rsquo;s Perspective&lt;/h2&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Cost &amp;amp; Pricing&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Subscription-based pricing with predictable costs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Higher upfront costs with variable hosting expenses.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Ease of Use &amp;amp; Maintenance&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Managed by Optimizely, reducing IT overhead and maintenance efforts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Requires internal IT resources for deployment, security, and scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Scalability &amp;amp; Performance&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Automatically scales with demand, ensuring uptime and performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Scaling requires configuration and monitoring from the client&amp;rsquo;s IT team.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Customization &amp;amp; Flexibility&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Limited customization options; best for businesses with standard needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: High degree of customization, allowing unique integrations and tailored workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Security &amp;amp; Compliance&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Security and compliance are managed by Optimizely, reducing client concerns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Requires internal governance and security management, adding complexity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Comparison from a Developer&amp;rsquo;s Perspective&lt;/h2&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Development Flexibility&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Limited ability to customize backend functionalities; mostly front-end modifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Full control over development, with access to APIs, extensions, and third-party integrations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;DevOps &amp;amp; CI/CD&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Minimal need for DevOps management, as infrastructure is handled by Optimizely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Requires setup and maintenance of CI/CD pipelines, infrastructure, and monitoring.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Integration Capabilities&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Predefined integrations and API limitations may restrict some use cases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Allows deep integrations with custom systems, databases, and third-party services.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Deployment &amp;amp; Version Control&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Automatic updates without client intervention; risk of breaking changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Controlled deployment cycles, allowing developers to test and release updates at their own pace.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Which One Should You Choose?&lt;/h2&gt;
&lt;table style=&quot;margin-left: 40px;&quot;&gt;
&lt;tbody style=&quot;padding-left: 40px;&quot;&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;th style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Factor&lt;/th&gt;
&lt;th style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Optimizely SaaS&lt;/th&gt;
&lt;th style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Optimizely PaaS&lt;/th&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Cost &amp;amp; Pricing&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Predictable, subscription-based&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Higher upfront, variable costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Maintenance&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Fully managed by Optimizely&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Requires internal IT management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Customization&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Limited&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Highly customizable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Scalability&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Automatic&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Requires configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Security &amp;amp; Compliance&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Managed by Optimizely&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Client-managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Developer Flexibility&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Limited backend access&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Full control over development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Deployment Control&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Automatic updates&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Controlled release cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;When to Choose Optimizely SaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you need a &lt;strong&gt;low-maintenance&lt;/strong&gt; solution with &lt;strong&gt;automatic updates&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your business processes fit within standard Optimizely capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If cost predictability and ease of use are top priorities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;When to Choose Optimizely PaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If your business requires &lt;strong&gt;custom integrations and development flexibility&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have an in-house IT team to manage updates, security, and scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you need greater control over &lt;strong&gt;deployment cycles and infrastructure&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Short &amp;amp; Sweet Conclusion&lt;/h2&gt;
&lt;p&gt;Both &lt;strong&gt;Optimizely SaaS and PaaS&lt;/strong&gt; offer robust solutions, but the right choice depends on your organization&amp;rsquo;s needs. &lt;strong&gt;Clients&lt;/strong&gt; should weigh cost, maintenance, and customization, while &lt;strong&gt;developers&lt;/strong&gt; should consider flexibility and control over the infrastructure. By carefully assessing these factors, businesses can ensure they select the best Optimizely platform for their digital experience strategy. You can read for more information on the &lt;a href=&quot;https://docs.developers.optimizely.com/&quot;&gt;Optimizely&#39;s documentations site&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/link/6d3bb925352f40a183d98c7fe6aae451.aspx&quot;&gt;https://world.optimizely.com/products/cms/saas/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</id><updated>2025-02-03T13:09:40.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Bulk publishing of unpublished content items/nodes</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2021/2/bulk-publishing-of-unpublished-content-itemsnodes/" /><id>&lt;p&gt;In Episerver, we don&#39;t have any built in feature that allows us to publish full site (bulk items) in one go. We recently faced one challenge to publish all category nodes.&lt;/p&gt;
&lt;p&gt;In CMS, we have an option of Project to perform bulk item update (change of state or publish). But in commerce we don&#39;t. And, in our case, we did import of content using custom code and a huge number of items were not published (even we used the SaveAction.Publish).&lt;/p&gt;
&lt;p&gt;Then, we decided to write custom code to do that (via scheduled job). And, after doing some RnD, I found that the following Method returns only published items.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContentRepository.GetChildren&amp;lt;T&amp;gt;(ContentReference contentLink)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, the question is how to retrieve the unpublished node/items?&lt;/p&gt;
&lt;p&gt;Here is the answer, use the Language option with GetChildren method.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContentRepository.GetChildren&amp;lt;NodeContent&amp;gt;(parentNode.ContentLink, LanguageSelector.AutoDetect(true))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is just a trick to get the unpublished items/nodes. And, for bulk publish, you can use the&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContentRepository.Publish(listOfPages);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s it. You are done.&lt;/p&gt;

&lt;p&gt;Thanks&lt;/p&gt;</id><updated>2021-02-06T07:26:50.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Skip jobs to execute on specific day(s) of the week</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2019/12/skip-jobs-to-execute-on-specific-days-of-the-week/" /><id>&lt;p&gt;Hi All,&lt;/p&gt;
&lt;p&gt;Recently came across a requirement of skipping jobs execution on weekends. So, I thought why not allow editors to control all/any jobs to stop execute on specific days of week.&lt;/p&gt;
&lt;p&gt;So, I took inspiration from &lt;strong&gt;@Paul&lt;/strong&gt;&#39;s reply on &lt;a href=&quot;/link/e348c78e8746471f931272aa98573e0b.aspx&quot;&gt;this thread&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, I created SelectionFactory for all existing jobs on StartPage (Home page), you can create this property on your setting item (as per your wish). Along with another property to select days of week to disable the jobs.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[Display(
    Name = &quot;Scheduled jobs&quot;,
    Description = &quot;Select jobs to stop execution on specific day&quot;,
    GroupName = Global.GroupNames.SiteSettings,
    Order = 1000)]
[SelectMany(SelectionFactoryType = typeof(ScheduledJobsSelectionFactory))]
public virtual string ScheduledJobs { get; set; }

[Display(
    Name = &quot;Skip job execution on day(s)&quot;,
    Description = &quot;Select days on which you don&#39;t want to execute jobs&quot;,
    GroupName = Global.GroupNames.SiteSettings,
    Order = 1010)]
[SelectMany(SelectionFactoryType = typeof(WeekDaysSelectionFactory))]
public virtual string SkipJobExecutionOnDays { get; set; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which something will look like this in our CMS (in below image).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/a8d8ed65d9564528b5868f946fcedfdf.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;[I selected some jobs and days to disable].&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*I will add code of selection factories at the end&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, our scheduled job that will scan all our jobs and check if their next execution is fallin on the skip day, then just updated next execution field and saved back. Here this code contains function to get HomePage.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ScheduledPlugIn(DisplayName = &quot;Skip JobExecution Scheduled Job&quot;)]
public class SkipJobExecutionScheduledJob : ScheduledJobBase
{
    private bool _stopSignaled;
    private readonly IContentLoader _contentLoader;
    private readonly IScheduledJobRepository _scheduledJobRepository;

    public SkipJobExecutionScheduledJob(
        IContentLoader contentLoader,
        IScheduledJobRepository scheduledJobRepository)
    {
        _contentLoader = contentLoader ?? throw new ArgumentNullException(nameof(contentLoader));
        _scheduledJobRepository = scheduledJobRepository ?? throw new ArgumentNullException(nameof(scheduledJobRepository));
        IsStoppable = true;
    }

    /// &amp;lt;summary&amp;gt;
    /// Called when a user clicks on Stop for a manually started job, or when ASP.NET shuts down.
    /// &amp;lt;/summary&amp;gt;
    public override void Stop()
    {
        _stopSignaled = true;
    }

    /// &amp;lt;summary&amp;gt;
    /// Called when a scheduled job executes
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;A status message to be stored in the database log and visible from admin mode&amp;lt;/returns&amp;gt;
    public override string Execute()
    {
        //Call OnStatusChanged to periodically notify progress of job for manually started jobs
        OnStatusChanged($&quot;Starting execution of {this.GetType()}&quot;);

        var sb = new StringBuilder();
        var homePage = this.GetHomePage();
        if (homePage == null) return $&quot;Could not retrieve site settings&quot;;

        var jobs = homePage.ScheduledJobs?.Split(&#39;,&#39;).ToList();
        var jobToDisable = jobs?.Select(x =&amp;gt; this._scheduledJobRepository.Get(Guid.Parse(x))).ToList()
               ?? new List&amp;lt;ScheduledJob&amp;gt;();
        var skipJobExecutionOnDays = homePage.SkipJobExecutionOnDays?.Split(&#39;,&#39;).ToList();
        if (jobToDisable.Count &amp;gt; 0 &amp;amp;&amp;amp; skipJobExecutionOnDays != null &amp;amp;&amp;amp; skipJobExecutionOnDays.Count &amp;gt; 0)
        {
            foreach (var job in jobToDisable)
            {
                if (skipJobExecutionOnDays.Contains(job.NextExecution.DayOfWeek.ToString()))
                {
                    job.NextExecution = job.NextExecution.AddDays(1);
                    this._scheduledJobRepository.Save(job);
                    OnStatusChanged($&quot;Updated \&quot;{job.Name}\&quot; to run on \&quot;{job.NextExecution}\&quot;&quot;);
                    sb.AppendLine($&quot;Updated \&quot;{job.Name}\&quot; to run on \&quot;{job.NextExecution}\&quot;&quot;);
                }

                //For long running jobs periodically check if stop is signaled and if so stop execution
                if (_stopSignaled)
                {
                    return &quot;Stop of job was called&quot;;
                }
            }
        }

        //For long running jobs periodically check if stop is signaled and if so stop execution
        if (_stopSignaled)
        {
            return &quot;Stop of job was called&quot;;
        }
        var rtn = sb.ToString();
        if (rtn == string.Empty)
        {
            rtn = &quot;Job completed successfully. No updates required.&quot;;
        }
        return rtn;
    }

    private StartPage GetHomePage()
    {
        var pageLink = SiteDefinition.Current.StartPage;
        if (pageLink == null || pageLink == ContentReference.EmptyReference)
            return null;

        this._contentLoader.TryGet(pageLink, out PageData page);

        if (page == null)
            return null;

        return this.GetHomePage(page);
    }

    private StartPage GetHomePage(PageData page)
    {
        if (page == null)
            return null;

        if (page is StartPage homePage)
            return homePage;

        homePage = this._contentLoader
                .GetAncestors(page.ContentLink)
                .FirstOrDefault(x =&amp;gt; x is StartPage)
            as StartPage;

        return homePage;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition, I am adding selection factory code here.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class ScheduledJobsSelectionFactory: ISelectionFactory
{
    public Injected&amp;lt;IScheduledJobRepository&amp;gt; ScheduledJobRepository { get; set; }

    public IEnumerable&amp;lt;ISelectItem&amp;gt; GetSelections(ExtendedMetadata metadata)
    {
        var scheduledJobs = this.ScheduledJobRepository.Service.List();

        var selections =
            scheduledJobs
                .Select(x =&amp;gt; new SelectItem
                {
                    Text = x.Name,
                    Value = x.ID.ToString()
                })
                .ToList();

        return selections;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class WeekDaysSelectionFactory : ISelectionFactory
{
    public IEnumerable&amp;lt;ISelectItem&amp;gt; GetSelections(ExtendedMetadata metadata)
    {
        return Enum
            .GetNames(typeof(DayOfWeek))
            .Select(x =&amp;gt; new SelectItem { Text = x, Value = x })
            .ToList();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a special note, you can add fields for time range to skip in that duration (Stop Execution Time and Start Execution Time).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Special Note: &lt;/strong&gt;&lt;span class=&quot;ILfuVd&quot;&gt;&lt;span class=&quot;e24Kjd&quot;&gt;This implementation is more focused with single site implementation&lt;/span&gt;&lt;/span&gt;. If you have jobs that are site specific you can follow comment by &lt;strong&gt;@Antti &lt;/strong&gt;(below in comments).&lt;/p&gt;
&lt;p&gt;And to restrict the access you can follow &lt;a href=&quot;/link/6c91863219a14ab988fe035a60573808.aspx&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reach-out to me if you have any question or concern :)&lt;/p&gt;
&lt;p&gt;Thanks &amp;amp; Regards&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;
&lt;p&gt;Happy Coding!&lt;/p&gt;</id><updated>2019-12-07T09:09:18.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Disabling Cache for Test Driven (page with active A/B test) content</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2019/11/disabling-cache-for-test-driven-content/" /><id>&lt;p&gt;Caching is a great feature to improve the site performance. But recently we faced issue with test driven content being cached. The user was seeing the same test content that get loaded first because it get cached. And, there were no good solution to vary content based on test driven content. So, we came up with a solution to disable cache for test driven content (page with active A/B testing).&lt;/p&gt;
&lt;p&gt;In order to do that you need to find if there is any active test for context item. I created an attribute by inheriting from default &lt;strong&gt;ContentOutputCacheAttribute&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class CustomContentOutputCacheAttribute : ContentOutputCacheAttribute
{
     private static readonly Injected&amp;lt;IMarketingTestingWebRepository&amp;gt; _marketingTestingWebRepository;

    public CustomContentOutputCacheAttribute()
    {
        this.UseOutputCacheValidator = UseOutputCache;
    }

    private static bool UseOutputCache(IPrincipal principal, HttpContextBase context, TimeSpan duration)
    {
        var url = context.Request.Url?.ToString();
        var content = UrlResolver.Current.Route(new UrlBuilder(url));
        if (content == null) return false;
        return !_marketingTestingWebRepository.Service.GetActiveTestsByOriginalItemId(content.ContentGuid).Any();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, next step is apply this attribute to your Page controller Index method. You can see the test content is not loading from cached.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class StoryPageController : PageController&amp;lt;StoryPage&amp;gt;
{
    [CustomContentOutputCache(Duration = 7200)]
    public ActionResult Index(StoryPage currentPage)
    {
        return this.View(currentPage);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, open your browser and check in network tab if the current page &lt;strong&gt;Response Headers &amp;gt; Cache-Control &lt;/strong&gt;value. If you have any active test running for current page then the value should be &lt;strong&gt;private &lt;/strong&gt;otherwise public with max-age=7200 [value set by you for how long you want to cache].&lt;/p&gt;
&lt;p&gt;Thanks and regards&lt;/p&gt;
&lt;p&gt;Happy Coding&lt;/p&gt;</id><updated>2019-11-13T09:16:43.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Make property help text permanently visible</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2019/10/make-property-help-text-permanently-visible/" /><id>&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Supported upto CMS version 12.9&lt;/p&gt;
&lt;p&gt;In continuous efforts to make editors life easy, it will be helpful to make the property help text (shown in below image) permanently visible.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/9cbcc2248052421e9e01d795e519be3d.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In reference to this &lt;a href=&quot;https://talk.alfnilsson.se/2014/12/18/display-help-text-in-on-page-editing/&quot;&gt;blog post&lt;/a&gt;, I would like address one &lt;a href=&quot;/link/405ece92732f4a2a974c9fa6aaef9c92.aspx&quot;&gt;issue&lt;/a&gt; that, when we try to create block directly from content area those help text are not visible.&lt;/p&gt;
&lt;p&gt;So, Let&#39;s start...&lt;/p&gt;
&lt;p&gt;We will add CSS properties, that will add title text into html element&#39;s :after selector (for elements that contains help text in title attribute).&lt;/p&gt;
&lt;p&gt;In Episerver, to add a css you will need &lt;strong&gt;module.config&lt;/strong&gt; file, to be added in main project. Create a module.config file and paste the following lines (don&#39;t forget to update the path to your css file)&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;&amp;lt;module xmlns:xdt=&quot;http://schemas.microsoft.com/XML-Document-Transform&quot;&amp;gt;
  &amp;lt;clientResources&amp;gt;
    &amp;lt;!-- Add css file path in path attribute--&amp;gt;
    &amp;lt;add name=&quot;epi-cms.widgets.base&quot; path=&quot;/ClientResources/epi-cms.css&quot; resourceType=&quot;Style&quot; /&amp;gt;
  &amp;lt;/clientResources&amp;gt;
&amp;lt;/module&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;epi-cms.css&lt;/code&gt; is our css file, in which we are going to add our own css classes and properties. Copy paste below css into your css file.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code&gt;/*  Set property descriptions to be visible */
.Sleek .dijitTabPaneWrapper .epi-form-container__section__row label[title]:after,
.Sleek .epi-createContent .epi-form-container__section__row &amp;gt; label[title]:after {
    content: attr(title);
    display: block;
    font-size: 0.8em;
    font-style: normal;
    margin: 0.3em 0 0 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This css class is for the normal edit window and when we create a block in assets pan. The second class is for, when you create a block from content area (by clicking on &lt;strong&gt;Create a new block&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;And here we got our help text visible&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/10c2f82a4dc24d698f0726d15a0a7919.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let me know if you have any question or concern.&lt;/p&gt;
&lt;p&gt;Thanks &amp;amp; Regards&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;
&lt;p&gt;Happy coding :)&lt;/p&gt;</id><updated>2019-10-19T10:01:59.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Adding icons/thumbnails to content tree items in Episerver CMS</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2019/7/adding-iconsthumbnails-to-content-tree-items-in-episerver-cms/" /><id>&lt;p&gt;&lt;strong&gt;Updates [2021/01/04]:&lt;/strong&gt; Geta has updated the module to allow you to configure the tree icons. See below link&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Geta/Epi.FontThumbnail#tree-icon-feature&quot;&gt;https://github.com/Geta/Epi.FontThumbnail#tree-icon-feature&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You might not need this customization anymore.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;In continuous efforts to make the content editor&amp;rsquo;s life easy, we will learn to add icons/thumbnails, to the items in our content tree. So that we can easily identify the type of an item.&lt;/p&gt;
&lt;p&gt;In EpiServer, we have an attribute to add preview images to our custom types (Page/Block types). But that will only show while adding new item (as shown in below image). So, this is the default scenario. When we are using the Episerver&amp;rsquo;s &lt;code&gt;&lt;strong&gt;ImageUrl&lt;/strong&gt;&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/a95e610fb93248a4ba3ede424f7670ff.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can see the icons on the content types (on right side), but those are missing on the items in tree (in left panel).&lt;/p&gt;
&lt;p&gt;So, I would recommend Geta module for thumbnails (&lt;a href=&quot;https://github.com/Geta/Epi.FontThumbnail&quot;&gt;Geta.Epi.FontThumbnail&lt;/a&gt;). That is very handy and easy to use. And, this (below image) is when using the Geta module.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/4f677fdbe47d4f6c9c8c141954f7c7ea.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The same situation we face here, icons are missing on content tree items.&lt;/p&gt;
&lt;p&gt;Here, I will add some code along with Geta module to add icons to our items in tree.&lt;/p&gt;
&lt;p&gt;Here we go.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by adding an Initialization Module, named let&#39;s say &lt;code&gt;TreeIconsInitialization&lt;/code&gt; inherited from &lt;code&gt;IInitializableModule&lt;/code&gt; and dependencies as show below.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(InitializableModule))]
[ModuleDependency(typeof(FontThumbnailInitialization))]
public class TreeIconsInitialization : IInitializableModule
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following code inside the Initialize function&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void Initialize(InitializationEngine context)
{
    if (context == null)
        return;

    var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
    assemblies = assemblies.Where(x =&amp;gt; x.GetName().Name.StartsWith(&quot;AlloyDemo&quot;)).ToList();

    var types = assemblies.SelectMany(x =&amp;gt; x.GetTypes()).ToList();
    var typesWithIcon = types
        .Where(type =&amp;gt; type.IsDefined(typeof(ThumbnailIcon), false))
        .ToList();

    var iconClasses = typesWithIcon
        .Select(type =&amp;gt; new
        {
            Type = type,
            IconClass = this.ParseIconClass(type)
        })
        .ToDictionary(key =&amp;gt; key.Type, value =&amp;gt; value.IconClass);

    foreach (var uiDescriptor in context
        .Locate
        .Advanced
        .GetInstance&amp;lt;UIDescriptorRegistry&amp;gt;().UIDescriptors)
    {
        if (iconClasses.ContainsKey(uiDescriptor.ForType))
        {
            uiDescriptor.IconClass += iconClasses[uiDescriptor.ForType];
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In above code, we are scanning the project assemblies to retrieve the types that are defined with the &lt;code&gt;ThumbnailIcon&lt;/code&gt; attribute. Here, function &lt;code&gt;ParseIconClass()&lt;/code&gt; is responsible for parsing the icon class (font awesome icon class).&lt;/p&gt;
&lt;p&gt;Here is the code for parsing the icon class:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private string ParseIconClass(Type type)
{
    if (type == null)
        return null;

    var attribute = Attribute.GetCustomAttribute(type, typeof(ThumbnailIcon)) as ThumbnailIcon;

    string path = attribute?.Path;

    if (string.IsNullOrWhiteSpace(path))
        return null;

    Uri.TryCreate($&quot;http://localhost{path}&quot;, UriKind.RelativeOrAbsolute, out Uri uri);
    var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
    string characterCodeString = query[&quot;Character&quot;];

    if (!int.TryParse(characterCodeString, out var characterCode))
        return null;

    FontAwesome fa = (FontAwesome)characterCode;
    string name = System.Enum.GetName(typeof(FontAwesome), fa);
    string kebab = FromPascalCaseToKebabCase(name);
    string iconClass = $&quot;fa fa-{kebab}&quot;;

    return iconClass;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this function, we are getting the path value of the &lt;code&gt;ThumbnailIcon&lt;/code&gt; attribute, as this path contains the icon code in querystring param &amp;ldquo;&lt;strong&gt;Character&lt;/strong&gt;&amp;rdquo;. Then converting that code into the font awesome icon class.&lt;/p&gt;
&lt;p&gt;The method&amp;nbsp;&lt;code&gt;FromPascalCaseToKebabCase()&lt;/code&gt; is for converting the icon name into actual font-awesome icon class.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private static string FromPascalCaseToKebabCase(string input)
{
    if (string.IsNullOrWhiteSpace(input))
        return null;

    StringBuilder retVal = new StringBuilder(32);

    retVal.Append(char.ToLowerInvariant(input[0]));
    for (int i = 1; i &amp;lt; input.Length; i++)
    {
        if (char.IsLower(input[i]))
        {
            retVal.Append(input[i]);
        }
        else
        {
            retVal.Append(&quot;-&quot;);
            retVal.Append(char.ToLower(input[i], CultureInfo.InvariantCulture));
        }
    }

    return retVal.ToString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After all these, of course we will need to add reference to font awesome css file. To do so, add (if, not already added) &lt;code&gt;module.config&lt;/code&gt; and following code.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;module&amp;gt;
  &amp;lt;clientResources&amp;gt;
    &amp;lt;add name=&quot;epi-cms.widgets.base&quot; path=&quot;/Static/css/font-awesome.min.css&quot; resourceType=&quot;Style&quot; /&amp;gt;
  &amp;lt;/clientResources&amp;gt;
&amp;lt;/module&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, here we have our icons/thumnails to content tree items&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/680b512f433d4a73bb504522564f7984.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/860a80d6a0184296874811735a1008f9.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;RnD credit goes to our EMVP &lt;a href=&quot;/link/5341f632537c4b0ab6b8fb651bd310f8.aspx?userid=ee58d302-1eab-e411-9afb-0050568d2da8&quot;&gt;Drew Null&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If anyone need a hand on any issue or understanding any code line, do let me know.&lt;/p&gt;
&lt;p&gt;Happy Coding, thanks and regards,&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;</id><updated>2019-07-15T21:18:55.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Indexing Block&#39;s Content to make it searchable</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2019/4/indexing-blocks-content-to-make-it-searchable/" /><id>&lt;p&gt;By default, the content of a block (that is added to ContentArea on a page) is not indexed and therefore you can&amp;rsquo;t search for the content of that block instance in your site.&lt;/p&gt;
&lt;p&gt;So, the content of a page (including block&#39;s content) is indexed as normal text under &quot;SearchText$$string&quot;. To check if the content from Block is being indexed or not. Go to Episerver CMS, &lt;strong&gt;Find -&amp;gt; Overview -&amp;gt; Explore&lt;/strong&gt;, look for the page you want to confirm. Expand to see all its indexed properties. You can see here that, by default the content of the block is not being indexed.&lt;/p&gt;
&lt;p&gt;Let&#39;s talk about how to enable it. Here we go....&lt;/p&gt;
&lt;p&gt;There are several ways to do that, use any of them as per your requirement.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To allow content of all the instances of a block type to be indexed. Just add an attribute &lt;code&gt;[IndexInContentArea]&lt;/code&gt; on your block.&lt;code&gt;&lt;/code&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[IndexInContentAreas]
public class CopyBlock : SectionBlock&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you still not see the content is being indexed. Perform some changes to your page and you will see it worked. It&#39;s only to reindex the page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To exclude content of certain instances of the block, add a bool type property with exact name &lt;strong&gt;IndexInContentAreas&lt;/strong&gt; on your block type.&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public virtual bool IndexInContentAreas { get; set; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And set its value to &lt;strong&gt;True&lt;/strong&gt;, so that content of all instances will be indexed by default. To exclude content of any instance, uncheck the checkbox for property &lt;strong&gt;IndexInContentAreas &lt;/strong&gt;(for that instance only).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using search convention, changing the default behaviour of &lt;span&gt;&lt;strong&gt;IContentIndexerConventions.ShouldIndexInContentAreaConvention&lt;/strong&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;To index a particular block type, create a class and inherit it with interface &lt;/span&gt;&lt;strong&gt;IShouldIndexInContentAreaConvention&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class ShouldIndexInContentAreaConvention : IShouldIndexInContentAreaConvention
{
       public bool? ShouldIndexInContentArea(IContent content)
       {
       	return content is CopyBlock;
       }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;Now create an Initializable module&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Find.Cms.Module.IndexingModule))]
public class SearchConventionInitializationModule : IInitializableModule
{
        public void Initialize(InitializationEngine context)
        {
            ContentIndexer.Instance.Conventions.ShouldIndexInContentAreaConvention = new ShouldIndexInContentAreaConvention();
        }

        public void Uninitialize(InitializationEngine context)
        {
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition, to control the depth of ContentArea to be indexed, can be controlled by &lt;strong&gt;&lt;span class=&quot;classLib&quot;&gt;MaxDepthContentAreaConverter&lt;/span&gt;&amp;nbsp;&lt;/strong&gt;(by default all nested ContentArea are indexed)&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class SearchConventionInitializationModule : IInitializableModule
{
        private const int MaxDepth = 4;

        public void Initialize(InitializationEngine context)
        {
            ContentIndexer.Instance.Conventions.ShouldIndexInContentAreaConvention = new ShouldIndexInContentAreaConvention();
            SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;ContentArea&amp;gt;().ModifyContract(x =&amp;gt; x.Converter = new MaxDepthContentAreaConverter(MaxDepth));
        }

        public void Uninitialize(InitializationEngine context)
        {
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Document reference: &lt;a href=&quot;/link/117e06a6d16f44178bb023043938a37b.aspx&quot;&gt;https://world.episerver.com/documentation/developer-guides/find/Integration/episerver-cms-7-5-with-updates/Indexing-content-in-a-content-area/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Happy Coding :)&lt;/p&gt;</id><updated>2019-04-12T21:44:27.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Sorting dictionaries / translations with Episerver events</title><link href="https://world.optimizely.com/blogs/pjangid/dates/2019/2/sorting-dictionaries--translations-with-episerver-events/" /><id>&lt;p&gt;What we are trying to achieve here is, whenever I create a new entry in Translations Block (to create dictionary item), the latest entry goes at the end of the list. Which is not sorted in alphabetic order. Therefore, it is difficult to find any entry as the list size increases.&lt;/p&gt;
&lt;p&gt;So, we have a &lt;strong&gt;TranslationsBlock&lt;/strong&gt;, that contains &lt;strong&gt;Translations&lt;/strong&gt; property list type. This Translations contains three properties (&lt;strong&gt;Key&lt;/strong&gt;, &lt;strong&gt;Phrase&lt;/strong&gt; and &lt;strong&gt;Description&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;We will apply sorting in alphabetic ascending (a-&amp;gt;z) order. First on Key, then Phrase and then Description.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Here we go,&lt;/p&gt;
&lt;p&gt;Create an &lt;strong&gt;Initialization&lt;/strong&gt; module named EventInitialization (whatever name you prefer) and decorate it with attributes&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, don&amp;rsquo;t forget to inherit class with interface IInitializableModule.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class EventInitialization : IInitializableModule &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Implement the methods of this interface.&lt;/p&gt;
&lt;p&gt;Now, inside the &lt;strong&gt;Initialize&lt;/strong&gt; (implemented from interface) method bind your event.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var events = ServiceLocator.Current.GetInstance&amp;lt;IContentEvents&amp;gt;();
events.PublishedContent += this.OnSavedContent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I attached it to PublishContent event, that will be raised as the content is being published.&lt;/p&gt;
&lt;p&gt;Now, the finishing work goes inside the &lt;strong&gt;OnSavedContent&lt;/strong&gt; method.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private void OnSavedContent(object sender, ContentEventArgs contentEventArgs)
{
    if (contentEventArgs.Content is TranslationsBlock translationsBlock)
    {
        var repository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
        var block = (TranslationsBlock)translationsBlock.CreateWritableClone();
        block.Translations = block.Translations?
            .OrderBy(x =&amp;gt; x.Key)
            .ThenBy(x =&amp;gt; x.Phrase)
            .ThenBy(x =&amp;gt; x.Description)
            .ToList();
        repository.Save((IContent)block, SaveAction.Publish);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, first I checked for the content type is our required type TranslationsBlock or not. Then creating a writable clone to update it. Now apply your operations and save it with the help of Save method of ContentRepository.&lt;/p&gt;
&lt;p&gt;Happy Coding :)&lt;/p&gt;
&lt;p&gt;Thanks,&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;</id><updated>2019-02-27T03:47:22.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>