<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://sitecore.merkle.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sitecore.merkle.com/" rel="alternate" type="text/html" /><updated>2026-05-21T08:24:14+00:00</updated><id>https://sitecore.merkle.com/feed.xml</id><title type="html">Sitecore Blog. Merkle DACH.</title><subtitle>Experience and knowledge about Sitecore</subtitle><author><name>Merkle DACH</name></author><entry><title type="html">Preview scheduled content changes with SitecoreAI Marketplace App</title><link href="https://sitecore.merkle.com/marketplace-app-preview-scheduled-content/" rel="alternate" type="text/html" title="Preview scheduled content changes with SitecoreAI Marketplace App" /><published>2026-05-21T07:00:00+00:00</published><updated>2026-05-21T07:00:00+00:00</updated><id>https://sitecore.merkle.com/marketplace-app-preview-scheduled-content</id><content type="html" xml:base="https://sitecore.merkle.com/marketplace-app-preview-scheduled-content/"><![CDATA[<p><img src="../files/2026/05/21/scheduled-preview-hero-image.png" alt="Comic-style illustration of a person relaxing on a beach, holding a drink and checking a smartphone displaying “CAMPAIGN LIVE,” symbolizing a scheduled campaign that successfully went live without issues." /></p>

<p><em>Relaxing while a campaign goes live — thanks to previewing scheduled content in advance, authors can confidently verify that everything will appear exactly as intended when it matters most.</em></p>

<h2 id="overview">Overview</h2>

<p>In this blog post I introduce a marketplace app I have recently been working on. The goal of this app is to give content authors the ability to do quality assurance of scheduled content, for example to verify that a banner scheduled to go live at midnight actually appears correctly before it publishes.</p>

<p>In the past, this was easily possible within the Experience Editor by changing the Preview Date and Time.</p>

<p><img src="../files/2026/05/21/experience-editor-preview-date.png" alt="Datepicker in Sitecore Experience Editor" /></p>

<p>Unfortunately this functionality is no longer available in SitecoreAI Pages Editor and content authors are left to guess whether their scheduled content will display correctly.</p>

<p>I took this lack in functionality as an opportunity to test out the SitecoreAI Marketplace and built an app, that brings a scheduled preview to the SitecoreAI Pages Editor.</p>

<h2 id="how-to-use-the-app">How to use the app</h2>

<p>After installing the marketplace app in your SitecoreAI environment, it is available on the top right of your SitecoreAI Pages Editor. Simply click the puzzle piece icon and select the app. Now a new panel should open on the left side of your editor.</p>

<p>The panel includes:</p>

<ul>
  <li>A date and time picker where you can select a time to simulate in the preview window</li>
  <li>A preview frame, that displays your page with, as it will look at the defined time, with respect to the publishing restrictions</li>
  <li>A tab, where you can directly change the publishing restrictions of content items related to the page</li>
  <li>A tab with technical details, which gives you detailed feedback on which versions are currently visible in the preview frame</li>
</ul>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
  <iframe src="https://www.youtube.com/embed/o-xH6IpOr7Q" frameborder="0" allowfullscreen="" style="position: absolute; top:0; left:0; width:100%; height:100%;" referrerpolicy="strict-origin-when-cross-origin">
  </iframe>
</div>

<figcaption>This video shows the capabilities of the Preview Marketplace App in action.</figcaption>

<h2 id="how-to-configure-the-app">How to configure the app</h2>

<p>You can find the code repository here: https://github.com/simonhck/timed-preview-marketplace-app</p>

<p>The app can be hosted on any platform that can run Next.js apps. During implementation I tested on Netlify, so the repo already contains the Netlify configuration files.</p>

<p>When you configure the app in ‘App Studio’, make sure to set the Route URL to / and grant API access to ‘SitecoreAI APIs’.</p>

<p><img src="../files/2026/05/21/marketplace-app-configuration.png" alt="Configuration of the marketplace app in App Studio" /></p>

<p>In order to connect the preview to your rendering host, you need to add two configurations - editing secret and rendering endpoint.</p>

<p>This can be done either directly in the env variables of your marketplace app with keys:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JSS_EDITING_SECRET
RENDERING_ENDPOINT
</code></pre></div></div>

<p>Or if you intend to use the app across multiple environments, a config item can be added to the settings folder of your site with values EditingSecret and RenderingEndpoint.</p>

<p><img src="../files/2026/05/21/preview-rendering-host-settings.png" alt="Configuration item for editing secret and rendering endpoints in Sitecore" /></p>

<h2 id="technical-details">Technical details</h2>

<p>The app relies on some plugin code that you have to integrate into your head app. In the repository, you find it under <strong>/target-site-plugin</strong>.</p>

<p>Under the hood, the preview goes through several steps:</p>

<ol>
  <li>By comparing item versions and their configured publishing restrictions, the app determines which content will be overridden in the preview</li>
  <li>These overrides are stored in a cookie mapped via the item ID</li>
  <li>The head app is called within the preview frame</li>
  <li>Within the head app, the plugin code retrieves the stored overrides and applies them to the layoutData before rendering the page</li>
</ol>

<p>After these steps, the page is rendered within the preview frame with all overrides applied and all content is shown according to the configured publishing restrictions.</p>

<h3 id="page-router-vs-app-router">Page router vs App router</h3>

<p>This app is mainly tested and built on App router and has been very reliable during my tests.</p>

<p>There is a <a href="https://github.com/simonhck/timed-preview-marketplace-app/tree/experimental/page-router-support">separate branch</a> in the repo that supports Page router, which I created for a client. However, this setup has been more shaky in my tests and you might need to make some adaptations based on JSS/Content SDK version, you are using on your project.</p>

<h2 id="next-steps--future-plans">Next steps / future plans</h2>

<h3 id="editing-within-the-app">Editing within the app</h3>

<p>To allow the author to make some immediate changes while reviewing time restricted content, I would like to add some kind of editing capability to the app.</p>

<h3 id="improved-handling-of-app-configurations">Improved handling of app configurations</h3>

<p>Sitecore has hinted at an improvement for marketplace apps, that they are working on to give administrators the ability to maintain app configurations directly in the app. This would be the ideal place to put the configurations for the rendering host, which is currently still in env variables or as item in SitecoreAI.</p>

<h3 id="proper-passing-of-preview-parameters">Proper passing of preview parameters</h3>

<p>Sitecore has hinted at improved preview capabilities for SitecoreAI, where users would be able to pass certain parameters directly to the preview via URL, including a preview time. Once available, this would make the workaround logic of this app obsolete.</p>

<p>There is no confirmed timeline for this change yet, and it could still be a while before it lands. In the meantime, this app provides a reliable solution that content authors can use today. Even once the native preview parameters arrive, the app will continue to serve as a polished interface for interacting with the preview, just without the need for the override logic under the hood.</p>

<h2 id="feedback-welcome">Feedback welcome</h2>

<p>I’d be happy to hear from anyone who integrated this app into their project or just played around with it.</p>

<p>If you have any feedback or questions, feel free to contact me.</p>]]></content><author><name>shauck</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="XM Cloud" /><category term="SitecoreAI" /><category term="Content" /><category term="Publishing" /><category term="Marketplace" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Solr 9.8.1 Upgrade – Lessons Learned</title><link href="https://sitecore.merkle.com/Solr-981-upgrade-lessons-learned/" rel="alternate" type="text/html" title="Solr 9.8.1 Upgrade – Lessons Learned" /><published>2026-01-08T09:00:00+00:00</published><updated>2026-01-08T09:00:00+00:00</updated><id>https://sitecore.merkle.com/Solr-981-upgrade-lessons-learned</id><content type="html" xml:base="https://sitecore.merkle.com/Solr-981-upgrade-lessons-learned/"><![CDATA[<p><img src="../files/2026/01/wesley-tingey-snNHKZ-mGfE-unsplash.jpg" alt="alt text" title="four stacks of more or less sorted documents with sticky notes" /></p>

<p>Foto <a href="https://unsplash.com/de/@wesleyphotography">Wesley Tingey</a> on <a href="https://unsplash.com/de/fotos/stapel-bucher-auf-dem-tisch-snNHKZ-mGfE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>

<p>We recently upgraded our XP solution from <strong>10.3.1</strong> to <strong>10.3.3</strong> to support the <strong>Solr 9.8.1</strong> upgrade. <br />
Version 9.8.1 was released on <strong>March 6, 2025</strong>. The previous version has reached <strong>EOL</strong> and is no longer listed on <em>apache.org</em>.</p>

<h2 id="our-initial-plan">Our initial plan</h2>

<p>We assumed the upgrade would be straightforward:</p>

<ol>
  <li>Download Solr</li>
  <li>Convert the .tgz to a .zip (to install it with the SIF)</li>
  <li>Install on a new port</li>
  <li>Create cores</li>
  <li>Update Solr connectionstring in Sitecore</li>
  <li>Deploy schema</li>
  <li>Recreate index</li>
  <li>Done</li>
</ol>

<h2 id="the-issue">The issue</h2>

<p>When deploying the schema for <strong>master_index</strong>, we encountered this error:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;lst</span> <span class="na">name=</span><span class="s">"responseHeader"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;int</span> <span class="na">name=</span><span class="s">"status"</span><span class="nt">&gt;</span>400<span class="nt">&lt;/int&gt;</span>
  <span class="nt">&lt;int</span> <span class="na">name=</span><span class="s">"QTime"</span><span class="nt">&gt;</span>0<span class="nt">&lt;/int&gt;</span>
<span class="nt">&lt;/lst&gt;</span>
<span class="nt">&lt;lst</span> <span class="na">name=</span><span class="s">"error"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;lst</span> <span class="na">name=</span><span class="s">"metadata"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;str</span> <span class="na">name=</span><span class="s">"error-class"</span><span class="nt">&gt;</span>org.apache.solr.common.SolrException<span class="nt">&lt;/str&gt;</span>
    <span class="nt">&lt;str</span> <span class="na">name=</span><span class="s">"root-error-class"</span><span class="nt">&gt;</span>org.apache.solr.common.SolrException<span class="nt">&lt;/str&gt;</span>
  <span class="nt">&lt;/lst&gt;</span>
  <span class="nt">&lt;str</span> <span class="na">name=</span><span class="s">"msg"</span><span class="nt">&gt;</span>Current core has 2048 fields, exceeding the max-fields limit of 2000.  <span class="nt">&lt;/str&gt;</span>
  <span class="nt">&lt;int</span> <span class="na">name=</span><span class="s">"code"</span><span class="nt">&gt;</span>400<span class="nt">&lt;/int&gt;</span>
<span class="nt">&lt;/lst&gt;</span>
</code></pre></div></div>

<p>This was unexpected — the upgrade seemed simple at first.</p>

<h2 id="backgroud">Backgroud</h2>

<p>Starting with Solr <strong>version 9</strong>, they introduced a limit on the number of fields per core (<a href="https://solr.apache.org/docs/9_8_1/core/org/apache/solr/update/processor/NumFieldLimitingUpdateRequestProcessorFactory.html">see documentation</a>).</p>

<p>This setting is defined in the <code class="language-plaintext highlighter-rouge">solrconfig.xml</code> of each core: <code class="language-plaintext highlighter-rouge">{SolrInstallPath}\server\solr\{prefix}_master_index\conf</code></p>

<h2 id="solution">Solution</h2>

<p>Update the configuration to a suitable value, for example:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nt">&lt;updateProcessor</span> <span class="na">class=</span><span class="s">"solr.NumFieldLimitingUpdateRequestProcessorFactory"</span> <span class="na">name=</span><span class="s">"max-fields"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;int</span> <span class="na">name=</span><span class="s">"maxFields"</span><span class="nt">&gt;</span>3000<span class="nt">&lt;/int&gt;</span>
    <span class="nt">&lt;bool</span> <span class="na">name=</span><span class="s">"warnOnly"</span><span class="nt">&gt;</span>true<span class="nt">&lt;/bool&gt;</span>
  <span class="nt">&lt;/updateProcessor&gt;</span>
</code></pre></div></div>

<ul>
  <li>Restart Solr</li>
  <li>Deploy the schema again</li>
  <li>If the error persists, increase maxFields and repeat the steps</li>
</ul>

<p><strong>Tip:</strong> Start with a high value (e.g., 50,000) and adjust later based on actual requirements.</p>

<h2 id="how-to-determine-the-required-value">How to determine the required value</h2>

<p>Solr does not provide this information directly. Here’s how we found it:</p>

<ol>
  <li>Open the Solr admin UI</li>
  <li>Navigate to the schema page for the core (<code class="language-plaintext highlighter-rouge">/solr/#/{prefix}_master_index/schema</code>)</li>
  <li>Inspect the page source and search for <code class="language-plaintext highlighter-rouge">data-option-array-index</code></li>
  <li>Use the highest index value as a reference</li>
</ol>

<p>If you know a better or simpler method, please share!</p>

<h2 id="sif-integration">SIF integration</h2>

<p>To avoid manual changes, we extended the SIF configuration:</p>

<ul>
  <li>Added a new variable: <code class="language-plaintext highlighter-rouge">"Solr.MaxFieldsValue.BigValue": 15000</code></li>
  <li>Updated the logic in <code class="language-plaintext highlighter-rouge">UpdateMaxFieldsValue</code> for relevant indexes (master, web, and inherited ones)</li>
</ul>

<h2 id="helpful-links">Helpful Links</h2>

<ul>
  <li><a href="https://archive.apache.org/dist/solr/solr/9.8.1/">Solr 9.8.1 Release</a></li>
  <li><a href="https://developers.sitecore.com/downloads/Sitecore_Experience_Platform/103/Sitecore_Experience_Platform_103_Update3">Sitecore Experience Platform 10.3 Update-3</a></li>
  <li><a href="https://developers.sitecore.com/downloads/Sitecore_Experience_Platform/104/Sitecore_Experience_Platform_104_Update1">Sitecore Experience Platform 10.4 Update-1</a></li>
</ul>]]></content><author><name>hlueneburg</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="Sitecore 10.3.3" /><category term="Sitecore 10.4.1" /><category term="Sitecore XP" /><category term="Solr" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">How to implement scheduled publishing in XM Cloud</title><link href="https://sitecore.merkle.com/xm-cloud-scheduled-publishing/" rel="alternate" type="text/html" title="How to implement scheduled publishing in XM Cloud" /><published>2025-11-28T07:00:00+00:00</published><updated>2025-11-28T07:00:00+00:00</updated><id>https://sitecore.merkle.com/xm-cloud-scheduled-publishing</id><content type="html" xml:base="https://sitecore.merkle.com/xm-cloud-scheduled-publishing/"><![CDATA[<p><img src="../files/2025/11/12/scheduled-publishing.png" alt="Alarm clock ringing on the left and a red mailbox on the right overflowing with documents" /></p>

<h2 id="implementing-scheduled-content-publishing-in-sitecoreai">Implementing scheduled content publishing in SitecoreAI</h2>

<p>After migrating from Sitecore XP to SitecoreAI/XM Cloud, one of my clients was facing the challenge, that the scheduled publishing feature that they previously used is no longer available due to SitecoreAIs restrictions to deploy custom code to the back end.</p>

<p>Sitecore XP as well as SitecoreAI already support <a href="https://doc.sitecore.com/sai/en/users/sitecoreai/schedule-availability-for-a-content-item.html#make-a-version-unavailable-for-publication">restricting content publication to certain time windows</a> but this is only to <strong>mark content as being publishable</strong>. When the time comes and the item can be published, it still requires someone actively hitting the publish button for that item.</p>

<p>Now I was tasked to implement a scheduled publishing, which should be as close as possible to the old solution to make it as easy as possible for content editors to transition. Requirements were as follows:</p>
<ul>
  <li>Content authors can schedule content publications by configuring the out of the box publishing restrictions.</li>
  <li>It was not required, that scheduled content publishing is exact to the second and some few minutes delay was acceptable.</li>
  <li>At this point scheduled de-publishing was not a requirement</li>
</ul>

<h2 id="solution">Solution</h2>

<p>The solution is based on two <a href="https://doc.sitecorepowershell.com/">Sitecore Powershell Extensions</a> scripts that run on a schedule. One script fills up a publishing queue with information which item is due at which time. The other script simply runs through the queue and triggers content publishing based on the item configurations in that queue.</p>

<h3 id="filling-up-the-queue">Filling up the queue</h3>

<p>For the schedule items we create a new template ‘Publishing schedule’ like this:</p>

<p><img src="../files/2025/11/12/publishing-schedule-template.png" alt="Configuration of Publishing schedule template with three fields: Target item as Single-Line Text, Publishing Time as Datetime and Status as Single-Line Text" /></p>

<p>The script to fill the publishing queue is called “Schedule item publications” and looks like this:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$scheduleFolderPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'/sitecore/system/settings/Feature/Scheduled Publishing/Publishing schedules/'</span><span class="w">
</span><span class="nv">$scheduleItemTemplate</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'/sitecore/templates/Feature/Scheduled Publisher/Publishing schedule'</span><span class="w">
</span><span class="nv">$languageVersion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"de-DE"</span><span class="w">
</span><span class="nv">$debug</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">

</span><span class="kr">function</span><span class="w"> </span><span class="nf">Schedule-Future-Publishing</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kr">param</span><span class="w"> </span><span class="p">(</span><span class="w">
        </span><span class="p">[</span><span class="n">string</span><span class="p">]</span><span class="nv">$itemId</span><span class="w">
    </span><span class="p">)</span><span class="w">
    
    </span><span class="nv">$items</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="s2">"master:"</span><span class="w"> </span><span class="nt">-ID</span><span class="w"> </span><span class="nv">$itemId</span><span class="w"> </span><span class="nt">-Language</span><span class="w"> </span><span class="nv">$languageVersion</span><span class="w"> </span><span class="nt">-Version</span><span class="w"> </span><span class="o">*</span><span class="w">
    
    </span><span class="kr">if</span><span class="p">(</span><span class="nv">$debug</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$items</span><span class="o">.</span><span class="nf">GetType</span><span class="p">()</span><span class="o">.</span><span class="nf">ToString</span><span class="p">()</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'Sitecore.Data.Items.Item'</span><span class="p">){</span><span class="w">
            </span><span class="nv">$path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$items</span><span class="o">.</span><span class="nf">Paths</span><span class="o">.</span><span class="nf">FullPath</span><span class="w">
        </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$items</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="nf">Paths</span><span class="o">.</span><span class="nf">FullPath</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="n">Write-Host</span><span class="w"> </span><span class="s1">'Evaluating '</span><span class="w"> </span><span class="nv">$path</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="c"># Get current UTC time</span><span class="w">
    </span><span class="nv">$utcNow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">UtcNow.ToString</span><span class="p">(</span><span class="s1">'yyyyMMddTHHmmssZ'</span><span class="p">)</span><span class="w">
    
    </span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$item</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$items</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="c"># Here we compare the publishing restrictions with the current date and time</span><span class="w">
        </span><span class="c"># If the item has a publishing date, that lies in the future, we create a new item in the publishing queue</span><span class="w">
        </span><span class="nv">$validFrom</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ItemField</span><span class="w"> </span><span class="nt">-IncludeStandardFields</span><span class="w"> </span><span class="nt">-Item</span><span class="w"> </span><span class="nv">$item</span><span class="w"> </span><span class="nt">-ReturnType</span><span class="w"> </span><span class="s1">'field'</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="s2">"__Valid from"</span><span class="w">
        
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$debug</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Host</span><span class="w"> </span><span class="s1">'Comparing: '</span><span class="w"> </span><span class="nv">$validFrom</span><span class="o">.</span><span class="nf">Value</span><span class="w"> </span><span class="s1">' &gt; '</span><span class="w"> </span><span class="nv">$utcNow</span><span class="w"> </span><span class="s1">' is: '</span><span class="w"> </span><span class="p">(</span><span class="nv">$validFrom</span><span class="o">.</span><span class="nf">Value</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$utcNow</span><span class="p">)</span><span class="w">
        </span><span class="p">}</span><span class="w">
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$validFrom</span><span class="o">.</span><span class="nf">Value</span><span class="w"> </span><span class="o">-ge</span><span class="w"> </span><span class="nv">$utcNow</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nv">$targetPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$scheduleFolderPath</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$validFrom</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">'_'</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$item</span><span class="o">.</span><span class="nf">ID</span><span class="o">.</span><span class="nf">ToShortID</span><span class="p">()</span><span class="w">
            </span><span class="nv">$createdItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$targetPath</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nv">$scheduleItemTemplate</span><span class="w">
            
            </span><span class="nv">$createdItem</span><span class="o">.</span><span class="nf">Editing</span><span class="o">.</span><span class="nf">BeginEdit</span><span class="p">()</span><span class="w">
            </span><span class="nv">$createdItem</span><span class="p">[</span><span class="s2">"Target Item"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$item</span><span class="o">.</span><span class="nf">ID</span><span class="w">
            </span><span class="nv">$createdItem</span><span class="p">[</span><span class="s2">"Publishing Time"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$validFrom</span><span class="o">.</span><span class="nf">Value</span><span class="w">
            </span><span class="nv">$createdItem</span><span class="p">[</span><span class="s2">"Status"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Pending'</span><span class="w">
            </span><span class="nv">$createdItem</span><span class="o">.</span><span class="nf">Editing</span><span class="o">.</span><span class="nf">EndEdit</span><span class="p">()</span><span class="w">
            
            </span><span class="kr">if</span><span class="p">(</span><span class="nv">$debug</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="n">Write-Host</span><span class="w"> </span><span class="s1">'Added '</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nv">$targetPath</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># Here we can configure all directories, in which we search for scheudled items</span><span class="w">
</span><span class="nv">$contentDirectories</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">@(</span><span class="w">
    </span><span class="s1">'/sitecore/content/MyProject/Home'</span><span class="p">,</span><span class="w">
    </span><span class="s1">'/sitecore/content/MyProject/Data'</span><span class="w">
</span><span class="p">)</span><span class="w">

</span><span class="c"># Clear the folder first</span><span class="w">
</span><span class="c"># The queue is rebuilt on every run so items, that are already published are cleared from the queue</span><span class="w">
</span><span class="nv">$childItems</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$scheduleFolderPath</span><span class="w">

</span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$childItem</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$childItems</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">Remove-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$childItem</span><span class="o">.</span><span class="nf">Paths</span><span class="o">.</span><span class="nf">FullPath</span><span class="w"> </span><span class="nt">-Force</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="c"># go through all subitems and their version and check for publishing restrictions</span><span class="w">
</span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$contentDirectory</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$contentDirectories</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$contentDirectoryItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Item</span><span class="w"> </span><span class="nt">-path</span><span class="w"> </span><span class="nv">$contentDirectory</span><span class="w"> </span><span class="nt">-Language</span><span class="w"> </span><span class="nv">$languageVersion</span><span class="w">

    </span><span class="n">Schedule-Future-Publishing</span><span class="w"> </span><span class="nt">-itemId</span><span class="w"> </span><span class="nv">$contentDirectoryItem</span><span class="o">.</span><span class="nf">ID</span><span class="w">
    
    </span><span class="nv">$contentItems</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$contentDirectory</span><span class="w"> </span><span class="nt">-Recurse</span><span class="w">
    
    </span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$contentItem</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$contentItems</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">Schedule-Future-Publishing</span><span class="w"> </span><span class="nt">-itemId</span><span class="w"> </span><span class="nv">$contentItem</span><span class="o">.</span><span class="nf">ID</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span></code></pre></div></div>

<p>This script goes through a list of items, that we can configure and their child items and compares the field __Vaild from to the current date and time. If the field value is greater than the current datetime (i.e. the item is scheduled for a future time) we create a new item based on our previously created item an put it into a folder, that serves as our publishing queue.</p>

<p>Via a scheduled task, we configure this script to run once every 24 hours. So, the publishing queue is filled once a day.</p>

<p><img src="../files/2025/11/12/task-schedule-future-publishing.png" alt="Configuration of a scheduled task in Sitecore to run the 'Schedule item publications' script once every 24 hours" /></p>

<h3 id="running-scheduled-publishes">Running scheduled publishes</h3>

<p>The second script we build will go through the publishing queue and publish items as soon as their target date is reached. Items, that get published also have their Status set from ‘Pending’ to ‘Published’ so they are only considered once for publishing and when the queue is refilled the next time, it will not reappear, because the date lies in the past then.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$scheduleFolderPath</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'/sitecore/system/settings/Feature/Scheduled Publishing/Publishing schedules/'</span><span class="w">
</span><span class="nv">$languageVersion</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"de-DE"</span><span class="w">
</span><span class="nv">$debug</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">$false</span><span class="w">

</span><span class="nv">$db</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Database</span><span class="w"> </span><span class="s2">"experienceedge"</span><span class="w">
</span><span class="nv">$lang</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">Sitecore.Globalization.Language</span><span class="p">]::</span><span class="n">Parse</span><span class="p">(</span><span class="nv">$languageVersion</span><span class="p">)</span><span class="w">

</span><span class="nv">$publishingSchedules</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-ChildItem</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$scheduleFolderPath</span><span class="w">

</span><span class="c"># Get current UTC time</span><span class="w">
</span><span class="nv">$utcNow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">DateTime</span><span class="p">]::</span><span class="n">UtcNow.ToString</span><span class="p">(</span><span class="s1">'yyyyMMddTHHmmssZ'</span><span class="p">)</span><span class="w">

</span><span class="kr">foreach</span><span class="p">(</span><span class="nv">$publishingSchedule</span><span class="w"> </span><span class="kr">in</span><span class="w"> </span><span class="nv">$publishingSchedules</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nv">$validFrom</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$publishingSchedule</span><span class="p">[</span><span class="s2">"Publishing Time"</span><span class="p">]</span><span class="w">
    
    </span><span class="c"># only consider items with status Pending and ignore already published items</span><span class="w">
    </span><span class="kr">if</span><span class="p">((</span><span class="nv">$validFrom</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$utcNow</span><span class="p">)</span><span class="w"> </span><span class="o">-and</span><span class="w"> </span><span class="nv">$publishingSchedule</span><span class="p">[</span><span class="s2">"Status"</span><span class="p">]</span><span class="w"> </span><span class="o">-eq</span><span class="w"> </span><span class="s1">'Pending'</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nv">$targetItem</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Get-Item</span><span class="w"> </span><span class="nv">$publishingSchedule</span><span class="p">[</span><span class="s2">"Target Item"</span><span class="p">]</span><span class="w"> </span><span class="nt">-Language</span><span class="w"> </span><span class="nv">$languageVersion</span><span class="w">
        
        </span><span class="c"># Smart publish, with related items, no subitems</span><span class="w">
        </span><span class="p">[</span><span class="n">Sitecore.Publishing.PublishManager</span><span class="p">]::</span><span class="n">PublishItem</span><span class="p">(</span><span class="nv">$targetItem</span><span class="p">,</span><span class="w"> </span><span class="nv">$db</span><span class="p">,</span><span class="w"> </span><span class="nv">$lang</span><span class="p">,</span><span class="w"> </span><span class="bp">$false</span><span class="p">,</span><span class="w"> </span><span class="bp">$true</span><span class="p">,</span><span class="w"> </span><span class="bp">$true</span><span class="p">)</span><span class="w">
        
        </span><span class="nv">$publishingSchedule</span><span class="o">.</span><span class="nf">Editing</span><span class="o">.</span><span class="nf">BeginEdit</span><span class="p">()</span><span class="w">
        </span><span class="nv">$publishingSchedule</span><span class="p">[</span><span class="s2">"Status"</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">'Published'</span><span class="w">
        </span><span class="nv">$publishingSchedule</span><span class="o">.</span><span class="nf">Editing</span><span class="o">.</span><span class="nf">EndEdit</span><span class="p">()</span><span class="w">
        
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$debug</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Host</span><span class="w"> </span><span class="s1">'Published: '</span><span class="w"> </span><span class="nv">$publishingSchedule</span><span class="o">.</span><span class="nf">Paths</span><span class="o">.</span><span class="nf">FullPath</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kr">if</span><span class="p">(</span><span class="nv">$debug</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="n">Write-Host</span><span class="w"> </span><span class="s1">'Not due: '</span><span class="w"> </span><span class="nv">$publishingSchedule</span><span class="o">.</span><span class="nf">Paths</span><span class="o">.</span><span class="nf">FullPath</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Again, we create a scheduled task to run this script once every 10 minutes. As mentioned above it was not necessary to have the item go live at an exact time. This way, we have a maximum delay of 10 minutes to the configured time.</p>

<p><img src="../files/2025/11/12/task-run-scheduled-publishing.png" alt="Configuration of a scheduled task in Sitecore to run the 'Run scheduled publishing' script once every 10 minutes" /></p>

<p>These two scripts fulfilled all requirements as agreed on with the customer and increased the editors’ day-to-day working experience by a far, because they could simply configure publishing restrictions for their items and be assured, that their content goes live without additional steps.</p>

<h2 id="considerations">Considerations</h2>

<p>This may not be the most elegant solution, and we considered a more efficient solution including webhooks and only triggering the schedule job, when an item is saved, so it is not necessary to evaluate all content repeatedly.</p>

<p>But at this point, we decided to not introduce an additional external service that would be needed to handle the webhook and performance also looked fine with the current amount of content. The script to schedule the item publications ran for ~3 minutes with a volume of 30.000 items, which were evaluated.</p>

<p>Also, I mentioned in the beginning, that de-publishing is not considered at this point, because the customer is not utilizing the “Valid to” configuration now. The script could be easily extended to cover that requirement as well, but for the sake of simplicity I left it out in this blog post.</p>

<p>I hope this will serve helpful to someone else looking for this common requirement and happy to hear about your solutions, how you solved this problem.</p>

<p>Head image was created via Copilot</p>]]></content><author><name>shauck</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="XM Cloud" /><category term="SitecoreAI" /><category term="Content" /><category term="Publishing" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Sitecore slow indexing</title><link href="https://sitecore.merkle.com/slow-indexing/" rel="alternate" type="text/html" title="Sitecore slow indexing" /><published>2025-10-01T07:00:00+00:00</published><updated>2025-10-01T07:00:00+00:00</updated><id>https://sitecore.merkle.com/slow-indexing</id><content type="html" xml:base="https://sitecore.merkle.com/slow-indexing/"><![CDATA[<p>Sitecore Slow Indexing Speed
Recently, I needed to reinstall a Sitecore project solution I had worked on in the past. The custom setup and application were quite complex, involving many steps beyond simply checking out and building the solution.</p>

<p>Finally, I reached my Sitecore localhost test URL in the browser, expecting the site to appear. Unfortunately, it didn’t work.</p>

<p>I recalled that the site was heavily dependent on Solr indexes—particularly two custom indexes in addition to the sitecore_master_index and sitecore_web_index. So I began investigating the indexes by:</p>

<ul>
  <li>Repopulating the schema to Solr (this worked well)</li>
  <li>Starting the index rebuilds (surprisingly slow)</li>
</ul>

<p>I remembered that the solution’s indexing had been extended significantly with various customizations. I wasn’t sure if I was misremembering the indexing performance or if it had always been this slow.</p>

<p>I started disabling customizations such as computed fields and custom field type registrations. The result: no improvement. I also debugged and investigated further but didn’t uncover anything new.</p>

<p>Why were Solr indexes—configured identically and running on the same Solr instance—indexing at different speeds?</p>

<p>After mentally reviewing all the involved resources and categorizing them as:</p>

<ul>
  <li>Exactly the same</li>
  <li>Theoretically the same with minor differences (e.g., different names)</li>
  <li>Truly different</li>
</ul>

<p>I concluded that I needed to check the databases. This was especially relevant because the two problematic indexes were both tied to the web database.</p>

<p>As a Sitecore developer, you naturally consider the differences between the master and web databases from a content perspective. Typically, the web database contains only published data, making it smaller and faster to index.</p>

<p>So I checked something I rarely examine as a Sitecore engineer: the <strong>SQL database table indexes</strong>.</p>

<p>When I looked into the master database’s Items and Fields tables, the index sizes appeared normal. However, the corresponding tables in the web database were empty or nearly empty—clearly incorrect.</p>

<p>**I proceeded to rebuild the SQL table indexes: Right-click <your table=""> → Indexes → Right-click “Rebuild All”**</your></p>

<p>Seconds later, the index sizes looked promising.</p>

<p>A Sitecore index rebuild confirmed that the indexing process was now hundreds of times faster. Instead of the status counter increasing one by one every second, it resumed its natural behavior—jumping in steps of 100+.</p>

<p>Although I’m not sure how the database indexes were lost (perhaps due to a restore or other process), I’m glad I identified the issue in an area I usually don’t need to worry about as a Sitecore engineer.</p>

<p><strong>So, if you encounter similar behavior, be sure to check your SQL table indexes!</strong></p>]]></content><author><name>aklein</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="Solr" /><category term="Indexing" /><summary type="html"><![CDATA[Sitecore Slow Indexing Speed Recently, I needed to reinstall a Sitecore project solution I had worked on in the past. The custom setup and application were quite complex, involving many steps beyond simply checking out and building the solution.]]></summary></entry><entry><title type="html">SUGCON Europe 2025 - Recap</title><link href="https://sitecore.merkle.com/sugcon-recap/" rel="alternate" type="text/html" title="SUGCON Europe 2025 - Recap" /><published>2025-05-05T07:00:00+00:00</published><updated>2025-05-05T07:00:00+00:00</updated><id>https://sitecore.merkle.com/sugcon-recap</id><content type="html" xml:base="https://sitecore.merkle.com/sugcon-recap/"><![CDATA[<p><img src="../files/2025/05/05/a-room-with-a-zoo.jpg" alt="SUGCON venue - a room with a zoo" title="SUGCON venue - a room with a zoo" /></p>

<p>On April 3-4, the 10th Sitecore User Group Conference took place in Antwerp, Belgium. For one and a half days, community members from all over the world could meet and exchange ideas at the incredible venue “A Room with a Zoo”.</p>

<p>I had already attended a Symposium in the past as well as multiple <a href="https://www.sitecore-usergroup.de/">Sitecore User Group Germany</a> meetups. But this was the first time I could visit SUGCON, and I was excited to learn about the latest product developments, get inspiration from other Sitecore developers, and meet new and old friends.</p>

<p><img src="../files/2025/05/05/simon-at-sugcon.jpg" alt="Simon at SUGCON" title="Simon at SUGCON" /></p>

<p>As you would expect, the dominating topics were AI and especially Sitecore Stream, and XM Cloud. But I enjoyed seeing innovative solutions from other community members and hearing about new and planned features from the Sitecore product teams.</p>

<p>In this post, I will share a brief recap of my highlight sessions and my personal opinion on some of these topics.</p>

<h2 id="day-1">Day 1</h2>

<p>Since I was traveling that day, I could not attend the two workshops on migrating XM/XP to XM Cloud best practices and Content Hub optimizations. The event started with the opening keynote.</p>

<h3 id="keynote-powering-the-future-of-sitecore">Keynote: Powering the Future of Sitecore</h3>

<p>The event was kicked off by Sitecore CEO Dave O’Flanagan. He shared with us the future roadmap of Sitecore and the key role that AI and Sitecore Stream will play in it, even for the “old” XM/XP solutions that Sitecore is still committed to further maintain and innovate, and which can also utilize Sitecore Stream.</p>

<p>We got a glimpse of the new XM Cloud App Marketplace, which will allow developers to deploy custom plugins to XM Cloud and share them with others via the Marketplace.</p>

<p>In a short demo, we also saw the “CI Hub” connector, which can be used to easily integrate Content Hub with your typical Office or Creative Software like Photoshop or PowerPoint.</p>

<h3 id="events-webhooks--the-authoring-api">Events, Webhooks, &amp; the Authoring API</h3>

<p>Jason Wilkerson demonstrated in this session how webhooks and events can be utilized to run custom logic on items. He showcased a custom-built app that can create new content within XM Cloud only by API and without opening the Content Editor once, which could, in a further step, also be used for automation.</p>

<p>It was good to see how customizations in the SaaS world can still work and what tools developers can use, especially when planning to move from XM/XP to XM Cloud, where we see a huge shift in how backend logic can be built.</p>

<h3 id="unlocking-the-power-of-sitecore-search">Unlocking the Power of Sitecore Search</h3>

<p>Jeroen Speldekamp brought us three hands-on proof of concepts:</p>
<ol>
  <li>How to use Geo data for a distance-aware search</li>
  <li>How to use marketing campaigns to trigger certain rules</li>
  <li>How to easily generate a simple chatbot/FAQs by generating questions and answers with Sitecore Stream based on data that was indexed from a website</li>
</ol>

<p>Here I really enjoyed the innovative way Sitecore Stream was used to speed up a tedious process like generating an FAQ and seeing the power of Sitecore Search in action.</p>

<h2 id="day-2">Day 2</h2>

<p>Day 2 started early after a great community dinner the evening before, where I had the chance to meet and exchange with others, digest all the information from that day, and enjoy great food.</p>

<p>The first session of the day was an update on Sitecore Stream in XM and XP.</p>

<h3 id="sitecore-stream-in-dxp">Sitecore Stream in DXP</h3>

<p>Vignesh Vishwanath demonstrated how the Sitecore Stream features will be integrated into XM and XP and how they can be installed. My key takeaways:</p>
<ul>
  <li>Minimum version is 10.2</li>
  <li>A Sitecore Cloud Portal account is required</li>
  <li>Stream can be downloaded as an installable module</li>
  <li>It is available as a free tier or paid subscription</li>
  <li>It’s a bit tricky to find the module, so best you contact your respective partner manager</li>
</ul>

<h3 id="step-by-step-optimization-for-nextjs-and-sitecore-jss">Step-by-Step Optimization for Next.js and Sitecore JSS</h3>

<p>This session by Alex Hawley, I did not attend, but it was highly praised by many people I talked to. I can recommend this blog post, though, that covers the same topic: <a href="https://vercel.com/guides/how-to-optimize-next.js-sitecore-jss">How to Optimize Next.js + Sitecore JSS</a>.</p>

<p>He also holds this talk on other platforms, so keep an eye on the Slack channels if you have also missed his talk at SUGCON.</p>

<h3 id="elevating-developer-experience-modernizing-the-jss-sdk-and-starter-kits-for-xm-cloud">Elevating Developer Experience: Modernizing the JSS SDK and Starter Kits for XM Cloud</h3>

<p>Liz Nelson and Christian Hahn presented the new “Content SDK”, which is at the moment still in beta version but is expected to go to first release within the next few months.</p>

<p>The Content SDK is a reduced version of the JSS SDK. These reductions were achieved by removing all code that is related solely to XM and XP (e.g., the Experience Editor). So the new Content SDK will only work for XM Cloud and from now on, both SDKs will be developed independently from each other.</p>

<p>You can already check out the Content SDK <a href="https://github.com/Sitecore/content-sdk">here</a>. If you are starting a new project now, the recommendation from Sitecore is to already start using the Content SDK as its first stable release is expected very soon. If you plan to migrate your JSS SDK app to Cloud SDK, you should wait for the official release.</p>

<p>The same has been done for the XM Cloud starter kits, which received a major rebrush to make it easier to understand and to create as little boilerplate code as possible.</p>

<h3 id="accelerating-success">Accelerating Success</h3>

<p>Katharina Luger gave a nice lightning talk introducing the Sitecore Acceleration campaign with the most interesting part being the cookbook: <a href="https://developers.sitecore.com/learn/accelerate">Sitecore Accelerate Cookbook</a>.</p>

<p>This is a collection of best practices where developers and consultants can find recommended solutions or approaches based on certain scenarios.</p>

<p>Currently, they cover XM Cloud and Content Hub, but the rest of the Cloud portfolio as well as XM and XP is already planned. So it is worth revisiting the page from time to time and checking for updates.</p>

<h3 id="sitecores-marketplace-roadmap-review--developer-program">Sitecore’s Marketplace Roadmap Review &amp; Developer Program</h3>

<p>Krassi Eneva and Justin Vogt gave us a deeper look into the new XM Cloud App Marketplace.</p>

<p>The Marketplace is still in a closed testing phase and is planned to be published later this year. What we already know:</p>
<ol>
  <li>Apps can have three scopes:
    <ol>
      <li>Public: Available to everyone on the Marketplace (apps can be made free, paid, or freemium)</li>
      <li>Organization: Apps are available across multiple projects in the same organization (e.g., if you want to reuse a certain feature across multiple customer projects)</li>
      <li>Private: Can only be used on one certain project (makes sense if the app covers very specific business logic)</li>
    </ol>
  </li>
  <li>Apps will be reviewed by Sitecore including the design
    <ol>
      <li>It is not necessary but recommended to use Sitecores <a href="https://blok.sitecore.com/">Blok Design System</a></li>
    </ol>
  </li>
  <li>The <a href="https://github.com/Sitecore/sitecore-marketplace-sdk">Marketplace SDK</a> is already available</li>
  <li>Apps can extend certain functionalities like Pages or create new Dashboards</li>
</ol>

<p>I’m looking forward to this new platform and what exciting ideas the community will come up with to extend Sitecore functionalities.</p>

<h3 id="a-walk-through-of-xm-cloud-content">A Walk-through of XM Cloud Content</h3>

<p>In the last session, Alistair Deneys gave a very hands-on demonstration of Sitecore’s new CMS “XM Cloud Content”. This will be a truly headless lightweight CMS with a couple of nice extra features.</p>

<p>In his demo, Alistair created a simple blog post including referenced authors by only using XM Cloud Content’s API. The demo included the whole process from defining the content structure to eventually publishing and requesting the created content.</p>

<p>He also highlighted the simplified architecture, that there will not be a separate WEB database, and publishing an item will simply change the entry’s publishing field. This has the nice effect that scheduled publications are easily possible out of the box.</p>

<p>Alternatively to the API, XM Cloud Content has an intuitive user interface as well, where users can define new content templates, create new content, and maintain it.</p>

<p>Right now, this new product is still under development and I am already looking forward to getting some hands-on experience with XM Cloud Content.</p>

<h2 id="closing-words">Closing Words</h2>

<p>My first SUGCON ended with the closing ceremony and I left with many new impressions and ideas. I cannot wait to apply my learnings to real projects and I will be looking forward to the next SUGCON event.</p>

<p>Big thanks to all organizers, speakers, and all the people involved in making this great community event possible.</p>]]></content><author><name>shauck</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="SUGCON" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">The challenges when upgrading Sitecore Connect for Content Hub from 5.0 to 5.2 on docker.</title><link href="https://sitecore.merkle.com/scch-upgrade-challenges/" rel="alternate" type="text/html" title="The challenges when upgrading Sitecore Connect for Content Hub from 5.0 to 5.2 on docker." /><published>2025-03-31T07:00:00+00:00</published><updated>2025-03-31T07:00:00+00:00</updated><id>https://sitecore.merkle.com/scch-upgrade-challenges</id><content type="html" xml:base="https://sitecore.merkle.com/scch-upgrade-challenges/"><![CDATA[<p><img src="../files/2025/03/31/hurdles.jpg" alt="Hurdles" title="Hurdles" /></p>

<h2 id="introduction">Introduction</h2>
<p><strong>Sitecore Connect for Content Hub (SCCH)</strong> is a bridge between Sitecore Experience Platform and Sitecore Content Hub to synchronize data between the two platforms. It allows editors and marketers to work seamlessly with content created in Sitecore Content Hub. This integration helps maintain consistency and efficiency across different platforms.</p>

<p>With <a href="https://developers.sitecore.com/downloads/Sitecore_Connect_for_Content_Hub/5x/Sitecore_Connect_for_Content_Hub_510/Release_Notes">SCCH version 5.2</a> Sitecore introduced many new features to the connector so we planned to do an upgrade of SCCH from version 5.0 to 5.2. Throughout this process I faced multiple challenges, which rendered this supposedly very small ticket to be more complex than expected.</p>

<p>The conditions I worked with were as follows:</p>
<ul>
  <li>Sitecore version 10.2.1.</li>
  <li>Locally running on docker.</li>
  <li>UAT and PROD running on Kubernetes.</li>
  <li>We took the solution over from another agency and were not involved in the installation of the SCCH in the first place.</li>
</ul>

<p>The goal was clear and the road ahead was <a href="https://doc.sitecore.com/xp/en/developers/connect-for-ch/52/connect-for-content-hub/walkthrough--upgrading-scch-5-1-to-5-2-on-docker.html">well documented</a> so: “ready, set, go!”</p>

<h2 id="initial-challenges-with-the-mssql-upgrade-step">Initial challenges with the mssql-upgrade step</h2>

<p>While going through the documentation, we first stumbled upon the mssql-upgrade step because we could not find any hint in the solution, that the already existing items were created utilizing an mssql-init image.</p>

<p>So, we asked Sitecore Support for clarification and got the response, that the mssql-upgrade image is not necessary when you have already version 5.0 or higher installed. The image is only used to clean old items from installations of versions lower than 5.0.</p>

<h2 id="items-as-resources">Items as resources</h2>

<p>From version 5.0 onwards SCCH is  utilizing <strong>“Items as resources”</strong>. This approach was introduced with Sitecore 10 and allows to store items not in the database but in the  file system instead. So, the needed items for the upgraded SCCH should be present as files in the image, that we import to our CM image. During startup Sitecore loads the synchronized items from the file into the Content Editor.</p>

<p>To verify, that it worked we simply look for the following two folders in the item tree:</p>
<ul>
  <li>/sitecore/system/Modules/CMP</li>
  <li>/sitecore/system/Modules/DAM</li>
</ul>

<p>These items belong to the SCCH and are included in the docker image as <strong>.dat</strong> files.</p>

<h2 id="resource-merger-issues">Resource merger issues</h2>

<p><em>“So I can simply update the CM docker image according to this <a href="https://doc.sitecore.com/xp/en/developers/connect-for-ch/52/connect-for-content-hub/walkthrough--preparing-to-install-the-scch-connector-module-in-a-container-environment.html#build-the-docker-images">documentation</a> and DLLs, configs and also the items will be deployed to my CM container”</em>, I thought.</p>

<p>However, the items were not there and with the benefit of hindsight I can say that next I went down a rabbit hole into the completely wrong direction.</p>

<p>I start to further investigate on “Items as resources.”</p>

<p>First, I confirm that the files are properly copied to the CM container, no issue here.</p>

<p><img src="../files/2025/03/31/items-as-resources.png" alt="Items as resources" title="Items as resources" /></p>

<p>But when I check the logs, I find entries from the “Resource merger”, where I see, that it works generally, only the files from “sitecore modules” are ignored:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2788 21:26:40 INFO  Resource merger: file C:\INETPUB\WWWROOT\SITECORE MODULES\ITEMS\CMP\MASTER\ITEMS.MASTER.CMP.DAT, updated item definitions count = 0.
2788 21:26:40 INFO  Resource merger: file C:\INETPUB\WWWROOT\SITECORE MODULES\ITEMS\CMP\MASTER\ITEMS.MASTER.CMP.DAT, new item definitions count = 65.
2788 21:26:40 INFO  Resource merger: file C:\INETPUB\WWWROOT\SITECORE MODULES\ITEMS\CMP\MASTER\ITEMS.MASTER.CMP.DAT, updated shared definitions count = 0.
2788 21:26:40 INFO  Resource merger: file C:\INETPUB\WWWROOT\SITECORE MODULES\ITEMS\CMP\MASTER\ITEMS.MASTER.CMP.DAT, new shared definitions count = 65.
</code></pre></div></div>

<p>While looking for the cause of this issue I learned about Protobuf, which is the serializer that is used for the items, that are stored on disk and about the <a href="https://github.com/GAAOPS/Sitecore.Protobuf.Browser">Sitecore Protobuf Browser</a> – a nice tool to compare multiple files. I used this to compare the serialized items of 5.0 and 5.2 to make sure, there are no incompatible items included, without success though.</p>

<p>So next I check for any misconfigurations and look at that, these filePaths look suspicious:</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;protobufItems</span> <span class="na">type=</span><span class="s">"Sitecore.Data.DataProviders.ReadOnly.Protobuf.ProtobufDataProvider, Sitecore.Kernel"</span> <span class="na">patch:source=</span><span class="s">"Customer.Project.ChubConnector.config"</span><span class="nt">&gt;</span>
<span class="nt">&lt;filePaths</span> <span class="na">hint=</span><span class="s">"list"</span> <span class="na">patch:source=</span><span class="s">"Customer.Project.ChubConnector.config"</span><span class="nt">&gt;</span>
<span class="nt">&lt;filePath&gt;</span>/App_Data/items/$(id)<span class="nt">&lt;/filePath&gt;</span>
<span class="nt">&lt;modulesFilePath&gt;</span>/sitecore modules/items/$(id)<span class="nt">&lt;/modulesFilePath&gt;</span>
<span class="nt">&lt;/filePaths&gt;</span>
<span class="nt">&lt;/protobufItems&gt;</span></code></pre></figure>

<p>The way these paths are configured, they will not pick up the files from the CMP and DAM modules, so I add these two lines via patch file to the databases:</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;cmpModulesFilePath&gt;</span>/sitecore modules/items/cmp/$(id)<span class="nt">&lt;/mpModulesFilePath&gt;</span>
<span class="nt">&lt;damModulesFilePath&gt;</span>/sitecore modules/items/dam/$(id)<span class="nt">&lt;/damModulesFilePath&gt;</span></code></pre></figure>

<p>I rebuilt the image and now the items were there. At this point I was sure, that I found a bug in the chub-assets image (this will turn out to be false). I checked in my changes, deployed to UAT, tested the Connector … and it did not work. The items were there, but when I tried to pick an asset from the DAM, the dialogue stayed blank.</p>

<h2 id="missing-configurations">Missing configurations</h2>

<p>I debugged the dialogue and quickly found out, that an api request to <strong>/sitecore/api/dam/MContent/GetAuthenticationDetails</strong> is returning a 404.</p>

<p>This api endpoint is defined in a DLL from the SCCH module and I could also confirm that this DLL was properly deployed to the CM instance.</p>

<p>Now do you remember, how I was chasing that white rabbit named “Items as resources?” Because next I did something, I should have done in the first place. I checked the showconfig.aspx, if the module configs were applied and if the processor that registers the api route is added. At this point I realized that none of the configurations were applied to Sitecore. I checked again the config files inside the module image and learned, that they require a certain setting:</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;sitecore</span> <span class="na">damEnabled:require=</span><span class="s">"yes"</span><span class="nt">&gt;</span>…<span class="nt">&lt;/sitecore&gt;</span></code></pre></figure>

<p>This turns out to be a change introduced with version 5.1 and you can also find the documentation to <a href="https://doc.sitecore.com/xp/en/developers/connect-for-ch/latest/connect-for-content-hub/enable-cmp-or-dam.html">Enable CMP or DAM</a> here.</p>

<h2 id="implementing-the-final-fixes">Implementing the final fixes</h2>

<p>But I had to clear out one last hurdle, because the config change just would not apply, no matter what I tried and eventually I found a mistake in our dockerfile, where the transformations from the SCCH module are done after our custom transformations and the module contains a transformation itself, that explicitly sets the values to false.</p>

<p><img src="../files/2025/03/31/reorder-dockerfile.png" alt="Reorder dockerfile instructions" title="Reorder dockerfile instructions" /></p>
<blockquote>
  <p>This must run last.</p>
</blockquote>

<p>After reordering the transformation steps, the settings finally changed to “yes” and I could see, that all configs were correctly applied – including the supposedly missing resource paths, as I described earlier, which I thought was a bug and all is fine and working.</p>

<p>After hours of work, which could have been an hour I could finally declare sweet victory.</p>

<h2 id="conclusion">Conclusion</h2>

<p>What do I take from this endeavor?</p>

<ol>
  <li>When in doubt, check the showconfig.aspx <strong>first</strong>.</li>
  <li>I learned about items as resources.</li>
  <li>Do not forget release notes when doing an upgrade, e.g. <a href="https://developers.sitecore.com/downloads/Sitecore_Connect_for_Content_Hub/5x/Sitecore_Connect_for_Content_Hub_510/Release_Notes">Release Notes for SCCH 5.1.0</a></li>
</ol>

<p>Even with all the challenges I faced I am still happy with the result, as I was able to learn a few new things but would be just as happy, if this post saves someone a couple of head scratches and hours.</p>

<p>Head image by <a href="https://unsplash.com/@interactivesports?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Interactive Sports</a> on <a href="https://unsplash.com/photos/white-track-field-with-white-ramsp-VYTQNnaboUA?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>]]></content><author><name>shauck</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="Kubernetes" /><category term="Docker" /><category term="Content Hub" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Troubleshooting an AKS Sitecore solution</title><link href="https://sitecore.merkle.com/troubleshooting-an-aks-sitecore-solution/" rel="alternate" type="text/html" title="Troubleshooting an AKS Sitecore solution" /><published>2024-12-20T11:00:00+00:00</published><updated>2024-12-20T11:00:00+00:00</updated><id>https://sitecore.merkle.com/troubleshooting-an-aks-sitecore-solution</id><content type="html" xml:base="https://sitecore.merkle.com/troubleshooting-an-aks-sitecore-solution/"><![CDATA[<p><img src="../files/2024/12/20/kubernetes.jpg" alt="Kubernetes" title="Kubernetes" />
Photo by <a href="https://unsplash.com/@growtika?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Growtika</a> on <a href="https://unsplash.com/photos/a-group-of-blue-boxes-ZfVyuV8l7WU?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>

<h1 id="introduction">Introduction</h1>
<p>With a Kubernetes solution such as AKS, it is easier to scale the application according to your needs and simpler to deploy. However, troubleshooting is more difficult and requires different tools than with an app service solution.</p>

<h1 id="tooling">Tooling</h1>

<h2 id="openlens">OpenLens</h2>
<p>OpenLens is a GUI tool to manage Kubernetes clusters, without the need of extensive command line usage. It can be downloaded <a href="https://github.com/MuhammedKalkan/OpenLens/releases">here</a>.</p>

<h3 id="pod-menu-extension">Pod Menu Extension</h3>
<p>With version 6.3.0, the Node / Pod Menu with the useful functionalities attach, shell and logs were removed from OpenLens.</p>

<p><img src="../files/2024/12/20/missing-pod-menu.png" alt="MissingPodMenu" title="Missing pod menu" /></p>

<p>To bring it back, you can add the POD Menu Extension from <a href="https://github.com/alebcay/openlens-node-pod-menu">this repository</a>.</p>

<p><img src="../files/2024/12/20/pod-menu.png" alt="PodMenu" title="Pod menu" /></p>

<h2 id="alternatives">Alternatives</h2>
<p>Currently OpenLens is the tool I use, but since the project is no longer being maintained, there are alternative open source tools that I may use in the future. They are very similar, free, open source and actively in development, here are some examples:</p>

<ul>
  <li><a href="https://github.com/derailed/k9s">K9s</a></li>
  <li><a href="https://github.com/unxsist/jet-pilot">Jet Pilot</a></li>
  <li><a href="https://github.com/getseabird/seabird">Seabird</a></li>
</ul>

<h2 id="connection-to-azure">Connection to Azure</h2>
<p>To connect an AKS Cluster from your local machine, first close your OpenLens instance, go to the <a href="https://portal.azure.com">Azure Portal</a>, find your <strong>AKS Cluster (1)</strong>, go to <strong>Overview (2)</strong> and press <strong>Connect (3)</strong>.</p>

<p><img src="../files/2024/12/20/connection-to-azure.png" alt="ConnectionToAzure" title="Connection to Azure" /></p>

<p>On the right side of the browser, a menu will open. Select Azure CLI and follow the instruction.</p>

<h3 id="config-file">Config file</h3>
<p>Adding the cluster via Azure CLI will edit your Kubernetes config file on your machine. Usually, it’s located on <strong>C:\Users\USERNAME.kube</strong></p>

<p>You may need to add the Kubernetes folder to OpenLens via menu, File, Preferences:</p>

<p><img src="../files/2024/12/20/file-preferences.png" alt="FilePreferences" title="File - Preferences" /></p>

<p>Go to Kubernetes (1), press Sync folder (2) and add the folder where the Kubeconfig file is stored C:\Users\USERNAME.kube</p>

<p><img src="../files/2024/12/20/sync-folder.png" alt="SyncFolder" title="Sync folder" /></p>

<h2 id="clusters-in-openlens">Clusters in OpenLens</h2>
<p>After adding the clusters, in the Catalog (1) select Clusters (2) and you will see all the added clusters (3) in the overview.</p>

<p><img src="../files/2024/12/20/clusters-overview.png" alt="ClustersOverview" title="Clusters overview" /></p>

<h2 id="namespace-filter">Namespace filter</h2>
<p>When you open the Pods menu (1), and see only one pod (2), it’s possible that the selected namespace (3) is not the one you want. You can op the namespace dropdown menu (4) and select the one you want.</p>

<p><img src="../files/2024/12/20/namespace-filter.png" alt="NamespaceFilter" title="Namespace filter" /></p>

<h2 id="basic-operations">Basic operations</h2>

<h3 id="view-and-download-logs">View and download logs</h3>
<p>To view the live logs of a Pod, go to Workloads (1), then Pods (2), select the Pod (3), press Pod logs (4) from the pod menu extension, select the container (5) and the logs will shown in the terminal below (6).</p>

<p><img src="../files/2024/12/20/show-logs.png" alt="ShowLogs" title="Show logs" /></p>

<p>To download all logs, click on the Download button (1) and choose between All logs (2).</p>

<p><img src="../files/2024/12/20/download-all-logs.png" alt="DownloadAllLogs" title="Download all logs" /></p>

<h3 id="pod-shell">Pod Shell</h3>
<p>You can use the pod shell to use shell commands for example on the CM. Go to Workloads (1), then Pods (2), select the Pod (3) and press Pod Shell (4). The Pod Shell will open on the bottom (5) where you can navigate to a folder, list, read, or update files.</p>

<p><img src="../files/2024/12/20/pod-shell.png" alt="PodShell" title="Pod shell" /></p>

<h3 id="restart-a-deployment">Restart a deployment</h3>

<ol>
  <li>Go to Workloads</li>
  <li>Deployments</li>
  <li>Select the deployment you want to restart</li>
  <li>Press the restart button and confirm</li>
</ol>

<p><img src="../files/2024/12/20/restart-deployment.png" alt="RestartDeployment" title="Restart a deployment" /></p>

<h3 id="the-terminal">The Terminal</h3>
<p>There is a Terminal (1) in OpenLens, with that you can use directly kubectl commands without any additional installation.</p>

<p><img src="../files/2024/12/20/terminal.png" alt="Terminal" title="Terminal" /></p>

<h4 id="copy-files-from-a-container-to-your-computer">Copy files from a container to your computer</h4>
<p>With the terminal, you can copy for example a dll to your local machine by using following command:</p>

<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="go">kubectl cp -n sitecore sitecore-cd-abcd123456-foo12:\inetpub\wwwroot\bin\website.dll .\localwebsite.dll</span></code></pre></figure>

<p>The command is composed as follows:</p>

<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="go">kubectl cp -n NAMESPACE sitecore-cd-YOUR-POD:PATH_TO_REMOTE.DLL .\LOCAL_PATH.DLL</span></code></pre></figure>

<h4 id="copy-files-from-your-computer-to-a-container">Copy files from your computer to a container</h4>
<p>Copying a local file to the remote container is based on the same principle:</p>

<figure class="highlight"><pre><code class="language-console" data-lang="console"><span class="go">kubectl cp .\localwebsite.dll -n sitecore sitecore-cd-abcd123456-foo12:\inetpub\wwwroot\bin\website.dll</span></code></pre></figure>

<h1 id="conclusion">Conclusion</h1>
<p>Troubleshooting an AKS solution can be a challenge, but with the right tools it can be easily managed. On the other hand, many things, such as deploying individual services without downtime, are much easier. We have been using OpenLens for a long time, it is a pity that the open source development has been discontinued, but there are many alternatives.</p>]]></content><author><name>ryilmaz</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="Kubernetes" /><category term="AKS" /><category term="Docker" /><category term="Azure" /><summary type="html"><![CDATA[Photo by Growtika on Unsplash]]></summary></entry><entry><title type="html">Cleaning up [orphan] from indexes</title><link href="https://sitecore.merkle.com/cleaning-up-orphan-from-indexes/" rel="alternate" type="text/html" title="Cleaning up [orphan] from indexes" /><published>2024-10-30T11:00:00+00:00</published><updated>2024-10-30T11:00:00+00:00</updated><id>https://sitecore.merkle.com/cleaning-up-orphan-from-indexes</id><content type="html" xml:base="https://sitecore.merkle.com/cleaning-up-orphan-from-indexes/"><![CDATA[<p><img src="../files/2024/10/30/sitecore-orphan.png" alt="alt text" title="Sitecore [orphan]" /></p>

<p>Recently on our live website, indexes stopped working while showing data from a particular page type. Also on these pages, most components were missing. 
Everything was working fine on the master database. In the logs, we found the following error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Message: Could not map index document field to property "Paths" on type App.Foundation.Search.SearchResultItem : Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). Actual value: [orphan]
</code></pre></div></div>

<p>Also, when we visited those pages on the web database and opened presentation details, it was not possible to check the datasource, because the browse button was throwing an error if the component datasource was coming from the local datasource. In the logs, we found the following error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Exception: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Source: Sitecore.XA.Foundation.LocalDatasources
   at Sitecore.XA.Foundation.LocalDatasources.RenderingDatasources.DatasourceSettingsProvider.GetSettingsItem(Item contextItem, ID renderingId)
   at Sitecore.XA.Foundation.LocalDatasources.RenderingDatasources.DatasourceSettingsProvider.HasSettings(Item contextItem, ID renderingId)
   at Sitecore.XA.Foundation.LocalDatasources.Pipelines.GetRenderingDatasource.AddAdditionalTemplates.Process(GetRenderingDatasourceArgs args)
   at (Object , Object )
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain)
   at Sitecore.XA.Foundation.LocalDatasources.CustomFields.FieldTypesEx.BucketInternalLink.ShowDialog(ClientPipelineArgs args)
</code></pre></div></div>

<p>So we had 2 different problems that might be connected.</p>

<p>With these clues in hand, we checked indexes for these page types. Then, found out that there were many cases with [orphan] assigned to the <em>_path</em> field:</p>

<p><img src="../files/2024/10/30/solr_orphan.png" alt="alt text" title="Solr Orphan in Path" /></p>

<p>We tried rebuilding databases but it didn’t help and [orphan]s stayed. </p>

<p>Then we tried database cleanup from sitecore/admin/dbcleanup.aspx:</p>

<p><img src="../files/2024/10/30/sitecore_database_cleanup.png" alt="alt text" title="Sitecore Database Cleanup" /></p>

<p>and rebuild indexes again. All orphans got cleared from indexes.</p>

<p>Once we checked the error we got from local datasources, we also saw that the error was gone.</p>

<p>Indexes worked again, pages were showing components after clearing the caches.</p>]]></content><author><name>tcanyalcin</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="Sitecore 10.3" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">EXM campaign dispatch stopped because CM instance crashed</title><link href="https://sitecore.merkle.com/exm-campaign-dispatch-stuck/" rel="alternate" type="text/html" title="EXM campaign dispatch stopped because CM instance crashed" /><published>2024-09-03T07:00:00+00:00</published><updated>2024-09-03T07:00:00+00:00</updated><id>https://sitecore.merkle.com/exm-campaign-dispatch-stuck</id><content type="html" xml:base="https://sitecore.merkle.com/exm-campaign-dispatch-stuck/"><![CDATA[<p><img src="../files/2024/09/03/email.jpg" alt="Email Marketing" title="Email Marketing" /></p>

<h2 id="introduction">Introduction</h2>

<p>In the world of digital marketing, email campaigns are a crucial tool for engaging with our audience, driving conversions, and building brand loyalty. 
However, even the most meticulously planned campaigns can face unexpected challenges. Recently, we encountered a significant disruption when our system crashed mid-dispatch, causing our email campaigns to get stuck. Here’s a look at what happened, how we responded, and the lessons we learned from the experience.</p>

<h4 id="the-incident-when-things-went-off-track">The Incident: When Things Went Off Track</h4>

<p>The campaigns were designed to reach thousands of subscribers with personalized content that would resonate with their interests.</p>

<p>But then, the unexpected happened. Just as the second campaign of three was rolling out, our CM server crashed due to another issue.</p>

<p>The result? Hundreds of emails were stuck in limbo, neither reaching the inboxes nor bouncing back as undeliverable.</p>

<h4 id="recovery-getting-back-on-track">Recovery: Getting Back on Track</h4>

<p>Once the system was back online, we had to decide how to proceed with the campaign.</p>

<p>Our options included resending the emails that were stuck or pausing the campaign altogether to avoid overwhelming our subscribers. After careful consideration, we chose to stagger the dispatch of the remaining emails. This allowed us to complete the campaign without bombarding recipients or risking further technical issues. We also took this opportunity to recheck the content and timing, ensuring that the messages were still relevant and effective.</p>

<h4 id="solution">Solution</h4>

<p>Since the campaign with the status “Sending” stopped with a progress of 40%, we wanted to be sure and reach out to the Sitecore Support team to find the best technical solution.</p>

<p><img src="../files/2024/09/03/exm-campaign-status.png" alt="EXM Master" title="Database snippet" /></p>

<p>In this case, we need to manually change the states. Pausing and resuming the campaign was not possible in our case. Clicking the Button in the Delivery tab of the campaign did not change anything.</p>

<p>So manually modifying the related message status in the EXM.Master DB helps.</p>

<p>Here is the SQL script we used:</p>

<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="n">USE</span> <span class="p">[</span><span class="n">Sitecore</span><span class="p">.</span><span class="n">Exm</span><span class="p">.</span><span class="n">master</span><span class="p">]</span>
<span class="k">GO</span>

<span class="k">UPDATE</span> <span class="p">[</span><span class="n">dbo</span><span class="p">].[</span><span class="n">Campaigns</span><span class="p">]</span>
<span class="k">SET</span> <span class="p">[</span><span class="n">Status</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'Paused'</span>
<span class="k">WHERE</span> <span class="n">MessageID</span> <span class="o">=</span> <span class="s1">'Your campaign id'</span>
<span class="k">GO</span></code></pre></figure>

<p>By setting the status to <b>Paused</b> we were able to resume the campaing by clicking <b>Resume</b> in the Delivery tab of the campaign.</p>

<p>You need also to find the campaign in the Content Editor by searching with the campaign ID, unprotect the item, and set the status again there to <b>Draft</b> to be able to send the Mail through the UI. That worked out.</p>

<p>After the successful send-out of this campaign, the last one with the Status <b>Queuing</b> remains in that state. Sitecore does not get the information to continue with the next campaign. That needs to be considered.</p>

<p>So we decided here to set the state to <b>Draft</b> by utilizing the SQL statement from above. 
Again, you need also to find the campaign in the content editor by searching with the campaign ID, unprotect the item, and set the status there to <b>Draft</b> to be able to send the Mail through the UI.</p>

<p><br />
Image from <a href="https://pixabay.com/de/users/mohamed_hassan-5229782/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=3543958">Mohamed Hassan</a> on <a href="https://pixabay.com/de//?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=3543958">Pixabay</a></p>]]></content><author><name>dgashi</name></author><category term="Sitecore" /><category term="Sitecore" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Extend rich text editor for custom link type</title><link href="https://sitecore.merkle.com/extend-rich-text-editor-for-custom-link-type/" rel="alternate" type="text/html" title="Extend rich text editor for custom link type" /><published>2024-05-06T05:00:00+00:00</published><updated>2024-05-06T05:00:00+00:00</updated><id>https://sitecore.merkle.com/extend-rich-text-editor-for-custom-link-type</id><content type="html" xml:base="https://sitecore.merkle.com/extend-rich-text-editor-for-custom-link-type/"><![CDATA[<p><img src="../files/2024/05/06/link-with-tracking.png" alt="alt text" title="Rich text editor with custom link" /></p>

<p>Based on the previous blog post about (<a href="/11-steps-to-extend-general-link">extenting Sitecore’s general link</a>), you may also want to enhance the content editing experience for your editors by extending the rich text editor with your newly created link.</p>

<p>In this tutorial, I’ll guide you through the steps to create a custom dialog for Sitecore’s rich text editor. To achieve this, follow the specified steps:</p>

<h2 id="1-create-your-custom-dialogform-class">1. Create your custom DialogForm class</h2>

<p>The first thing you need to do is create your custom DialogForm. It should derive from DialogForm, and you’ll need to override <code class="language-plaintext highlighter-rouge">OnCancel</code> and <code class="language-plaintext highlighter-rouge">OnOK</code> methods.</p>

<p>For example: Extending General Link Fields with Tracking Data</p>

<p>As in our example, you want to enhance the general link fields in your custom dialog by adding tracking data, specifically the fields EventCategory, EventAction, and EventLabel. You can follow these steps:</p>

<ul>
  <li>Create your custom <code class="language-plaintext highlighter-rouge">DialogForm</code> class, inheriting from the base <code class="language-plaintext highlighter-rouge">DialogForm</code>.</li>
  <li>Override the <code class="language-plaintext highlighter-rouge">OnCancel</code> and <code class="language-plaintext highlighter-rouge">OnOK</code> methods to handle the dialog behavior.</li>
  <li>In the OnOK method, retrieve the values from your custom fields (e.g., EventCategory, EventAction, and EventLabel).</li>
  <li>Append these values to the result or perform any other necessary actions based on the user’s input.</li>
</ul>

<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">Sitecore</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Diagnostics</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Shell.Applications.ContentEditor</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Text</span><span class="p">;</span>

<span class="k">using</span> <span class="nn">Sitecore</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Diagnostics</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Web.UI.HtmlControls</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Web.UI.Pages</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">Sitecore.Web.UI.Sheer</span><span class="p">;</span>

<span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Diagnostics.CodeAnalysis</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">System.Text</span><span class="p">;</span>

<span class="k">namespace</span> <span class="nn">Your.own.namespace</span>
<span class="p">{</span>
	<span class="p">[</span><span class="n">ExcludeFromCodeCoverage</span><span class="p">]</span>
	<span class="k">public</span> <span class="k">class</span> <span class="nc">InsertLinkWithTracking</span> <span class="p">:</span> <span class="n">DialogForm</span>
	<span class="p">{</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">URL</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Combobox</span> <span class="n">Target</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">Class</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">Title</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">Querystring</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">EventCategory</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">EventAction</span><span class="p">;</span>
		<span class="k">protected</span> <span class="n">Edit</span> <span class="n">EventLabel</span><span class="p">;</span>

		<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnCancel</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">args</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">Assert</span><span class="p">.</span><span class="nf">ArgumentNotNull</span><span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="s">"sender"</span><span class="p">);</span>
			<span class="n">Assert</span><span class="p">.</span><span class="nf">ArgumentNotNull</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">"args"</span><span class="p">);</span>
			<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">Mode</span> <span class="p">==</span> <span class="s">"webedit"</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="k">base</span><span class="p">.</span><span class="nf">OnCancel</span><span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">args</span><span class="p">);</span>
			<span class="p">}</span>
			<span class="k">else</span>
			<span class="p">{</span>
				<span class="n">SheerResponse</span><span class="p">.</span><span class="nf">Eval</span><span class="p">(</span><span class="s">"scCancel()"</span><span class="p">);</span>
			<span class="p">}</span>
		<span class="p">}</span>

		<span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnOK</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventArgs</span> <span class="n">args</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">Assert</span><span class="p">.</span><span class="nf">ArgumentNotNull</span><span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="s">"sender"</span><span class="p">);</span>
			<span class="n">Assert</span><span class="p">.</span><span class="nf">ArgumentNotNull</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">"args"</span><span class="p">);</span>
			<span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">URL</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
			<span class="p">{</span>
				<span class="n">SheerResponse</span><span class="p">.</span><span class="nf">Alert</span><span class="p">(</span><span class="s">"Bitte geben Sie die URL ein."</span><span class="p">,</span> <span class="n">Array</span><span class="p">.</span><span class="n">Empty</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;());</span>
			<span class="p">}</span>

			<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="n">Mode</span> <span class="p">==</span> <span class="s">"webedit"</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="k">base</span><span class="p">.</span><span class="nf">OnOK</span><span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">args</span><span class="p">);</span>
			<span class="p">}</span>
			<span class="k">else</span>
			<span class="p">{</span>
				<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StringBuilder</span><span class="p">(</span><span class="s">$"href=\"</span><span class="p">{</span><span class="n">URL</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">Querystring</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="k">if</span> <span class="p">(</span><span class="n">Querystring</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="nf">StartsWith</span><span class="p">(</span><span class="s">"?"</span><span class="p">))</span>
					<span class="p">{</span>
						<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="n">Querystring</span><span class="p">.</span><span class="n">Value</span><span class="p">);</span>
					<span class="p">}</span>
					<span class="k">else</span>
					<span class="p">{</span>
						<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="s">$"?</span><span class="p">{</span><span class="n">Querystring</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">"</span><span class="p">);</span>
					<span class="p">}</span>
				<span class="p">}</span>
				<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="sc">'"'</span><span class="p">);</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">Target</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="k">switch</span> <span class="p">(</span><span class="n">Target</span><span class="p">.</span><span class="n">Value</span><span class="p">)</span>
					<span class="p">{</span>
						<span class="k">case</span> <span class="s">"New"</span><span class="p">:</span>
							<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="s">" target=\"_blank\""</span><span class="p">);</span>
							<span class="k">break</span><span class="p">;</span>
					<span class="p">}</span>
				<span class="p">}</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">Class</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="s">$" class=\"</span><span class="p">{</span><span class="n">Class</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">\""</span><span class="p">);</span>
				<span class="p">}</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">Title</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="s">$" title=\"</span><span class="p">{</span><span class="n">Title</span><span class="p">.</span><span class="n">Value</span><span class="p">}</span><span class="s">\""</span><span class="p">);</span>
				<span class="p">}</span>

                <span class="c1">// Custom fields for tracking link</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">EventCategory</span><span class="p">.</span><span class="n">Value</span><span class="p">)</span>
					<span class="p">&amp;&amp;</span> <span class="p">!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">EventAction</span><span class="p">.</span><span class="n">Value</span><span class="p">)</span>
					<span class="p">&amp;&amp;</span> <span class="p">!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrWhiteSpace</span><span class="p">(</span><span class="n">EventLabel</span><span class="p">.</span><span class="n">Value</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="n">result</span><span class="p">.</span><span class="nf">Append</span><span class="p">(</span><span class="s">$" data-clickevent=\\'</span><span class="p">{</span> <span class="p">{</span><span class="err">\</span><span class="s">"eventCategory\": \"{EventCategory.Value}\", \"eventAction\": \"{EventAction.Value}\", \"eventLabel\": \"{EventLabel.Value}\"} }\\'"</span><span class="p">);</span>
				<span class="p">}</span>

				<span class="n">SheerResponse</span><span class="p">.</span><span class="nf">Eval</span><span class="p">(</span><span class="s">$"scClose('</span><span class="p">{</span><span class="n">result</span><span class="p">}</span><span class="s">')"</span><span class="p">);</span>
			<span class="p">}</span><span class="s">
</span>		<span class="p">}</span>

		<span class="k">protected</span> <span class="kt">string</span> <span class="n">Mode</span>
		<span class="p">{</span>
			<span class="k">get</span>
			<span class="p">{</span>
				<span class="kt">string</span> <span class="n">str</span> <span class="p">=</span> <span class="n">StringUtil</span><span class="p">.</span><span class="nf">GetString</span><span class="p">(</span><span class="k">base</span><span class="p">.</span><span class="n">ServerProperties</span><span class="p">[</span><span class="s">"Mode"</span><span class="p">]);</span>
				<span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="nf">IsNullOrEmpty</span><span class="p">(</span><span class="n">str</span><span class="p">))</span>
				<span class="p">{</span>
					<span class="k">return</span> <span class="n">str</span><span class="p">;</span>
				<span class="p">}</span>
				<span class="k">return</span> <span class="s">"shell"</span><span class="p">;</span>
			<span class="p">}</span>
			<span class="k">set</span>
			<span class="p">{</span>
				<span class="n">Assert</span><span class="p">.</span><span class="nf">ArgumentNotNull</span><span class="p">(</span><span class="k">value</span><span class="p">,</span> <span class="s">"value"</span><span class="p">);</span>
				<span class="k">base</span><span class="p">.</span><span class="n">ServerProperties</span><span class="p">[</span><span class="s">"Mode"</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Remark:</p>
<ul>
  <li>Each line of code except the custom field part originates from the decompiled source of the Sitecore’s code.</li>
  <li>It is recommended to place the file in “…\sitecore\shell\Controls\Rich Text Editor...” where all the general controls for the rich text editor of Sitecore are placed.</li>
</ul>

<h2 id="2-create-the-xml-for-your-custom-dialog">2. Create the XML for your custom dialog</h2>
<p>The next step is to create the visual presentation of the dialog. Therefore, you have to create an XML file which contains the control definition for the dialog.
In the example you can find the tree new fields (EventCategory, EventAction and EventLabel) for our custom tracking link. Additionally, the <code class="language-plaintext highlighter-rouge">CodeBeside</code> links to our previous created class.</p>

<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="cp">&lt;?xml version="1.0" encoding="utf-8" ?&gt;</span>
<span class="nt">&lt;control</span> <span class="na">xmlns:def=</span><span class="s">"Definition"</span> <span class="na">xmlns=</span><span class="s">"http://schemas.sitecore.net/Visual-Studio-Intellisense"</span><span class="nt">&gt;</span>
	<span class="nt">&lt;RichText.InsertLinkWithTracking&gt;</span>
		<span class="nt">&lt;FormDialog</span> <span class="na">Icon=</span><span class="s">"office/32x32/edit_in_workflow.png"</span>
					<span class="na">Header=</span><span class="s">"Link mit Tracking einfügen"</span>
					<span class="na">Text=</span><span class="s">""</span>
					<span class="na">OKButton=</span><span class="s">"Link"</span><span class="nt">&gt;</span>
			<span class="nt">&lt;script</span> <span class="na">Type=</span><span class="s">"text/javascript"</span> <span class="na">Language=</span><span class="s">"javascript"</span> <span class="na">Src=</span><span class="s">"/sitecore/shell/Controls/Rich Text Editor/InsertLinkCommon.js"</span><span class="nt">&gt;</span>.<span class="nt">&lt;/script&gt;</span>
			<span class="nt">&lt;CodeBeside</span> <span class="na">Type=</span><span class="s">"Your.own.namespace.InsertLinkWithTracking, YourSolution"</span><span class="nt">/&gt;</span>

			<span class="nt">&lt;GridPanel</span> <span class="na">Class=</span><span class="s">"scFormTable"</span> <span class="na">Width=</span><span class="s">"100%"</span> <span class="na">CellPadding=</span><span class="s">"2"</span> <span class="na">Columns=</span><span class="s">"2"</span> <span class="na">GridPanel.VAlign=</span><span class="s">"top"</span> <span class="na">GridPanel.Style=</span><span class="s">"padding-left: 20px"</span><span class="nt">&gt;</span>
				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"URL:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"URL"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Label</span> <span class="na">for=</span><span class="s">"Target"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">&gt;</span>
					<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Target Window:"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;/Label&gt;</span>
				<span class="nt">&lt;Combobox</span> <span class="na">ID=</span><span class="s">"Target"</span> <span class="na">Width=</span><span class="s">"100%"</span><span class="nt">&gt;</span>
					<span class="nt">&lt;ListItem</span> <span class="na">Value=</span><span class="s">"Self"</span> <span class="na">Header=</span><span class="s">"Active browser"</span><span class="nt">/&gt;</span>
					<span class="nt">&lt;ListItem</span> <span class="na">Value=</span><span class="s">"New"</span> <span class="na">Header=</span><span class="s">"New browser"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;/Combobox&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Style Class:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"Class"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Alternate Text:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"Title"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Query String:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"Querystring"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Tracking Informationen:"</span> <span class="na">GridPanel.Colspan=</span><span class="s">"2"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Event Category:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"EventCategory"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Event Action:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"EventAction"</span><span class="nt">/&gt;</span>

				<span class="nt">&lt;Literal</span> <span class="na">Text=</span><span class="s">"Event Label:"</span> <span class="na">GridPanel.NoWrap=</span><span class="s">"true"</span><span class="nt">/&gt;</span>
				<span class="nt">&lt;Edit</span> <span class="na">ID=</span><span class="s">"EventLabel"</span><span class="nt">/&gt;</span>
			<span class="nt">&lt;/GridPanel&gt;</span>
		<span class="nt">&lt;/FormDialog&gt;</span>
	<span class="nt">&lt;/RichText.InsertLinkWithTracking&gt;</span>
<span class="nt">&lt;/control&gt;</span></code></pre></figure>

<h2 id="3-add-your-custom-control-to-the-rich-text-commands">3. Add your custom control to the rich text commands</h2>
<p>In the third step, you need to extend the out-of-the-box JavaScript file that calls your dialog. Therefore, you can copy the default “RichText Commands.js”file from Sitecore and extend it with your newly created control. It is recommended to also place the file in “…\sitecore\shell\Controls\Rich Text Editor...”.</p>

<p>For our example, we added the following code snippet:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Add the action from the HTML Edit Button Item to CommandList.</span>
<span class="nx">Telerik</span><span class="p">.</span><span class="nx">Web</span><span class="p">.</span><span class="nx">UI</span><span class="p">.</span><span class="nx">Editor</span><span class="p">.</span><span class="nx">CommandList</span><span class="p">[</span><span class="dl">"</span><span class="s2">InsertLinkWithTracking</span><span class="dl">"</span><span class="p">]</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">commandName</span><span class="p">,</span> <span class="nx">editor</span><span class="p">,</span> <span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
	<span class="kd">var</span> <span class="nx">d</span> <span class="o">=</span> <span class="nx">Telerik</span><span class="p">.</span><span class="nx">Web</span><span class="p">.</span><span class="nx">UI</span><span class="p">.</span><span class="nx">Editor</span><span class="p">.</span><span class="nx">CommandList</span><span class="p">.</span><span class="nx">_getLinkArgument</span><span class="p">(</span><span class="nx">editor</span><span class="p">);</span>
	<span class="nx">Telerik</span><span class="p">.</span><span class="nx">Web</span><span class="p">.</span><span class="nx">UI</span><span class="p">.</span><span class="nx">Editor</span><span class="p">.</span><span class="nx">CommandList</span><span class="p">.</span><span class="nx">_getDialogArguments</span><span class="p">(</span><span class="nx">d</span><span class="p">,</span> <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> <span class="nx">editor</span><span class="p">,</span> <span class="dl">"</span><span class="s2">DocumentManager</span><span class="dl">"</span><span class="p">);</span>
	<span class="nx">scEditor</span> <span class="o">=</span> <span class="nx">editor</span><span class="p">;</span>
	<span class="c1">// Call the dialog box.</span>
	<span class="nx">editor</span><span class="p">.</span><span class="nx">showExternalDialog</span><span class="p">(</span>
		<span class="dl">"</span><span class="s2">/sitecore/shell/default.aspx?xmlcontrol=RichText.InsertLinkWithTracking&amp;la=</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">scLanguage</span><span class="p">,</span>
		<span class="kc">null</span><span class="p">,</span> <span class="c1">//argument</span>
		<span class="mi">500</span><span class="p">,</span> <span class="c1">//Width</span>
		<span class="mi">550</span><span class="p">,</span> <span class="c1">//Height</span>
		<span class="nx">scInsertLinkWithTrackingCallback</span><span class="p">,</span> <span class="c1">//callback</span>
		<span class="kc">null</span><span class="p">,</span> <span class="c1">// callback args</span>
		<span class="dl">"</span><span class="s2">InsertLinkWithTracking</span><span class="dl">"</span><span class="p">,</span>
		<span class="kc">true</span><span class="p">,</span> <span class="c1">//modal</span>
		<span class="nx">Telerik</span><span class="p">.</span><span class="nx">Web</span><span class="p">.</span><span class="nx">UI</span><span class="p">.</span><span class="nx">WindowBehaviors</span><span class="p">.</span><span class="nx">Close</span><span class="p">,</span> <span class="c1">// behaviors</span>
		<span class="kc">false</span><span class="p">,</span> <span class="c1">//showStatusBar</span>
		<span class="kc">false</span> <span class="c1">//showTitleBar</span>
	<span class="p">);</span>
<span class="p">};</span>

<span class="c1">// The function is called when the user closes the dialog.</span>
<span class="kd">function</span> <span class="nx">scInsertLinkWithTrackingCallback</span><span class="p">(</span><span class="nx">sender</span><span class="p">,</span> <span class="nx">returnValue</span><span class="p">)</span> <span class="p">{</span>

	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">returnValue</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="kd">var</span> <span class="nx">selectedHtml</span> <span class="o">=</span> <span class="nx">scEditor</span><span class="p">.</span><span class="nx">getSelectionHtml</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">selectedHtml</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">undefined</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
		<span class="nx">scEditor</span><span class="p">.</span><span class="nx">pasteHtml</span><span class="p">(</span><span class="dl">""</span><span class="p">,</span> <span class="dl">"</span><span class="s2">DocumentManager</span><span class="dl">"</span><span class="p">);</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="nx">scEditor</span><span class="p">.</span><span class="nx">pasteHtml</span><span class="p">(</span><span class="dl">"</span><span class="s2">&lt;a </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">returnValue</span><span class="p">.</span><span class="nx">text</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">&gt;</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">selectedHtml</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">&lt;/a&gt;</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">DocumentManager</span><span class="dl">"</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="4-add-a-custom-button-for-the-rich-text-editor">4. Add a custom button for the rich text editor</h2>
<p>Last but not least a button needs to be placed on the rich text so that the dialog can be triggered.</p>

<p>Therefore follow these steps:</p>
<ul>
  <li>Switch the database in Sitecore to the core database and navigate to the following path: “/sitecore/system/Settings/Html Editor Profiles”. Here, you’ll find various rich text editor profiles that you can customize.</li>
  <li>Select the profile you want to extend. For example, let’s say you choose the “Rich Text Full” profile.</li>
  <li>Add a <code class="language-plaintext highlighter-rouge">Html Editor Button</code> to the toolbar. In our example we added the button to “Toolbar 1”.</li>
  <li>Customize the button’s properties. In the Data section add your Click-Event to the field Click. In our example <code class="language-plaintext highlighter-rouge">InsertLinkWithTracking</code>.</li>
  <li>To make your button easily identifiable, add a meaningful icon.</li>
</ul>

<h2 id="5-test-your-implementation">5. Test your implementation</h2>
<p>Now everything is implemented and you can test your custom dialog in the rich text editor by clicking the buton in the rich text toolbar.</p>

<p><img src="../files/2024/05/06/richtext-toolbar.png" alt="Customized richtext toolbar" /></p>

<p>If all fields are filled out, the output for our link with tracking will be as follows:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"/test"</span> <span class="na">title=</span><span class="s">"Link with tracking"</span> <span class="na">data-clickevent=</span><span class="s">"{'eventCategory': 'Test Category', 'eventAction': 'Test Action', 'eventLabel': 'Test Label'}"</span><span class="nt">&gt;</span>Custom link with tracking<span class="nt">&lt;/a&gt;</span>
</code></pre></div></div>

<p>Remark: If the dialog shows an error message or doesn’t load correctly, clear your cache and reload the page.</p>

<h2 id="conclusion">Conclusion</h2>
<p>Let’s wrap it up by summarizing the steps to extend the default rich text editor with your custom button:</p>
<ul>
  <li>Create your custom DialogForm.cs file</li>
  <li>Create the control for your custom dialog</li>
  <li>Extend the JavaScript file with your control</li>
  <li>Add a “Html Editor Button” to the toolbar in the core database</li>
</ul>

<p>As you can see, customizing the rich text editor with your own dialog is relatively straightforward and for your content editor, it is an important step to enhance the content editing experience.</p>]]></content><author><name>rjung</name></author><category term="Sitecore" /><category term="Sitecore" /><category term="Sitecore 10.3" /><category term="Sitecore XP" /><category term="Extend link" /><category term="Sitecore richt text editor" /><summary type="html"><![CDATA[]]></summary></entry></feed>