Optimizing Sitecore Branch Templates for Seamless Page Creation Without Datasource Path Modification

Hello everyone, in this blog, I’ll address a common challenge I’ve faced when using branch templates to create item structures.

I noticed that when creating a branch structure and assigning a local datasource item, the datasource does not update automatically during item creation—the local datasource continues to reference the branch’s local items. In this blog, we’ll explore how to use a Sitecore pipeline to automatically update the datasource path.

This solution will work with all version of Sitecore even if it is XMC, Headless or XP. I have tried this on XMC and it works absolutely fine

Let's jump to solution. We will override AddFromTemplateProcessor and extend it to add our logic. Let's create AddFromBranchPresetProcessor which extends AddFromTemplateProcessor. Check below code.

       
using Sitecore;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Layouts;
using Sitecore.Pipelines.ItemProvider.AddFromTemplate;
using System;
using System.Linq;

namespace Foundation.Core.Pipelines.AddFromTemplate
{
    public class AddFromBranchPresetProcessor : AddFromTemplateProcessor
    {
        public string DefaultDeviceId {  get; set; }

        public override void Process(AddFromTemplateArgs args)
        {
            try
            {
                Assert.ArgumentNotNull(args, nameof(args));

                var templateItem = args.Destination.Database.GetItem(args.TemplateId);

                Assert.IsNotNull(templateItem, "Template did not exist!");

                if (templateItem.TemplateID != TemplateIDs.BranchTemplate)
                {
                    return;
                }

                Assert.HasAccess((args.Destination.Access.CanCreate() ? 1 : 0) != 0, "AddFromTemplate - Add access required (destination: {0}, template: {1})", args.Destination.ID, args.TemplateId);

                // Create the branch template instance
                var newItem = args.Destination.Database.Engines.DataEngine.AddFromTemplate(args.ItemName, args.TemplateId, args.Destination, args.NewId);

                if (newItem == null) { return; }

                // find all rendering data sources on the branch root item that point to an item under the branch template,
                // and repoint them to the equivalent subitem under the branch instance
                this.RewriteBranchRenderingDataSources(newItem, templateItem, newItem.Paths.FullPath);

                args.Result = newItem;
            }
            catch(Exception ex)
            {
                Sitecore.Diagnostics.Log.Error($"the execption:  {ex.Message.ToString()}", this);
            }
            
        }

        protected virtual void RewriteBranchRenderingDataSources(Item item, BranchItem branchTemplateItem, string branchRoot)
        {
            LayoutField layoutField = item.Fields[FieldIDs.LayoutField];
            if(string.IsNullOrEmpty(layoutField?.Value))
            {
                return;
            }
            var layoutDefinition = LayoutDefinition.Parse(layoutField.Value);
            var deviceDefinition = layoutDefinition.GetDevice(this.DefaultDeviceId);

            string branchTemplateRoot = branchTemplateItem.InnerItem.Paths.FullPath + "/$name";

            foreach (RenderingDefinition rendering in deviceDefinition.Renderings)
            {
                var dsId = rendering.Datasource;

                if (!string.IsNullOrEmpty(dsId))
                {
                    var dsItem = item.Database.GetItem(dsId);

                    if (dsItem != null)
                    {
                        if (dsItem.Paths.FullPath.Contains(branchTemplateRoot))
                        {
                            var newDsItem = item.Database.GetItem(dsItem.Paths.FullPath.Replace(branchTemplateRoot, branchRoot));
                            rendering.Datasource = newDsItem.ID.ToString();
                        }
                    }
                }
            }

            item.Editing.BeginEdit();
            item[FieldIDs.LayoutField] = layoutDefinition.ToXml();
            item.Editing.EndEdit();
        }
    }
}       
	   

Now let's add this processor as patch file.

       
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"
  xmlns:role="http://www.sitecore.net/xmlconfig/role/">
	<sitecore>
		<pipelines>
			<group name="itemProvider" groupName="itemProvider">
				<pipelines>
					<addFromTemplate>
						<processor type="Foundation.Core.Pipelines.AddFromTemplate.AddFromBranchPresetProcessor, ALJ.Foundation.Core">
							<DefaultDeviceId>{FE5D7FDF-89C0-4D99-9AA3-B5FBD009C9F3}</DefaultDeviceId>
						</processor>
					</addFromTemplate>
				</pipelines>
			</group>
		</pipelines>
	</sitecore>
</configuration>       
	   

The code is simple. RewriteBranchRenderingDataSources() is responsible for updating the datasource location path and takes the newly generated item as a parameter for its operation. Careful null handling is essential, since both Sitecore SXA and XM Cloud use branch templates to build the entire site structure. Occasionally, items without a layout may also pass through this processor, which can lead to exceptions because not all items created during site structure setup are guaranteed to have a layout. Therefore, this processor should only be applied when a layout is present.

Thank you for reading and keep learning.

You can check my other blogs too if interested. Blog Website

Comments

Popular posts from this blog

Sitecore XM Cloud Form Integration with Azure Function as Webhook

Sitecore 10.2 Installation using Windows PowerShell

Automate RSS Feed to Sitecore XM Cloud: Logic App, Next.js API & Authoring API Integration