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

      Designing Better UX For Left-Handed People

      July 25, 2025

      This week in AI dev tools: Gemini 2.5 Flash-Lite, GitLab Duo Agent Platform beta, and more (July 25, 2025)

      July 25, 2025

      Tenable updates Vulnerability Priority Rating scoring method to flag fewer vulnerabilities as critical

      July 24, 2025

      Google adds updated workspace templates in Firebase Studio that leverage new Agent mode

      July 24, 2025

      Trump’s AI plan says a lot about open source – but here’s what it leaves out

      July 25, 2025

      Google’s new Search mode puts classic results back on top – how to access it

      July 25, 2025

      These AR swim goggles I tested have all the relevant metrics (and no subscription)

      July 25, 2025

      Google’s new AI tool Opal turns prompts into apps, no coding required

      July 25, 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

      Laravel Scoped Route Binding for Nested Resource Management

      July 25, 2025
      Recent

      Laravel Scoped Route Binding for Nested Resource Management

      July 25, 2025

      Add Reactions Functionality to Your App With Laravel Reactions

      July 25, 2025

      saasykit/laravel-open-graphy

      July 25, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Sam Altman won’t trust ChatGPT with his “medical fate” unless a doctor is involved — “Maybe I’m a dinosaur here”

      July 25, 2025
      Recent

      Sam Altman won’t trust ChatGPT with his “medical fate” unless a doctor is involved — “Maybe I’m a dinosaur here”

      July 25, 2025

      “It deleted our production database without permission”: Bill Gates called it — coding is too complex to replace software engineers with AI

      July 25, 2025

      Top 6 new features and changes coming to Windows 11 in August 2025 — from AI agents to redesigned BSOD screens

      July 25, 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

    Laravel Scoped Route Binding for Nested Resource Management

    July 25, 2025
    Development

    Add Reactions Functionality to Your App With Laravel Reactions

    July 25, 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

    CVE-2025-0467 – VMware GPU Firmware Memory Corruption

    Common Vulnerabilities and Exposures (CVEs)

    An innovative financial services leader finds the right AI solution: Robinhood and Amazon Nova

    Machine Learning

    CVE-2025-47292 – Cap Collectif Remote Code Execution Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Four Critical RCE Flaws Found in Grafana Plugins via Chromium: Patch Now!

    Security

    Highlights

    Development

    GitHub Copilot Guide: Boosting Software Productivity with AI

    May 27, 2025

    Software development has always been about solving complex problems, but the speed at which we’re now expected to deliver solutions is faster than ever. With agile methodologies and DevOps practices becoming the norm, teams are under constant pressure to ship high-quality code in increasingly shorter cycles. This demand for speed and quality places immense pressure
    The post GitHub Copilot Guide: Boosting Software Productivity with AI appeared first on Codoid.

    5 ways to turn AI’s time-saving magic into your productivity superpower

    April 18, 2025

    CVE-2025-54309 – CrushFTP Remote Admin Access Vulnerability

    July 18, 2025

    ThinkPRM: A Generative Process Reward Models for Scalable Reasoning Verification

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

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