Sitecore Basics - Multisite dynamic insert options

When you have a Sitecore environment for multiple websites, you often come across the need to have the same templates but different insert options.
Maybe 1 site allows you to create certain child items and another one doesn't.

So Sitecore offers you the ability to configure insert options in several ways!
Choose what suits your project/environment the best.

Please note that this blog post is focused on a non-SXA project. SXA offers different insert rules which may be tailored better for what you want to achieve.

In my scenario I wanted to create custom configuration items in the Sitecore Tree and allow each Sitecore site to reference a configuration which would apply insert options to certain templates that are defined in code.

I'll go through what I created in Sitecore, show an example for a website and explain the implementation of the custom pipeline.

The Sitecore basics

Insert options are typically defined on the Standard Values of a template. They can also be defined on Branch templates which will copy the defined insert options to the item created from the branch template.

In summary, the insert options can be controlled with:

  • defining insert options on the standard values
  • defining insert options on a branch template
  • defining insert options on an item itself
  • creating insert options rules
  • implementing an insert option pipeline

We'll look at the last option in this blog post here!

Creating the Sitecore items

Templates

The first 2 templates are for creating the different configurations that can be applied to a site. Create items from the Insert Option Configuration template in a shared location.

In my case I have 3 fields on the Insert Option Configuration template:

  • Site Configuration Options
  • Page Options
  • Page Configuration Options

Define your own fields here. I've chosen a Treelist type to allow the editors to select which templates/branches they can include.

The Insert Options Configuration Folder will hold Insert Option Configurations. So make sure that you update the insert option on the standard value of the folder to the Insert Options Configuration item:

The last template is the Site Insert Option Configuration which - when created underneath a website - will allow you to select the created configuration from the shared section in the tree.

Creating the Sitecore items

First we create our set of Insert Options Configurations.

For example: I defined 2 different configurations. And on the Site Configuration Options field, I selected the necessary templates that an editor is allowed to create underneath the Site Configuration.

Then on the Site Configuration of a specific Site, I created the configuration to select the right item. You'll have to do this for each site where you want to apply your custom insert options.

Custom insert options pipeline

Ok so we have our items in Sitecore but how do we tie things together? How do we actually get those insert options to apply?

We'll need to patch in a custom processor. It is best to add it after the default Sitecore pipeline GetItemMasters.
This offers you the ability to add your insert options on top of the ones already available - and lets you check if the insert option isn't alreay available!

Don't worry about the security of the insert options. Sitecore has a custom pipeline that runs after this to validate that the user using the insert options actually has access to template.

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <processors>
      <uiGetMasters argsType="Sitecore.Pipelines.GetMasters.GetMastersArgs">
        <processor type="Headless.Processors.DynamicHeadlessInsertOptionsProcessor" patch:after = "processor[@type='Sitecore.Pipelines.GetMasters.GetItemMasters,Sitecore.Kernel']"/>
      </uiGetMasters>
    </processors>
  </sitecore>
</configuration>

The processor itself receives a GetMastersArgs object. args.Item will give you the Sitecore Item that the user currently has selected or right clicked on.
Selected is for when the user wants to use the insert options in the Sitecore menu.
Right clicked is for when the user wants to use the popup menu to insert an item.

The first check I'm doing is validating if I'm underneath a site that I want to support this functionality for. I'm using an extension method for this: GetLastAncestorByTemplateId. You can traverse the parents however you want.

This is also needed since I need to get to the configuration item for that specific website. We're using Glass mappert in this project. So it was easy to map the headless Site item to the interface IHeadlessSite. Which allowed me to get the SiteInsertOptionConfigurations.

This is a list, we can define multiple insert options configurations per website, they will all apply.

Then we check if the current item descends from a specific template id, if so we'll call AddToMasters. This method will loop the insert options to add and insert them if they aren't already present. Hence the SingleOrDefault check.

public class DynamicHeadlessInsertOptionsProcessor
{
    public void Process(Sitecore.Pipelines.GetMasters.GetMastersArgs args)
    {
        //Check if we are underneath a Headless Site, if not exit
        var site = args.Item.GetLastAncestorByTemplateId(new ID(TemplateIDs.HeadlessSite));
        var headlessSite = site.To<IHeadlessSite>();
        if (headlessSite?.SiteInsertOptionConfigurations == null)
            return;

        foreach (var insertOptions in headlessSite.SiteInsertOptionConfigurations.Where(x => x.InsertOptions != null))
        {            
            if (args.Item.DescendsFrom(new ID(TemplateIDs.BaseSiteConfiguration)))
                AddToMasters(args, insertOptions.InsertOptions.SiteConfigurationOptions);
            
            if (args.Item.DescendsFrom(new ID(TemplateIDs.BasePageConfiguration)))
                AddToMasters(args, insertOptions.InsertOptions.PageConfigurationOptions);
            
            if (args.Item.DescendsFrom(new ID(TemplateIDs.BasePage)))
                AddToMasters(args, insertOptions.InsertOptions.PageOptions);
        }
    }

    private void AddToMasters(GetMastersArgs args, IEnumerable<Item> insertOptions)
    {
        if (insertOptions == null)
            return;

        foreach (var option in insertOptions)
        {
            // only add if the list doesn't contain the item yet
            if (args.Masters.SingleOrDefault(x => x.ID.Equals(option.ID)) == null)
                args.Masters.Add(option);
        }
    }
}

The result

We now have a pretty dynamic system where we can define insert options per site which is what we wanted to achieve!

Additionally insert options defined on the standard values are extended with the insert option configuration templates. So you can define general insert options on the standard values which will be applied in every site. And site specific insert options can be controlled with the newly created configuration.

One of the drawbacks is that this pipeline runs every single time for every item that you select in the tree. Your CM environment could take a small performance hit. But the editors didn't notice anything major.

I'm sure this can be made even more dynamic. For example, now on the configuration item I've defined 3 specific fields for the editors (actually they are Site administrators) to make the selection of the insert options. You could create a system that links a specific template type to some insert options. Which will allow you an even more controlled way. But this would have been too much for my scenario.