Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      The Value-Driven AI Roadmap

      September 9, 2025

      This week in AI updates: Mistral’s new Le Chat features, ChatGPT updates, and more (September 5, 2025)

      September 6, 2025

      Designing For TV: Principles, Patterns And Practical Guidance (Part 2)

      September 5, 2025

      Neo4j introduces new graph architecture that allows operational and analytics workloads to be run together

      September 5, 2025

      ‘Job Hugging’ Trend Emerges as Workers Confront AI Uncertainty

      September 8, 2025

      Distribution Release: MocaccinoOS 25.09

      September 8, 2025

      Composition in CSS

      September 8, 2025

      DataCrunch raises €55M to boost EU AI sovereignty with green cloud infrastructure

      September 8, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Finally, safe array methods in JavaScript

      September 9, 2025
      Recent

      Finally, safe array methods in JavaScript

      September 9, 2025

      Perficient Interviewed for Forrester Report on AI’s Transformative Role in DXPs

      September 9, 2025

      Perficient’s “What If? So What?” Podcast Wins Gold Stevie® Award for Technology Podcast

      September 9, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Distribution Release: MocaccinoOS 25.09

      September 8, 2025
      Recent

      Distribution Release: MocaccinoOS 25.09

      September 8, 2025

      Speed Isn’t Everything When Buying SSDs – Here’s What Really Matters!

      September 8, 2025

      14 Themes for Beautifying Your Ghostty Terminal

      September 8, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Lessons from the Front: Configurable Workflow Rules for New Items in XM Cloud

    Lessons from the Front: Configurable Workflow Rules for New Items in XM Cloud

    July 25, 2025

    Intro 📖

    In this post I’d like to share a workflow “attacher” implementation I built on a recent Sitecore XM Cloud project. The solution attaches workflows to new items based on a configurable list of template and path rules. It was fun to build and ended up involving a couple Sitecore development mechanisms I hadn’t used in a while:

    • The venerable Sitecore configuration factory to declaratively define runtime objects
    • The newer pipeline processor invoked when items are created from a template: addFromTemplate

    This implementation provided our client with a semi-extensible way of attaching workflows to items without writing any additional code themselves. “But, Nick, Sitecore already supports attaching workflows to items, why write any custom code to do this?” Great question 😉.

    The Problem 🙅‍♂️

    The go-to method of attaching workflows to new items in Sitecore is to set the workflow fields on Standard Values for the template(s) in question. For example, on a Page template in a headless site called Contoso (/sitecore/templates/Project/Contoso/Page/__Standard Values). This is documented in the Accelerate Cookbook for XM Cloud here. Each time a new page is created using that template, the workflow is associated to (and is usually started on) the new page.

    Setting workflow Standard Values fields on site-specific or otherwise custom templates is one thing, but what about on out-of-the-box (OOTB) templates like media templates? On this particular project, there was a requirement to attach a custom workflow to any new versioned media items.

    I didn’t want to edit Standard Values on any of the media templates that ship with Sitecore. However unlikely, those templates could change in a future Sitecore version. Also, worrying about configuring Sitecore to treat any new, custom media templates in the same way as the OOTB media templates just felt like a bridge too far.

    I thought it would be better to “listen” for new media items being created and then check to see if a workflow should be attached to the new item or not. And, ideally, it would be configurable and would allow the client’s technical resources to enumerate one or more workflow “attachments,” each independently configurable to point to a specific workflow, one or more templates, and one or more paths.

    The Solution ✅

    🛑 Disclaimer: Okay, real talk for a second. Before I describe the solution, broadly speaking, developers should try to avoid customizing the XM Cloud content management (CM) instance altogether. This is briefly mentioned in the Accelerate Cookbook for XM Cloud here. The less custom code deployed to the CM the better; that means fewer points of failure, better performance, more expedient support ticket resolution, etc. As Robert Galanakis once wrote, “The fastest code is the code which does not run. The code easiest to maintain is the code that was never written.”

    With that out of the way, in the real world of enterprise XM Cloud solutions, you may find yourself building customizations. In the case of this project, I didn’t want to commit to the added overhead and complexity of building out custom media templates, wiring them up in Sitecore, etc., so I instead built a configurable workflow attachment mechanism to allow technical resources to enumerate which workflows should start on which items based on the item’s template and some path filters.

    addFromTemplate Pipeline Processor 🧑‍🔧

    Assuming it’s enabled and not otherwise bypassed, the addFromTemplate pipeline processor is invoked when an item is created using a template, regardless of where or how the item was created. For example:

    • When a new page is created in the Content Editor
    • When a new data source item is created using Sitecore PowerShell Extensions
    • When an item is created as the result of a branch template
    • When a new media item is uploaded to the media library
    • When several media items are uploaded to the media library at the same time
    • …etc.

    In years past, the item:added event handler may have been used in similar situations; however, it isn’t as robust and doesn’t fire as consistently given all the different ways an item can be created in Sitecore.

    To implement an addFromTemplate pipeline processor, developers implement a class inheriting from AddFromTemplateProcessor (via Sitecore.Pipelines.ItemProvider.AddFromTemplate). Here’s the implementation for the workflow attacher:

    using Contoso.Platform.Extensions;
    using Sitecore.Pipelines.ItemProvider.AddFromTemplate;
    ...
    
    namespace Contoso.Platform.Workflow
    {
        public class AddFromTemplateGenericWorkflowAttacher : AddFromTemplateProcessor
        {
            private List<WorkflowAttachment> WorkflowAttachments = new List<WorkflowAttachment>();
    
            public void AddWorkflowAttachment(XmlNode node)
            {
                var attachment = new WorkflowAttachment(node);
                if (attachment != null)
                {
                    WorkflowAttachments.Add(attachment);
                }
            }
    
            public override void Process(AddFromTemplateArgs args)
            {
                try
                {
                    Assert.ArgumentNotNull(args, nameof(args));
    
                    if (args.Aborted || args.Destination.Database.Name != "master")
                    {
                        return;
                    }
    
                    // default to previously resolved item, if available
                    Item newItem = args.ProcessorItem?.InnerItem;
    
                    // use previously resolved item, if available
                    if (newItem == null)
                    {
                        try
                        {
                            Assert.IsNotNull(args.FallbackProvider, "Fallback provider is null");
    
                            // use the "base case" (the default implementation) to create the item
                            newItem = args.FallbackProvider.AddFromTemplate(args.ItemName, args.TemplateId, args.Destination, args.NewId);
                            if (newItem == null)
                            {
                                return;
                            }
    
                            // set the newly created item as the result and downstream processor item
                            args.ProcessorItem = args.Result = newItem;
                        }
                        catch (Exception ex)
                        {
                            Log.Error($"{nameof(AddFromTemplateGenericWorkflowAttacher)} failed. Removing partially created item, if it exists", ex, this);
    
                            var item = args.Destination.Database.GetItem(args.NewId);
                            item?.Delete();
    
                            throw;
                        }
                    }
    
                    // iterate through the configured workflow attachments
                    foreach (var workflowAttachment in WorkflowAttachments)
                    {
                        if (workflowAttachment.ShouldAttachToItem(newItem))
                        {
                            AttachAndStartWorkflow(newItem, workflowAttachment.WorkflowId);
                            // an item can only be in one workflow at a time
                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.Error($"There was a processing error in {nameof(AddFromTemplateGenericWorkflowAttacher)}.", ex, this);
                }
            }
    
            private void AttachAndStartWorkflow(Item item, string workflowId)
            {
                item.Editing.BeginEdit();
    
                // set default workflow
                item.Fields[Sitecore.FieldIDs.DefaultWorkflow].Value = workflowId;
                // set workflow
                item.Fields[Sitecore.FieldIDs.Workflow].Value = workflowId;
                // start workflow
                var workflow = item.Database.WorkflowProvider.GetWorkflow(workflowId);
                workflow.Start(item);
    
                item.Editing.EndEdit();
            }
        }
    }

    Notes:

    • The WorkflowAttachments member variable stores the list of workflow definitions (defined in configuration).
    • The AddWorkflowAttachment() method is invoked by the Sitecore configuration factory to add items to the WorkflowAttachments list.
    • Assuming the creation of the new item wasn’t aborted, the destination database is master, and the new item is not null, the processor iterates over the list of workflow attachments and, if the ShouldAttachToItem() extension method returns true, the AttachAndStartWorkflow() method is called.
    • The AttachAndStartWorkflow() method associates the workflow to the new item and starts the workflow on the item.
    • Only the first matching workflow attachment is considered—an item can only be in one (1) workflow at a time.

    The implementation of the ShouldAttachToItem() extension method is as follows:

    ...
    
    namespace Contoso.Platform
    {
        public static class Extensions
        {
            ...
            public static bool ShouldAttachToItem(this WorkflowAttachment workflowAttachment, Item item)
            {
                if (item == null)
                    return false;
    
                // check exclusion filters
                if (workflowAttachment.PathExclusionFilters.Any(exclusionFilter => item.Paths.FullPath.IndexOf(exclusionFilter, StringComparison.OrdinalIgnoreCase) > -1))
                    return false;
    
                // check inclusion filters
                if (workflowAttachment.PathFilters.Any() &&
                    !workflowAttachment.PathFilters.Any(includeFilter => item.Paths.FullPath.StartsWith(includeFilter, StringComparison.OrdinalIgnoreCase)))
                    return false;
    
                var newItemTemplate = TemplateManager.GetTemplate(item);
    
                // check for template match or template inheritance
                return workflowAttachment.TemplateIds.Any(id => ID.TryParse(id, out ID templateId)
                    && (templateId.Equals(item.TemplateID)
                        || newItemTemplate.InheritsFrom(templateId)));
            }
        }
        ...
    }

    Notes:

    • This extension method determines if the workflow should be attached to the new item or not based on the criteria in the workflow attachment object.
    • The method evaluates the path exclusion filters, path inclusion filters, and template ID matching or inheritance (in that order) to determine if the workflow should be attached to the item.

    Here’s the WorkflowAttachment POCO that defines the workflow attachment object and facilitates the Sitecore configuration factory’s initialization of objects:

    using Sitecore.Diagnostics;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml;
    
    namespace Contoso.Platform.Workflow
    {
        public class WorkflowAttachment
        {
            public string WorkflowId { get; set; }
    
            public List<string> TemplateIds { get; set; }
    
            public List<string> PathFilters { get; set; }
    
            public List<string> PathExclusionFilters { get; set; }
    
            public WorkflowAttachment(XmlNode workflowAttachmentNode)
            {
                TemplateIds = new List<string>();
                PathFilters = new List<string>();
                PathExclusionFilters = new List<string>();
    
                if (workflowAttachmentNode == null)
                    throw new ArgumentNullException(nameof(workflowAttachmentNode),
                        $"The workflow attachment configuration node is null; unable to create {nameof(WorkflowAttachment)} object.");
    
                // parse nodes
                foreach (XmlNode childNode in workflowAttachmentNode.ChildNodes)
                {
                    if (childNode.NodeType != XmlNodeType.Comment)
                        ParseNode(childNode);
                }
    
                // validate
                Assert.IsFalse(string.IsNullOrWhiteSpace(WorkflowId), $"{nameof(WorkflowId)} must not be null or whitespace.");
                Assert.IsTrue(TemplateIds.Any(), "The workflow attachment must enumerate at least one (1) template ID.");
            }
    
            private void ParseNode(XmlNode node)
            {
                switch (node.LocalName)
                {
                    case "workflowId":
                        WorkflowId = node.InnerText;
                        break;
                    case "templateIds":
                        foreach (XmlNode childNode in node.ChildNodes)
                        {
                            if (childNode.NodeType != XmlNodeType.Comment)
                                TemplateIds.Add(childNode.InnerText);
                        }
                        break;
                    case "pathFilters":
                        foreach (XmlNode childNode in node.ChildNodes)
                        {
                            if (childNode.NodeType != XmlNodeType.Comment)
                                PathFilters.Add(childNode.InnerText);
                        }
                        break;
                    case "pathExclusionFilters":
                        foreach (XmlNode childNode in node.ChildNodes)
                        {
                            if (childNode.NodeType != XmlNodeType.Comment)
                                PathExclusionFilters.Add(childNode.InnerText);
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }

    Configuration ⚙

    The following patch configuration file is defined to A. wire-up the addFromTemplate pipeline processor and B. describe the various workflow attachments. In the sample file below, for brevity, there’s only one (1) attachment defined, but multiple attachments are supported.

    <configuration>
      <sitecore>
      ...
      <pipelines>
        <group name="itemProvider" groupName="itemProvider">
          <pipelines>
            <addFromTemplate>
              <processor
                type="Contoso.Platform.Workflow.AddFromTemplateGenericWorkflowAttacher, Contoso.Platform"
                mode="on">
                <!-- Contoso Media Workflow attachment for versioned media items and media folders -->
                <workflowAttachmentDefinition hint="raw:AddWorkflowAttachment">
                  <workflowAttachment>
                    <!-- /sitecore/system/Workflows/Contoso Media Workflow -->
                    <workflowId>{88839366-409A-4E57-86A4-167150ED5559}</workflowId>
                    <templateIds>
                      <!-- /sitecore/templates/System/Media/Versioned/File -->
                      <templateId>{611933AC-CE0C-4DDC-9683-F830232DB150}</templateId>
                      <!-- /sitecore/templates/System/Media/Media folder -->
                      <templateId>{FE5DD826-48C6-436D-B87A-7C4210C7413B}</templateId>
                    </templateIds>
                    <pathFilters>
                      <!-- Contoso Media Library Folder -->
                      <pathFilter>/sitecore/media library/Project/Contoso</pathFilter>
                    </pathFilters>
                    <pathExclusionFilters>
                      <pathExclusionFilter>/sitecore/media library/System</pathExclusionFilter>
                      <pathExclusionFilter>/Sitemap</pathExclusionFilter>
                      <pathExclusionFilter>/Sitemaps</pathExclusionFilter>
                      <pathExclusionFilter>/System</pathExclusionFilter>
                      <pathExclusionFilter>/_System</pathExclusionFilter>
                    </pathExclusionFilters>
                  </workflowAttachment>
                </workflowAttachmentDefinition>
                ...
              </processor>
            </addFromTemplate>
          </pipelines>
        </group>
      </pipelines>
      ...
      </sitecore>
    </configuration>

    Notes:

    • N number of <workflowAttachmentDefinition> elements can be defined.
    • Only one (1) <workflowId> should be defined per attachment.
    • The IDs listed within the <templateIds> element are the templates the new item must either be based on or inherit from.
    • The <pathFilters> element enumerates the paths under which the workflow attachment should apply. If the new item’s path is outside of any of the paths listed, then the workflow is not attached. This element can be omitted to forgo the path inclusion check.
    • The <pathExclusionFilters> element enumerates the paths under which the workflow attachment should not apply. If the new item’s path contains any of these paths, then the workflow is not attached. This element can be omitted to forgo the path exclusion check. This filtering is useful to ignore new items under certain paths, e.g., under the Sitemap or Thumbnails media folders, both of which are media folders controlled by Sitecore.

    Closing Thoughts ☁

    While certainly not a one-size-fits-all solution, this approach was a good fit for this particular project considering the requirements and a general reticence for modifying Standard Values on OOTB Sitecore templates. Here are some pros and cons for this solution:

    Pros ✅

    • Provides a semi-extensible, configuration-based way to start workflows on new items.
    • Adding, updating, or removing a workflow attachment requires a configuration change but not code change.
    • Allows for a template ID match or inheritance for more flexibility.
    • Allows for path inclusion and exclusion filtering for more granular control over where in the content tree the workflow attachment should (or should not) apply.

    Cons ❌

    • Deploying custom server-side code to the XM Cloud CM instance isn’t great.
    • Arguably, creating custom templates inheriting from the OOTB templates in order to attach the workflows was the “more correct” play.
    • A deployment to change a configuration file could still require a code deployment—many (most?) pipelines don’t separate the two. If configuration changes are deployed, then so is the code (which, of course, necessitates additional testing).

    Takeaways:

    • If you’re building an XM Cloud solution, do your best to avoid (or at least minimize) customizations to the CM.
    • If you need to attach workflows to specific project templates or custom templates, do so via Standard Values (and serialize the changes)—don’t bother with custom C# code.
    • If, for whatever reason, you need to resort to a custom solution, consider this one (or something like it).
    • Of course, this solution can be improved; to list a few possible improvements:
      • Pushing the configuration files into Sitecore to allow content authors to manage workflow attachment definitions. This would require a permissions pass and some governance to help prevent abuse and/or misconfigurations.
      • Add support to conditionally start the workflow; at present, the workflow always starts on new items.
      • Add logic to protect against workflow clobbering if, for whatever reason, the new item already has a workflow attached to it.
      • Improve path matching when applying the path inclusion and exclusion filters.
      • Logging improvements.

    Thanks for the read! 🙏

    Resources 📚

    • https://sitecore.stackexchange.com/questions/523/execute-custom-logic-whenever-an-item-is-added-from-a-particular-branch-template
    • https://sitecore-community.github.io/docs/documentation/Sitecore%20Fundamentals/Sitecore%20Configuration%20Factory
    • https://developers.sitecore.com/learn/accelerate/xm-cloud

    Source: Read More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleSecuring Linux: Steady Momentum in AppArmor and SELinux Uptake
    Next Article The Intersection of Agile and Accessibility – Accessibility Testing in Continuous Integration

    Related Posts

    Development

    Leading the QA Charge: Multi-Agent Systems Redefining Automation

    September 9, 2025
    Development

    Stop Duct-Taping AI Agents Together: Meet SmythOS

    September 9, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Rematch Not Working on PC: 7 Simple Fixes for Windows

    Operating Systems

    CVE-2025-4200 – Zagg – Electronics & Accessories WooCommerce WordPress Theme Local File Inclusion Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    KontextAIPrecision AI Image and Video Editing and Generation Harness the power of Flux Kontext AI technology.

    Web Development

    Gears of War: Reloaded is a love letter to the original — flaws and all. It looks great, plays well, and plays it safe with a few modern upgrades.

    News & Updates

    Highlights

    CVE-2025-1772 – CVE-2021-22222: Apache Struts Deserialization RCE

    July 5, 2025

    CVE ID : CVE-2025-1772

    Published : July 5, 2025, 11:15 p.m. | 2 hours, 53 minutes ago

    Description : Rejected reason: This CVE ID has been rejected or withdrawn by its CVE Numbering Authority.

    Severity: 0.0 | NA

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    CVE-2025-7844 – TPM 2.0 Stack Buffer Overflow

    August 4, 2025

    Sensitive Gangsta Merch

    July 19, 2025

    The Silent Architect: How Data Governance Will Decide the Winners and Losers in the AI World

    April 28, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.