<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://www.tag1.com/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tag1 Insights for Planet Drupal</title>
    <link>https://www.tag1.com/</link>
    <atom:link href="https://www.tag1.com/planet-drupal.xml" rel="self" type="application/rss+xml" />
    <description>Dive into expert insights, deep analysis and forward-looking perspectives from the minds shaping Drupal, AI and open-source innovation.</description>
    <lastBuildDate>Wed, 18 Mar 2026 00:00:00 GMT</lastBuildDate>
    <language>en</language>
    
    <item>
      <title>Preparing File Upload Secure Validator for Drupal 12 with AI</title>
      <link>https://www.tag1.com/blog/preparing-file-upload-secure-validator-drupal12/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
<p>At Tag1, we believe in proving AI within our own work before recommending it to clients. This post is part of our <a href="https://www.tag1.com/insights/?tag=AI#selected">AI Applied content series</a>, where team members share real stories of how they're using Artificial Intelligence and the insights and lessons they learn along the way. Here, <a href="https://www.tag1.com/team/#stefanos-petrakis">Stefanos Petrakis</a>, maintainer of the File Upload Secure Validator module, shows how he used AI to modernize a small but widely used Drupal security module and prepare it for Drupal 12.</p>
    </div>
</div>
</div>
<h2>From &quot;I'll Get to It&quot; to Done: Modernizing File Upload Secure Validator</h2>
<p>I’ve been meaning to clean up the <a href="https://www.drupal.org/project/file_upload_secure_validator" target="_blank" rel="noopener noreferrer">File Upload Secure Validator</a> project and get it ready for Drupal 12 for a while now. This small, focused module has been around for nearly a decade. Despite its simplicity, it continues to serve more than 10,000 reported sites, and adoption has only accelerated with the introduction of <a href="https://www.drupal.org/project/ai" target="_blank" rel="noopener noreferrer">Drupal AI</a>. With the help of Cline and Claude, I finally did a full overhaul of the codebase: switching to Drupal 11-only support, expanding the automated test suite, and positioning the project for Drupal 12.</p>
<p></p><figure id="fig1" class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/blog/weekly-drupal-project-usage.png" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/blog/weekly-drupal-project-usage.png" alt="Graph showing Drupal usage over time and reflecting an increase since the release of Drupal AI." />
</a>
<figcaption class="italic text-[var(--text-color)]">Figure 1: Weekly File Upload Secure Validator usage report, that reflects an increase in usage post Drupal AI release.</figcaption>
</figure><p></p>
<h2>A Decade-Old Module Meets Drupal 12</h2>
<p>This was the kind of maintenance work I kept putting off, the same feeling I get when I need to sit down and do my taxes. I knew the project needed cleanup and modernization, but I wanted a little push and some company in doing the work. The missing motivation and sense of camaraderie were, in many ways, the biggest challenges.</p>
<p>On top of that, I had a clear vision for how I wanted to extend the test suite, and I knew it would be time-consuming. Time, or the lack of it, was a major factor, especially for this kind of detailed, behind-the-scenes work on an open source module.</p>
<h2>Turning a Wish List Into a Working Plan</h2>
<p>To move things forward, I turned to Cline and Claude to help plan the future of the module. I started by writing down a list of &quot;wishes&quot; for the project: the improvements I wanted to see in the code, tests, and overall quality.</p>
<p>Cline turned that list into a detailed execution plan. It also generated questions about the approach, which led us into a few iterations before we settled on the final course of action. That planning process gave structure to the work and made it much easier to tackle in focused sessions.</p>
<p>All of the changes happened in the project's repository on the <a href="https://git.drupalcode.org/project/file_upload_secure_validator/-/tree/2.2.x?ref_type=heads" target="_blank" rel="noopener noreferrer">2.2.x branch</a>, with the final result released as <a href="https://www.drupal.org/project/file_upload_secure_validator" target="_blank" rel="noopener noreferrer">version 2.2.1 on Drupal.org</a>.</p>
<h2>From Red CI Pipelines to Green Across the Board</h2>
<p>Before this overhaul, the project had accumulated a number of issues:</p>
<ul>
<li>Multiple GitLab CI failures</li>
<li>Drupal 11.3+ deprecation warnings</li>
<li>Unit test failures (including static method and <code>TranslatableMarkup</code> issues)</li>
<li>Limited test coverage</li>
<li>383+ PHPCS violations</li>
<li>CSpell errors</li>
<li>PHPStan attribute errors</li>
</ul>
<p>After the overhaul, the picture looks very different:</p>
<ul>
<li>All GitLab CI tests passing</li>
<li>Zero deprecation warnings</li>
<li>23 tests with 164 assertions</li>
<li>0 PHPCS errors and 0 warnings</li>
<li>0 CSpell errors</li>
<li>0 PHPStan errors</li>
<li>100% CI quality checks passing</li>
</ul>
<p>This overhaul gave me the &quot;manpower&quot; and momentum I was missing to push the project forward. Just as importantly, it gave me confidence that I can continue supporting this module in the future.</p>
<h2>AI-Amplified Maintenance for Critical Dependencies</h2>
<p>Maintaining and supporting open source libraries can often become demanding because of limited time and resources. In client projects, dependencies on under-maintained open source projects can increase the effort required to maintain or upgrade the client's own platform.</p>
<p>Partners like  Cline and Claude can change the game in an advantageous way. Such a change can help teams keep critical open source dependencies up to date, improve quality, and reduce risk without requiring a huge amount of extra human capacity.</p>
<div class="highlight-box">
<p>This post is part of Tag1’s <a href="https://www.tag1.com/insights/?tag=AI&type=blogPosts#selected">AI Applied content series</a>, where we share how we're using AI inside our own work before bringing it to clients. Our goal is to be transparent about what works, what doesn’t, and what we are still figuring out, so that together, we can build a more practical, responsible path for AI adoption.</p>
</div>
<p><em>Bring practical, proven AI adoption strategies to your organization, let's start a conversation! <a href="https://www.tag1.com/contact/">We'd love to hear from you.</a></em></p>
]]></description>
      <pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Stefanos Petrakis</dc:creator>
      <guid>https://www.tag1.com/blog/preparing-file-upload-secure-validator-drupal12/</guid>
    </item>
    
    
    <item>
      <title>It&#39;s 106 Miles to DrupalCon and We Got a Full Tank of Gas: Tag1 Is Headed to Chicago</title>
      <link>https://www.tag1.com/blog/drupalcon-chicago-2026/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<p>DrupalCon is coming back to Chicago, March 23 to 26, and Tag1 will be there as a Champion Plus sponsor with sessions, coffee, and <em>a lot</em> to talk about.</p>
<img loading="lazy" decoding="async" class="float-left mx-2" src="https://www.tag1.com/img/insights/content/drupalcon-chicago.png" alt="DrupalCon Chicago logo" width="150" height="162" />
<p>This year's conference coincides with Drupal's 25th anniversary, which feels like the right moment to take stock of where we've been and where the platform is headed. We've been building and contributing to Drupal since its earliest days, and the work the community is doing right now, particularly around AI integration, Workspaces, and modern developer tooling, is some of the most exciting we've seen.</p>
<p>We're bringing three sessions to DrupalCon Chicago this year, covering testing, enterprise content staging, and what happens when you point AI at a seriously complex migration.</p>
<h2>Drupal Test Traits: Learn by Example</h2>
<p><strong>Moshe Weitzman | Tuesday, March 24 at 9:00 AM | Salon A-1</strong></p>
<img loading="lazy" decoding="async" class="float-right" src="https://www.tag1.com/img/insights/content/moshe-drupalcon-chicago-2026.png" alt="Stylized portrait of Moshe Weitzman" width="175" height="219" />
<p>Moshe will kick off our session lineup with <a href="https://events.drupal.org/chicago2026/session/drupal-test-traits-learn-example" target="_blank" rel="noopener noreferrer">Drupal Test Traits (DTT)</a>, the open source testing framework he built that takes a fundamentally different approach to testing content-heavy Drupal sites. Rather than spinning up empty databases and populating them with mock data, DTT tests against your actual site content, so you're validating the things that actually matter to your editors and visitors. Expect practical examples drawn from Massachusetts' <a href="https://www.mass.gov/" target="_blank" rel="noopener noreferrer">mass.gov</a> testing suite.</p>
<h2>Workspaces Is Revolutionizing Drupal Core</h2>
<p><strong>Fabian Franz &amp; Peta Hoyes | Wednesday, March 25 at 3:00 PM | Salon A-4</strong></p>
<img loading="lazy" decoding="async" class="float-right" src="https://www.tag1.com/img/insights/content/fabian-peta-drupalcon-chicago-2026.png" alt="Stylized portraits of Fabian Franz and Peta Hoyes" width="360" height="219" />
<p>Fabian and Peta will present on <a href="https://events.drupal.org/chicago2026/session/workspaces-revolutionizing-drupal-core-unlock-true-enterprise-content" target="_blank" rel="noopener noreferrer">Workspaces</a>, one of Tag1's major contributions to Drupal core and the foundation for true enterprise content management in Drupal. If you've followed our recent work on the <a href="https://www.tag1.com/blog/tag1-joins-drupal-ai-initiative/">Drupal AI Initiative</a>, you already know that Workspaces is the governance layer we're extending to manage AI-driven changes, but the session goes well beyond AI, covering site-wide content staging, publishing workflows, rollbacks, translation management, A/B testing, and more. If your organization has outgrown single-item content moderation, this is the session for you.</p>
<h2>Migrate Smarter: How a Structured AI Methodology Unlocked a Complex Drupal 7 to 11 Migration</h2>
<p><strong>Marco Molinari | Wednesday, March 25 at 3:00 PM | Salon A-5</strong></p>
<img loading="lazy" decoding="async" class="float-right" src="https://www.tag1.com/img/insights/content/marco-drupalcon-chicago-2026.png" alt="Stylized portrait of Marco Molinari" width="175" height="219" />
<p>Yes, we know, we're double-booked. How awkward! Marco's talk on <a href="https://events.drupal.org/chicago2026/session/migrate-smarter-how-structured-ai-methodology-unlocked-complex-drupal-7-11" target="_blank" rel="noopener noreferrer">AI-assisted migration</a> covers how he used BMAD (Breakthrough Method for Agile AI-Driven Development) to migrate an Italian humor site (132K nodes, 100K newsletter subscribers, 18 custom modules, 93 contrib modules) from Drupal 7 to Drupal 11. What makes the session valuable is Marco's candid assessment of where AI-assisted development delivered real acceleration and where human expertise was still irreplaceable.</p>
<h2>Come See Us (and Try Something New)</h2>
<p>You'll find us at the big orange Tag1 booth on the expo floor with coffee in the morning, cool drinks in the afternoons, and comfy seating all day. Come recharge with us!</p>
<p>We're also offering a special sneak peek of Tag1's Drupal AI site builder. It can generate multi-page sites with real content, multilingual support, and layout customization, all using <a href="https://www.tag1.com/plus-suite/">Plus Suite</a>, <a href="https://www.tag1.com/workspaces/">Workspaces</a> and Drupal best practices. If you catch <a href="https://events.drupal.org/chicago2026/session/workspaces-revolutionizing-drupal-core-unlock-true-enterprise-content" target="_blank" rel="noopener noreferrer">Fabian and Peta's Workspaces session</a>, the demo is a natural next step to see that content staging infrastructure in action. And if you've read our recent post on <a href="https://www.tag1.com/blog/tag1-joins-drupal-ai-initiative/">joining the Drupal AI Initiative</a>, this is where that work comes to life. Come play with it, and tell us how you're using AI on your own sites.</p>
<p>On Tuesday evening, we'll have two tables at the <a href="https://events.drupal.org/chicago2026/drupal-25th-anniversary-gala" target="_blank" rel="noopener noreferrer">Drupal 25th Anniversary Gala</a>. We're looking forward to great food, a special anniversary beer, live music, and a chance to celebrate 25 years of Drupal with the people who built it. We'd love to see you there.</p>
<p>See you in Chicago!</p>
]]></description>
      <pubDate>Mon, 16 Mar 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Hank VanZile</dc:creator>
      <guid>https://www.tag1.com/blog/drupalcon-chicago-2026/</guid>
    </item>
    
    
    <item>
      <title>Building the Governance Layer: Tag1 Joins the Drupal AI Initiative</title>
      <link>https://www.tag1.com/blog/tag1-joins-drupal-ai-initiative/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
<p>Tag1 has joined the Drupal AI Initiative as a Certified Gold Partner and Maker. We’re starting by extending Workspaces, Drupal's enterprise content governance layer, so that AI agents operate under the same staging, review, and rollback framework as human editors. It’s the necessary foundation for everything that comes next.
</p></div><p></p>
</div>
</div>
<p>I'm proud to share that Tag1 has joined the <a href="https://new.drupal.org/ai/makers" target="_blank" rel="noopener">
Drupal AI Initiative </a> as a Certified Gold Partner and Maker, committing dedicated engineering time to help shape how AI works inside Drupal. We've been building Drupal since day one, and we're excited to be part of its most consequential evolution in years. We plan to contribute across multiple areas of the initiative, but we’re starting by bringing governance to AI-driven changes because that's the necessary foundation for all of it.</p>
<h2>The Governance Gap</h2>
<p>AI tools that generate content and alter site layout and configuration are getting more capable by the day. But capability without governance is a problem, especially for organizations with compliance requirements, editorial review processes, and production sites that can't afford surprises.</p>
<p>You wouldn't give a new team member free rein to change your website on their first day. It doesn't matter how talented they are. The same logic applies to AI agents: they need a sandbox to work in and a human to review before anything goes live. Letting agents make changes to your website without review, approval, or an audit trail isn't bold; it's reckless.</p>
<p>Tag1 contributed the solution to this problem to Drupal core. <a href="https://www.tag1.com/workspaces/">Workspaces</a> already gives teams the ability to stage content and configuration changes in isolation, review them, and publish when ready. It's the enterprise content governance layer of Drupal. Extending that same model to AI agents is the obvious move.</p>
<h2>What Tag1 is Building</h2>
<p>We’re starting by extending <a href="https://www.tag1.com/workspaces/">Workspaces</a> within the AI Initiative so that AI agents operate under the same governance framework that human editors do. The goal is that agents propose changes in isolated branches, humans review/approve, and every action is auditable and reversible. It's how we think AI should be applied everywhere: practical, tested, and built in the open.</p>
<p>We're starting with the underlying architecture, <a href="https://www.drupal.org/project/drupal/issues/3573462" target="_blank" rel="noopener"> defining the interface contracts</a> that ensure AI tools and other subsystems work correctly within Workspaces. This is the kind of unglamorous infrastructure work that makes everything else possible. If the contracts are right, any AI tool that follows them gets governed staging, review, and rollback for free. If they're wrong, nothing downstream works reliably.</p>
<p>The Tag1 team has been contributing to Drupal's architecture for over two decades. This work is a continuation of that commitment, getting the foundations right so the rest of the ecosystem can build with confidence.</p>
<h2>Governance for Every Drupal Site</h2>
<p>AI governance built into Drupal core means every site benefits. An agency building on Drupal CMS gets the same governance primitives as a Fortune 500 running a custom installation. The framework is designed to be agent-agnostic, so it works regardless of which AI tools an organization chooses to deploy. The governance layer doesn't care whether the change came from a chatbot or a custom automation. It cares that the change was reviewed and approved before it hit production.</p>
<p>Drupal has always been good at balancing rapid change with shared standards. The current wave of AI adoption is the latest version of that challenge, and open source collaboration is still the best way to get it right. Adding governance by extending Workspaces is just the start, and we’ll have more to share as the work progresses.</p>
<h2>Let's Talk</h2>
<p>If you're thinking about content governance in Drupal, or working through how to keep AI agents safe and auditable on your sites, <a href="https://tag1.com/contact">we'd love to hear from you</a>. We test everything in our own engineering and operations before we bring it to clients - that's our AI Applied philosophy - and the best solutions come from real conversations about real problems.</p>
]]></description>
      <pubDate>Thu, 12 Mar 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Hank VanZile</dc:creator>
      <guid>https://www.tag1.com/blog/tag1-joins-drupal-ai-initiative/</guid>
    </item>
    
    
    <item>
      <title>When Good Links Go Bad: How AI Cut Link Verification in Drupal’s Metatag Module from Hours to Minutes</title>
      <link>https://www.tag1.com/blog/link-verification-in-drupal-metatag-module-with-ai/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
<p>At Tag1, we believe in proving AI within our own work before recommending it to clients. This post is part of our <a href="https://www.tag1.com/insights/?tag=AI#selected">AI Applied content series</a>, where team members share real stories of how they're using AI and the insights and lessons they learn along the way. Here, <a href="https://www.tag1.com/team/#sammy-gituko">Sammy Gituko</a>, Software Developer, explores how AI supported improvements to the Metatag module by speeding up the discovery, verification, and replacement of broken documentation links across 30+ plugin files from hours to minutes. 
    </p></div>
</div>
</div>
<h2>A Small Fix That Wasn’t So Simple</h2>
<p>My first contribution to the <a href="https://www.drupal.org/project/metatag" target="_blank" rel="noopener noreferrer">Drupal Metatag module</a> started with what looked like a simple issue: fixing broken external documentation links. The task was logged as <a href="https://www.drupal.org/project/metatag/issues/3559765#comment-16372132" target="_blank" rel="noopener noreferrer">Issue #3559765 Fix broken links in the Meta tags section</a> , and at first, it seemed like a quick cleanup job. But the deeper I looked, the more it revealed about the fragility of open source documentation, and how AI can speed up the repetitive parts of technical contribution work while still requiring careful human judgment.</p>
<p>Broken links may not sound exciting, but they highlight a widespread challenge in open source maintenance. Documentation links age fast. Websites vanish. URL structures change without warning. And because the Metatag module contains dozens of plugin files pointing to different sources, even a small fix meant a lot of detail work.</p>
<h2>How AI Accelerated the Research Phase</h2>
<p>To begin, I scanned the <code>src/Plugin/metatag/Tag/</code> directory, which contains over 30 plugin files. This was where AI added real value, not by writing code, but by making the background research faster and more structured. I found six that had broken or unreliable links:</p>
<ul>
<li><strong>SetCookie.php:</strong> Link to <code>metatags.org</code> was returning 404</li>
<li><strong>Rating.php:</strong> Link to <code>metatags.org</code> was broken, though the RTA link worked</li>
<li><strong>Google.php:</strong> Google webmasters link returned 404</li>
<li><strong>Expires.php:</strong> Link to <code>csgnetwork.com</code> calculator had connection errors</li>
<li><strong>Standout.php:</strong> Google News documentation was broken (404)</li>
<li><strong>NewsKeywords.php:</strong> Google News documentation was broken (404)</li>
</ul>
<p>For each broken link, I needed to verify the issue, find a reliable replacement from an authoritative source, confirm it worked and was stable, then update it in the code without disrupting formatting or introducing linting errors.</p>
<h3>Finding Every Link</h3>
<p>Checking each file manually would have been tedious. Using AI, I generated efficient <code>grep</code> patterns for discovering URLs across the whole directory, like this suggestion that matched multiple URL styles: <code>https?://|www\.</code> That one line let me identify every external link across 30+ plugin files in minutes.</p>
<h3>Verifying What Was Broken</h3>
<p>The next challenge was figuring out which links actually worked. Instead of opening them one by one, AI recommended using a simple <code>curl</code> command to automatically test HTTP status codes:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> <span class="token parameter variable">-s</span> <span class="token parameter variable">-o</span> /dev/null <span class="token parameter variable">-w</span> <span class="token string">"%{http_code}"</span> <span class="token string">"https://example.com"</span></code></pre>
<p>This approach let me quickly categorize links as 200 (working), 404 (broken), or 301 (redirects), giving me a precise list of which needed attention.</p>
<h3>Finding Better Sources</h3>
<p>When replacing links, AI helped search for credible alternatives, suggesting sources like MDN, W3C, IETF, or Google Search Central. It also helped compare multiple options and recommend the best one.</p>
<h2>When AI Needed a Human Touch</h2>
<p>Despite its efficiency, AI couldn’t make every decision. Some choices depended on contextual understanding, deciding whether a replacement even made sense.</p>
<h3>Google News Documentation</h3>
<p>Two plugin files, <code>Standout.php</code> and <code>NewsKeywords.php</code>, both referenced Google News documentation that no longer existed. AI surfaced generic help pages, but none were relevant. Since the tags were already marked <code>@deprecated</code>, I chose to remove the links entirely. This was a judgment call informed by understanding the code’s context and the importance of avoiding misleading or obsolete references.</p>
<h3>Content Rating (RTA) Documentation</h3>
<p>In <code>Rating.php</code>, the existing RTA link technically worked but wasn’t reader-friendly. The AI proposed a few options, but ultimately, I picked Wikipedia’s page on content rating systems. It included the RTA standard, offered better context, and felt more accessible, a human decision about user experience, not just URL accuracy.</p>
<h3>What This Taught Me</h3>
<p>Several clear themes came out of this contribution:</p>
<ul>
<li>Third-party documentation is fragile. Even long-established sources like <code>metatags.org</code> and <code>csgnetwork.com</code> can disappear or restructure, breaking countless references.</li>
<li>Redirects can cause silent problems. A 301 redirect still “works,” but introduces slower load times and unnecessary chains. Direct links are cleaner.</li>
<li>AI excels at repetitive verification. Checking and verifying dozens of URLs took minutes instead of hours.</li>
<li>Context remains human. AI found replacements but couldn’t know when removing links made more sense or why accessibility might matter more than originality.</li>
<li>Authoritative sources reduce maintenance. Linking to MDN, IETF, or W3C means fewer headaches for future maintainers and reviewers.</li>
</ul>
<h3>The Outcome</h3>
<p>The <a href="https://www.drupal.org/files/issues/2025-12-05/3559765-fix-metatag-broken-links.patch" target="_blank" rel="noreferrer noopener">final patch</a> replaced or removed all broken documentation links:</p>
<p><strong>Fixed with authoritative replacements:</strong></p>
<ul>
<li><code>SetCookie</code>: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie" target="_blank" rel="noopener noreferrer">MDN documentation</a></li>
<li><code>Google</code>: Google Search Central</li>
<li><code>Expires</code>: IETF RFC 1123</li>
<li><code>Rating</code>: Wikipedia</li>
</ul>
<p><strong>Removed (no suitable or relevant replacements):</strong></p>
<ul>
<li><code>Standout </code>: Google News documentation removed</li>
<li><code>NewsKeywords</code>: Google News documentation removed</li>
</ul>
<p>The workflow became smoother, faster, and easier to reproduce. Using AI to handle repetitive validation tasks allowed me to focus my attention on decisions that actually required human reasoning.</p>
<h3>A Better Way Forward</h3>
<p>This contribution showed how AI can accelerate contribution workflows without replacing the thoughtful judgment that open source development depends on. By blending AI-assisted discovery with context-aware decision-making, contributors can move faster and still produce work that’s accurate, accessible, and maintainable.</p>
<p>Maintaining external documentation links might never be glamorous, but it’s a perfect example of how AI can make quality improvements faster and more sustainable, one verified link at a time.</p>
<div class="highlight-box">
<p>This post is part of Tag1’s <a href="https://www.tag1.com/insights/?tag=AI&type=blogPosts#selected">AI Applied content series</a>, where we share how we're using AI inside our own work before bringing it to clients. Our goal is to be transparent about what works, what doesn’t, and what we are still figuring out, so that together, we can build a more practical, responsible path for AI adoption.</p>
</div>
<p><em>Bring practical, proven AI adoption strategies to your organization, let's start a conversation! <a href="https://www.tag1.com/contact/">We'd love to hear from you.</a></em></p>
]]></description>
      <pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Sammy Gituko</dc:creator>
      <guid>https://www.tag1.com/blog/link-verification-in-drupal-metatag-module-with-ai/</guid>
    </item>
    
    
    <item>
      <title>What I Learned Using AI for Drupal Development</title>
      <link>https://www.tag1.com/blog/using-ai-for-drupal-development/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
<p>At Tag1, we believe in proving AI within our own work before recommending it to clients. This post is part of our <a href="https://www.tag1.com/insights/?tag=AI&type=blogPosts#selected">AI Applied content series</a>, where team members share real stories of how they're using Artificial Intelligence and the insights and lessons they learn along the way. Here, <a href="https://www.tag1.com/team/#ajit-shinde">Ajit Shinde</a> (Senior Drupal Developer), explores how AI supported his work on the contributed <strong>Trash module</strong>, including a complex taxonomy hierarchy challenge, and what he discovered about getting real value from these tools.</p>
<p></p></div><p></p>
</div>
</div>
<h2>How I Used AI to Tackle a Tricky Trash Module Challenge</h2>
<p>I have been using AI for work for quite some time now for various personal projects. I was inspired and empowered by Tag1's internal AI workshop, and I started using it extensively for both internal projects and a challenging contrib module assignment. What surprised me most was not AI magically solving hard problems, but how much time it could save on the repetitive parts of the work, especially around writing and iterating on tests. I want to share what I discovered because some of it genuinely surprised me.</p>
<h2>The Challenge of Adding Taxonomy Support to Trash</h2>
<p>Wanting to find an issue to tackle with AI for my own learning experience, I picked up <a href="https://www.drupal.org/project/trash/issues/3491947" target="_blank" rel="noopener noreferrer">issue #3491947</a> to add taxonomy support to the <a href="https://www.drupal.org/project/trash" target="_blank" rel="noopener noreferrer">Trash module</a>. The Trash module provides soft-delete functionality for Drupal entities and, once configured, it lets you delete entities temporarily and restore them later or purge them permanently if needed. This sounds straightforward enough, but taxonomy terms have a wrinkle that makes things interesting. When you delete a parent term, Drupal core deletes its children too, and this hierarchical deletion creates real challenges for a trash and restore workflow.</p>
<p>This issue had been open for a while with some interesting discussion about the right approach, and I had actually started working on it earlier as part of Tag1's sponsored open source development. I had created an initial MR that enabled trashing terms, but the hierarchy problem was still unsolved, and I wanted to see how AI would handle the complexity.</p>
<p>The module maintainer, Andrei Mateescu (<a href="https://www.drupal.org/u/amateescu" target="_blank" rel="noopener noreferrer">amateescu</a>), had suggested an elegant approach in the issue queue: since cascading deletes happen in the same request, all the deleted terms would have the same timestamp, so we could use that deleted timestamp to restore child terms along with their parent. It sounded promising, and I wanted to test whether it would actually work. So I started down the path, expecting that if I could lean on the shared deleted timestamp, I might avoid storing extra hierarchy data myself.</p>
<h2>First Steps: Planning with Cline</h2>
<p>I started with the Cline extension for VS Code in a “Plan” mode (Figure 1).</p>
<p></p><figure id="fig1" class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/content-feature/cline-prompt-window.png" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/content-feature/cline-prompt-window.png" alt="An image of the Cline prompt interface, highlighting an option below the prompt window indicating where and how users can access plan mode." />
</a>
<figcaption class="italic text-[var(--text-color)]">Figure 1: Cline Plan mode</figcaption>
</figure><p></p>
<p>Just for fun, I opened with a prompt asking to help me formulate a plan by going through the issue and studying the Trash module code. It could not access the issue directly, which is actually good from a security perspective, but Claude scanned my project and was able to find the Trash module in it, scanned through the code, and presented a decent understanding of how trashing works. I then explained the hierarchical deletion problem in detail, and the AI checked again and reached the same conclusion I had. When I shared Andreii’s suggestion about using the delete timestamp for restore, the AI presented a three-phase plan covering implementation, basic tests, and optional UX improvements.</p>
<p>At first glance it looked reasonable, but I spotted a major flaw immediately. The AI assumed that deleting a term would trash the parent and hard-delete the children, which is simply wrong. Once trash support is enabled, all terms being deleted move to trash including the children, and every bit of logic that followed was built on this broken assumption. This is exactly the kind of thing that makes working with AI both frustrating and fascinating. There were other misunderstandings (hallucinations) too. So, I decided to start fresh.</p>
<p>After a few trials, I used the following detailed prompt in a new session:</p>
<pre class="language-md"><code class="language-md">Assume a role of an expert Drupal developer developing a custom feature for the contributed Trash module on a Drupal site. We want the module to support "trashing" taxonomy terms, which is currently disabled.

Background/Current State:
The Trash module adds a deleted column to entity data tables to enable soft-delete/trash functionality.
Taxonomy term trashing is currently disabled via a code line that I can remove to re-enable it.
When a taxonomy term is deleted by default in Drupal, its children (if they have only one parent) are also deleted. This complicates restoration logic.

Goals:
Allow taxonomy terms to be trashed (not hard-deleted), leveraging the module’s infrastructure.
Enable correct restoration of trashed terms, including their child terms.

Technical Challenges:
Taxonomy Overview Page Tree Rendering:\n\nThe taxonomy overview page uses the buildTree function to load hierarchical term data via direct database queries. buildTree currently doesn’t account for the trashed (deleted) column, so trashed terms might break the vocabulary page (e.g., inaccessible, errors). Core updates to taxonomy API are not allowed (must solve without modifying Drupal core).

Restoration Logic for Hierarchies:
The existing trash implementation only restores single entities.
When a parent term is restored from trash, I need to also restore child terms that were trashed at the same time.
All terms trashed together share the same deleted value, which can be used to identify them as part of a single trash operation.

Please provide step-by-step guidance for:
Enabling trash support for taxonomy terms.
Where and how to safely re-enable support (removing the disabling line).
Modifying or extending buildTree (or its usage) without core changes to prevent errors from trashed terms.
Approach for filtering out trashed terms when rendering the tree.

If possible, suggest ways to hook, alter, or override tree loading, limited to contributed/custom code.
Implementing restoration logic for hierarchical terms.
How to batch-restore child terms if their parent is restored from trash.
Using the shared deleted value (timestamp/etc.) to identify child terms involved in the same operation.
Any Drupal hooks or architectural suggestions for this process.
</code></pre>
<p>I had to do several trial-and-error iterations to come up with this prompt. With that detailed context, the AI performed much better.</p>
<div class="callout-box">
<h3>Why This Prompt Worked</h3>
<p>This prompt was far more effective than earlier ones because it clearly defined the AI’s role, goals, and constraints from the start.</p>
<p>By identifying the AI as an <em>expert Drupal developer</em>, the prompt aligned its reasoning with real-world Drupal development patterns rather than generic guesses.</p>
<p>Defining clear end goals (enabling trash for taxonomy terms and restoring hierarchical relationships) kept the conversation focused, while listing technical challenges upfront, such as fixing <code>buildTree()</code> behavior without touching core and managing term restoration logic, provided essential guardrails against hallucinations.</p>
<p>Together, these details created a structured context that helped the AI generate more accurate, actionable output instead of speculative or incorrect code suggestions.</p>
</div>
<p>It enabled the trash support for taxonomy in configuration by identifying the code. It then tried to fix the listing page queries and failed. I pointed it to use query tags to tag the listing queries and alter them to check if the term is trashed. This was implemented swiftly. The AI implemented a Trash handler for taxonomy terms, which was actually a good decision I had not prompted. But its implementation had a fatal flaw in that it used a class-level array variable for storing the term hierarchy (static caching). This obviously will not work because delete and restore operations happen in separate requests with no persistence between them.</p>
<p>While testing, I found that the trash module automatically trashed the child terms if the parent is deleted. This was expected behavior.</p>
<h2>The Discovery That Changed My Understanding</h2>
<p>Through old-fashioned debugging and stepping through the deletion flow, I finally understood why the timestamp approach would not work, and it comes down to how Drupal core handles term deletions.</p>
<p>Say you have a hierarchy of terms A and B where A is the parent. When term A is deleted, <code>Term::preSave</code> is called and then the term is deleted. After that, <code>Term::postDelete</code> is called to delete the orphaned children. But here is the problem: When the child term B is about to be deleted, <code>Term::preSave</code> is called again, and that function resets the parent to root before the term gets trashed. This removes any trace of the previous hierarchy entirely.</p>
<p>So even though both terms end up with the same deleted timestamp, we have lost the parent-child relationship by the time they are in the trash. We will need to store the hierarchy data somewhere else, probably with the term itself <a href="https://www.drupal.org/project/trash/issues/3491947" target="_blank" rel="noopener noreferrer">as others had suggested in the issue</a>. I posted this finding back to the issue queue because it changes the direction of the solution. After that, the maintainer suggested that we pause and reconsider the overall direction of the fix before writing more implementation code. I didn’t want to just stop working at that point, so I decided to focus on something that would still add value: a solid set of tests around the behavior we had uncovered.</p>
<p>I reached this conclusion through traditional debugging, but AI became useful once the solution path was unclear, because it could help me quickly generate and refine tests instead of spending my time on boilerplate.</p>
<h2>Starting Fresh and Finding My Rhythm</h2>
<p>With the understanding that solving hierarchical restore is tricky and needs more intervention from the module’s maintainer, and that the direction of the fix was on hold, I decided to change how I contributed. Instead of trying to force a solution, I asked AI to help enumerate test scenarios and create tests for trashing, restoring, and purging terms, and it came up with a decent list of scenarios (it applied trashing, restoring, and purging):</p>
<ul>
<li>Single term</li>
<li>Hierarchies with one parent</li>
<li>Hierarchies with multiple parents</li>
</ul>
<p>I specifically requested creating atomic tests where one test does one thing. In previous iterations I had noticed it crammed multiple assertions into single tests, but this time it created a comprehensive test class covering all scenarios properly. AI generated the initial test code and structure, and my job was to review, adjust, and guide it toward clean, atomic tests that matched how we actually use the Trash module.</p>
<p>This felt like a real breakthrough in how to work with these tools, namely: you must provide as much context, detail, and guidance as possible in order to get good results.</p>
<p>It tried to run these tests, failed a few times, and I had to step in to explain that the project uses DDEV and tests need to run inside the container. Even then it took three or four more attempts to get the tests running, which tested my patience a bit. As expected, all tests except the hierarchical restore passed.</p>
<p>None of this moved the core solution forward directly, but it still added real value. By using AI to design scenarios and generate most of the test code, I could keep making progress without sinking a lot of time into repetitive work. Those tests now act as a reusable safety net for future changes, so when the direction is finally decided, we will already have a solid foundation to build and iterate on.</p>
<p>Without AI, I probably would have stopped at one or two manual checks or a much smaller test suite, simply because of the time investment. With AI handling the boring parts of writing and reshaping tests, I could afford to cover more scenarios and refine them, even though the underlying solution is still on hold.</p>
<h2>Internal Work Showed Me the Real Potential</h2>
<p>The Trash module work showed me how AI can help with testing around complex problems, but internal projects are where it really clicked for me. For internal projects, the AI proved more immediately useful, and I got genuinely excited about the possibilities here. For example, on a separate internal project, I needed to create a text filter plugin for rich text formats in Drupal that checks for special unordered lists and replaces them with a <a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">Storybook component</a>.</p>
<p>I crafted my prompt carefully based on previous attempts where the AI went wild and tried to implement everything from scratch including the filter plugin, the component, and all the theme functions. In a new session I prompted explicitly that I did not need a new theme function, that it should just use twig include from Storybook, and that it needed to handle nested lists properly since they are multi-level.</p>
<p>It generated a decent filter plugin on the first try, and while there were mistakes of course, I guided it toward modern PHP patterns like attributes and constructor property promotion and PHPStan compliance. A couple of project-specific issues needed manual fixes, but the core implementation was solid.</p>
<p>The tests went really well too. I started with a Kernel test, then tried a Functional test, and eventually settled on Unit tests on the tech lead's suggestion. The Unit test was precise and to the point, and I think this is where AI really shines because the context is clear and limited in unit testing scenarios.</p>
<h2>CSS Help Was a Pleasant Surprise</h2>
<p>As someone who is mostly a backend developer, this is where I found the use of AI to be genuinely delightful. It quickly understood the project's styling approach, detected that we were using Tailwind, and suggested fixes with basic prompts. No deep context needed, just quick wins that saved me from the usual frustration of wrestling with frontend styling.</p>
<p>Together, these internal projects reinforced the same pattern I saw with the Trash module: AI is most helpful when I give it a narrow, well-defined problem and let it handle the repetitive parts while I focus on the decisions.</p>
<h2>The Lessons That Actually Matter</h2>
<p>The biggest realization I had is that you need to treat AI as a junior developer who needs clear guidance and supervision. It never replaced my debugging or architectural judgment, but it did make it cheaper and faster to handle the repetitive parts of the work. Providing context aggressively makes an enormous difference, and the more specific you are about the codebase structure the better the results turn out. In my case that meant offloading a lot of test boilerplate, trying out different test structures (Kernel, Functional, Unit), and iterating on scenario coverage without feeling like I was wasting time. Limiting where the AI can scan also helps manage the context window size, which matters more than I initially realized.</p>
<p>I found that spending time in Plan mode before rushing to Act pays off tremendously because it lets the AI think through the problem first. Keeping scope small is also critical since large ambitious prompts lead to hallucinations and broken assumptions while small well-directed tasks actually get completed correctly.</p>
<p>When the AI starts hallucinating, restoring to a previous checkpoint or starting fresh with the same context works much better than trying to correct a conversation that has gone off the rails. I learned this the hard way through several frustrating sessions.</p>
<p>Managing the “context window” for the AI is important. Sometimes starting a fresh conversation makes more sense than continuing with the existing one. I made sure that I exported the context in each conversation, adjusted it and carried that to the next session.</p>
<p>Used this way, AI feels like a junior developer who is great at cranking out tests and repetitive scaffolding, while I stay focused on debugging, design decisions, and understanding the problem. The thing that excites me most is that using AI we can finally do test-driven development without it feeling like a burden. That alone makes learning to work with these tools worthwhile, and I am looking forward to exploring this further on future projects.</p>
<div class="highlight-box">
<p>This post is part of Tag1’s <a href="https://www.tag1.com/insights/?tag=AI&type=blogPosts#selected">AI Applied content series</a>, where we share how we're using AI inside our own work before bringing it to clients. Our goal is to be transparent about what works, what doesn’t, and what we are still figuring out, so that together, we can build a more practical, responsible path for AI adoption.</p>
</div>
<p><em>Bring practical, proven AI adoption strategies to your organization, let's start a conversation! <a href="https://www.tag1.com/contact/">We'd love to hear from you.</a></em></p>
<p>Image by <a href="https://cline.bot/blog/why-ai-engineers-need-planning-more-than-perfect-prompts-2">Cline</a></p>
]]></description>
      <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Ajit Shinde</dc:creator>
      <guid>https://www.tag1.com/blog/using-ai-for-drupal-development/</guid>
    </item>
    
    
    <item>
      <title>Exploring the Future of the Drupal Group Module</title>
      <link>https://www.tag1.com/tag1-team-talks/future-of-drupal-group-module/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<!-- YOUTUBE EMBED -->
<link rel="stylesheet" href="https://www.tag1.com/css/lite-yt-embed.css" />
<script src="https://www.tag1.com/js/lite-yt-embed.js"></script>
<lite-youtube videoid="kZzyu8W83JA" playlabel="Play: Exploring the Future of the Drupal Group Module"></lite-youtube>
<iframe class="w-full mt-8" height="200" src="https://player.captivate.fm/episode/a8b26b4d-7e9b-46d5-8e17-53238d1f9417"></iframe>
<!-- Take Away -->
<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
<p>After 13 years of iteration, Group creator and Tag1 Drupal Specalist <a href="https://www.tag1.com/team/#kristiaan-van-den-eynde">Kristiaan Van den Eynde</a> shares a clear roadmap for Group 4. The long-awaited release unifies the v2/v3 split into a single upgrade path, consolidates key submodules back into core to cut maintenance, and removes the confusing two-step editor workflow that has long frustrated content teams. Looking ahead, Kristiaan outlines plans for native public/private Group support and a more flexible query access system that could reshape how Drupal platforms handle access control for years to come.</p>
<p></p></div><p></p>
</div>
</div>
<!-- What You Will Learn -->
<section class="learn">
  <h2>What You Will Learn</h2>
  <ul>
    <li>Why Group split into v2 and v3, and how Group 4 unifies them with a clear upgrade path.</li>
    <li>How moving submodules (like subGroup and Group Sites) into core reduces maintenance without compromising engineering.</li>
    <li>Why Group Sites is a modern, high-performance alternative to Drupal multisite, powered by the Access Policy API for context-aware microsites.</li>
    <li>How Group 4 replaces the confusing two-step editor form and paves the way for context-aware editing on standard Drupal forms in v4.1.</li>
    <li>What’s next: native public/private Group support and a richer query access system that could influence Drupal core.</li>
  </ul>
</section>
<!-- TRANSCRIPT -->
<h2>Transcript</h2>
<div class="transcript">
<p><strong>[00:00:05] Michael Meyers:</strong> Hey everyone. Welcome back to Tag1 Team Talks. Today we're talking about the future of the Group Module with creator and maintainer Kristiaan Van den Eynde. If you're building anything in Drupal where different people need different levels of access to different sections of your site, think teams, company departments, local chapters, membership tiers, you're probably using Group. And if you're building a platform on Drupal, one of its most powerful and popular use cases — think universities, corporations, government agencies running dozens, hundreds, even thousands of sites and microsites — you're almost certainly using it. It lets you control who sees what, who can edit what, who can manage what, and is all scoped to a specific collection of content and users, and it's relied on by some of the biggest organizations running Drupal today. I'm Michael Meyers, the managing director at Tag1, and my guest today is the creator and lead maintainer of Group, Kristiaan Van den Eynde, a senior architect and engineering leader here at Tag1, who's been iterating on this module for over 13 years, which is really amazing. He's a top Drupal core contributor, a core subsystem maintainer, and not just an amazing engineer — he's also a really great communicator who has presented at numerous DrupalCons, Dev Days, and he's got that rare ability to take the most complex and detailed subjects and make them approachable to anyone. So whatever your background, I'm confident that you'll learn a lot out of today's episode. Kristiaan, welcome and thanks for joining.</p>
<p><strong>[00:01:35] Kristiaan Van den Eynde:</strong> Yeah, thank you. And thank you for that nice introduction. It's nice to be here and I'm looking forward to talking about Group and where we're going with it and to answer some of the questions that people may be having that you are going to ask today.</p>
<p><strong>[00:01:51] Michael Meyers:</strong> Awesome. Yeah. Just to give people a quick overview of what we're gonna talk about — the future of Groups, right? The long awaited consolidation from the two major versions into Group 4. You're doing a major restructuring of the module ecosystem, pulling a lot of these submodules back into the core module. We're gonna talk about improvements to some pain points like the Experience Editor, which I love that you've been so open about and working so hard to address. And I'm also really excited about talking about some of the code quality issues and what it takes to maintain and sustain such a popular module at this scale. But before we get into the future of Group, I wanna talk about everyone's future with Tag1. Tag1 is the number two all time contributor to Drupal, the world's second most popular content management system. For nearly 20 years, we've been the architects of the open web, leading the creation of the software and best practices that powers millions of websites and thousands of organizations worldwide. We're your full service strategic partner, applying that same architectural expertise across technologies and throughout your organization. From discovery and design to building and scaling complex applications, we lead AI strategy and implementation, design and manage infrastructure, and architect large scale web applications across a wide range of technologies and platforms. We're trusted by industry leaders, including Sumitomo and NTT Data, the European Patent Office and the American Federation of Teachers to solve their mission critical challenges, and we build lasting solutions and relationships. Check out Tag1.com to learn more about how we can help you.</p>
<p><strong>[00:03:27] Michael Meyers:</strong> All righty. So with that outta the way, Kristiaan, before we talk about the future of Groups, I have always wondered — the origin. Why did you call it Group module and not Groups module?</p>
<p><strong>[00:03:42] Kristiaan Van den Eynde:</strong> Okay. So I've let it go, but at first it annoyed me tremendously that anyone coming up to me at a Con would say, "Oh yeah, I'm a fan of Groups." I'm like, okay, but it's called Group. The way that came into existence is, as you may recall, before Group existed, everyone was using a module called Organic Groups. That's a long time ago now. I'm not gonna go into the whole backstory of why I created Group. But I wanted to build this alternative with a different data structure and just basically a different vision of how I thought things like this could work. I was looking on drupal.org for available project names and I thought, let's keep it simple. What am I doing here? I'm building this entity called a Group. So let's just call it that. I tend to look at Drupal Core a lot when I build stuff. And back then I wasn't a core maintainer even. So I just looked at the node module in core — for content, if you want to create a page or an article, internally that's called a node, and the module is just called Node. So I had the same concept where I had this thing called a Group. So I thought, let's call the module Group.</p>
<p><strong>[00:05:09] Kristiaan Van den Eynde:</strong> Funny side story. That name was taken on drupal.org, but it was taken by Amitai Burstein, the same guy who wrote Organic Groups. And I don't know what he was planning to do with it. It seemed like he was trying to rewrite OG or something. And then I just reached out to him and I said, hey, I'd like to take over this project namespace, nothing is happening with it. And it was like, oh yeah, sure, knock yourself out. And he gave it to me. And then I created Group, which later on surpassed Organic Groups. By the way, all the love in the world for Amitai — he is a great guy. I understand why people tend to call it Groups because a lot of them migrated from Organic Groups. But yeah, the name is Group and I think the ship has sailed. I would appreciate it if people started calling it Group because there was a reason behind calling it that.</p>
<p><strong>[00:06:34] Michael Meyers:</strong> You won me over with the node argument. I've always wondered, because I always — I know why people call it Groups, but now it makes a lot of sense to me as to why you chose to do that. I think people are gonna keep annoying you and calling it Groups. I'll try not to annoy you too much today with it. And Amitai is awesome and he's got his hands full with workspaces — he's another awesome Tag1 engineer.</p>
<p><strong>[00:07:45] Michael Meyers:</strong> So, back to the future — can you set the stage for everybody at a high level? You're thinking about the architecture for the newest version of Group. What are your guiding principles? What are you thinking about? How do you structure your approach to this?</p>
<p><strong>[00:08:08] Kristiaan Van den Eynde:</strong> Okay. So at very first when I wrote Group, I accepted all of the feature requests. I had so many ideas. I just worked on all the things at once, right? And it became bloated, it became unmaintainable and it led to the situation where people came with even more feature requests because they saw that anything goes. And then in the later versions I tried to steer away from that and make it more mature. The people at Deeson in the UK, who I was working for at the time, they actually helped me with that because they gave me a space where I could bounce these ideas off of them and they would provide insights with their experience and challenge me. So that's how the sort of building blocks of version two and three came to be. And over time you'll see that I've tried to make it more and more mature, following industry standards more and more. As I grew as a developer, I saw more things. I started recognizing patterns. I saw things that we were doing in Core. I copied those over. Then I found that some of these things were lacking, and I improved upon them, which is how we got some systems into core, like Variation Cache and the Access Policy API. And these are like the sorts of habits that I've grown to have that I want to continue having towards the future — where I look at what I can improve and try and make these iterations where it just becomes more and more mature, more and more stable. And I keep challenging the status quo.</p>
<p><strong>[00:11:55] Michael Meyers:</strong> So I know one of the biggest things that you're focusing on with the new version of Group is consolidating down to a single module. Right? So for a long time now, you've been maintaining two major versions of Group side by side. Talk me through why you chose to split this out into two modules to begin with, and some of the challenges that that created.</p>
<p><strong>[00:12:27] Kristiaan Van den Eynde:</strong> Okay. So this is one of the biggest regrets from my professional career — that I went ahead with this. And in the end I still think it was the right choice, but it's caused me so much pain that if I had a time machine, I'm not sure if I would do it again. Essentially when I developed Group at first and then going into the first version for Drupal 8, and then trying to create the second version, which was a lot more mature and moved away from the concepts that I had still inherited from Drupal 7 — the only thing that you could put inside a Group was what we consider content entities: pages, articles, but it could also be a different Group. But there was a desire from the community to also be able to put configuration into Groups. That would be the configuration, for instance, of what fields should be on an article — or especially popular, the Webform module. Webform allows you to create these forms on the fly, and a lot of that is tied to configuration. You couldn't put that inside a Group at the time. So right before the release of the second version of Group, we actually had sponsorship from ANNAI out of Japan, and they wanted that functionality where you could put web forms inside of a Group. So I made that possible. But now I had this conundrum — because inside of the Group module, the thing that was representing which content belongs to which Group was called Group content. And it turns out that changing names of classes — that's not all that hard. But these pieces of code usually have what we call a machine name, and it's that specific piece of text that we put in a database. And changing that is really hard in Drupal, it turns out.</p>
<p><strong>[00:16:09] Kristiaan Van den Eynde:</strong> I wanted to change it and I didn't want to have like two names — Group content and Group config. I wanted to have a name that represents all of the things. So I ended up going with the name Group relationship. It's a relationship between a Group and something that you put in it, whether that's content or config or perhaps in the future, something completely different. So that sounded like a future-proof name. And I wanted to go with that, but changing that name in the database — well, it was possible for the most part, but not everywhere. So what I ended up doing was I said, you know what, I'm gonna change the name wherever I can except for the few places where I can't. And that's going to be version two. So people who were on version one can go to version two and nothing will crash. But at the same time, I'm gonna release version three, which is exactly the same as version two — an exact copy, except it will have those new names also in the machine names. And that was a really dumb idea, but it worked.</p>
<p><strong>[00:18:31] Michael Meyers:</strong> So tell me more about that upgrade path. If I'm on two, do I need to go to three to get to four?</p>
<p><strong>[00:18:44] Kristiaan Van den Eynde:</strong> Yeah, so you do have to go to version three first, and then you can go to version four just like anyone else. So the whole point is that in the latest update hooks, the code that we write to allow you to update your software — in version three I've added some of that code that allows you to download version three on top of version two and run those updates and it will do most of the heavy lifting for you. And then there's this user manual on drupal.org that tells you exactly what to do after that, because there will be some files on your disc that you need to look through and make some changes. There will be custom code that you need to make some changes to, because I cannot predict which or what custom code you've written for your project. But all of that is written down in these documents and it has steps to follow. Nowadays, obviously most projects start with version three, and soon they will start with version four. So for them it's a non-issue. It's just for those legacy projects that keep upgrading to the latest version to have like secure software with coverage and everything. You wouldn't wanna put a junior on that — you wanna put one of your best people on that.</p>
<p><strong>[00:20:30] Michael Meyers:</strong> If I am launching a new site and using Group, what module should I be using today? Should I be using three? Is four something I can start with today?</p>
<p><strong>[00:20:44] Kristiaan Van den Eynde:</strong> You should be using three because the upgrade to four should be quite seamless. But if you want, you can try out four already — it's actually very stable. It's just that it's a dev release right now and that means I do still reserve the right to make drastic changes. I don't envision making them anymore because most of the roadmap that I had in mind is completed. The data architecture is the same as I want it to be. It's just some features right now, some UX polish because it's actually like 90% done, perhaps even more. So you could try starting on version four already. I probably won't blow up your project, but keep in mind that there is a chance that I still might — probably not, though.</p>
<p><strong>[00:22:10] Michael Meyers:</strong> Another big change that you've made is really addressing this amazing module ecosystem that you've created. You have subGroups, Group Sites, context providers — there's a lot of modules and submodules that are part of Group. My understanding is now you want to pull some or all or a lot of that back into the main module. What's driving that decision?</p>
<p><strong>[00:22:40] Kristiaan Van den Eynde:</strong> Maintenance. Really just maintenance. To give you an idea: for each update that I made for Group two or three right now, let's say I made a new release which deprecated some code — that's technical speak for functionality that would no longer be supported in the future — whenever I did that, I needed to create that for version three, then if it had any of those funky names, I needed to also create a version for two where those names are changed. Then I needed to go to my subGroup module and start implementing those changes. And do that for version two there too, because just like Group, subGroup had two versions to support both versions of Group. And I would have to do that for all of my ecosystem modules that I was maintaining. The reason that I had all of these modules out there in an ecosystem to begin with was to have them as examples — because I do not believe in privileged code. I do not believe that if I want to offer functionality from within Group that my code should have special access to do stuff. What I can do, you should be able to do. That's always been the premise of how I write my code. So by having subGroup and Group Sites as standalone modules, it's truly a testament to that fact — to show people that you can do this with Group. But it's become such a pain to maintain because now I have multiple issue queues, multiple versions. If I want to create a new release, I have to do that for all of these modules. The whole concept of bloat no longer exists on today's internet — downloading a couple of extra megabytes is no longer a hurdle. So getting all of that code back into the main module would mean that if I make these changes, I can do it all in one go.</p>
<p><strong>[00:27:57] Michael Meyers:</strong> You mentioned Group Sites a couple of times there. Give me a sense of why it's such a big deal, especially for organizations that are running essentially multiple sites off of a Drupal installation.</p>
<p><strong>[00:28:25] Kristiaan Van den Eynde:</strong> So out of the box, Drupal has always had this concept of multisites. And as you know, I'm trying not to offend anyone, but I'm pretty sure that no one is actively maintaining that. It's horrible. The way it works — it hasn't really been revisited in ages. It hasn't caught up with modern times at all. And I noticed that a lot of people were using Group to create multiple microsites within one website so that in a back office they could add all of the content they wanted and it would show up on one or multiple microsites. I saw this really great example of this like many years ago when I was working in the UK. We had this website for a lot of pubs, but it was all maintained by this one large brewery. Each pub would have its own look and feel, its own style, its own domain name. And they were using Group for that already — sure — but they had a lot of custom code. I wanted to make this more accessible because this is something awesome. So I set out with this concept and I created it and instantly got amazing feedback. We managed to sell the idea to one of our clients when I was working in Germany at Factorial, which was a large multinational. And their whole new web platform is built on Group Sites and it's really fast. And then we had Local Gov, based in the UK — they had a couple sessions at Dev Days outlining how they were using Group Sites and how they actually built something before they realized that Group Sites existed. And they were like, oh, this is awesome. And they actually went through the effort to move everything to Group Sites. So now we actually have a modern solution for microsites in Drupal.</p>
<p><strong>[00:31:43] Kristiaan Van den Eynde:</strong> Moshe Weitzman — both he and I maintain the user system subsystem in core. And during one of these talks that we had with subsystem maintainers a couple months ago, Moshe was actually saying, why do we still have this multisite system in core? It needs to go. And he really meant: we should just bank on Group to get that part sorted. It doesn't belong in core anymore. And I fully agree with him — not because I'm the author of Group, but just because as a core maintainer, it doesn't make sense to have that in core now that we have the Access Policy API in core. Anything is possible, especially multisites. So let's just put that in Group. It leverages all of the latest technology and it allows you to just choose any sort of context in which you want to trigger which microsite is active — whether that's a domain name or part of the path in the URL or your location in the world or anything we can detect from the context of your browser session. We can use that to choose which microsite to show you. And it basically just tells the database, I know you have like a million microsites in there, but I only need this one — and the database, because of some trickery with the Access Policy API, will go, oh okay, that's fine, I just forgot that there are like 999,000 other websites out there. Which makes all of the queries really fast. So yeah, it's one of the things I'm most proud of because it's the accumulation of all of the things I've built over the past decade and it works so well together.</p>
<p><strong>[00:39:24] Michael Meyers:</strong> I think one of the things that I love about you is your critical approach to things — not just what you were talking about earlier about how things are implemented in core and we should rip them out, but you're also really candid about your own code, your own product Group and what you can improve. And I know that's a big thing you're trying to address in version four. The editorial experience is probably the perfect example of that. You've been the first guy to say, yeah, it's been rough, there are clear challenges with this. Can you tell us how you're trying to address some of the workflow issues and capabilities for editors?</p>
<p><strong>[00:40:22] Kristiaan Van den Eynde:</strong> Yeah, definitely. So quick backstory — trying to get content into a Group can be really annoying, depending on how you set it up. Not every piece of content that you want to put in the Group is treated similarly. So if you want to put a page or an article in a Group, you just care about that page or article being in the Group. You don't usually want to specify why or how you put that inside the Group. But alternatively, if you want to put a user in a Group, there are multiple ways to do that, but usually it's under the guise of a membership. So you want to put that user inside of a Group, but now you do care about why or how that user belongs to that Group. So we have some extra information on that relationship — for instance, which roles they should have in that Group. One user could belong to Group A with no roles, but could belong to Group B with very administrative level roles.</p>
<p><strong>[00:41:48] Kristiaan Van den Eynde:</strong> Now the issue with that is that we need to show a form for that metadata. And so in the past, the best I could come up with was to create this sort of wizard where you could configure whether or not you wanted that second step to specify the metadata. So at first you would have this form to create a page, and then if we didn't get to that second step, great — you would just create that page and that would go inside the Group immediately. But sometimes if you misconfigured that, or if you did need that second step, people would end up on this form where they were supposed to specify metadata about why this article belongs to a Group. And it would confuse them because they just created an article and now they're on a second form — what's going on? And sometimes that form would even be empty because some administrators would see fields there but a regular user wouldn't. So when they got to that form it was just blank, and they had a button to click save, and then their article got added to their Group, which made no sense whatsoever. So that user experience was horrible, to say the least.</p>
<p><strong>[00:43:57] Kristiaan Van den Eynde:</strong> So now for version four, I've managed to find a lightweight alternative where you absolutely no longer have that second step. But I sort of check if there is metadata required for the relationship. And if it is required, then you will see it on the first form and it will validate properly, it will save properly. But you will never have more than one form. And if there's nothing to show, it will just show nothing. So you won't have a second step that's blank — you will just have your original form.</p>
<p><strong>[00:44:37] Kristiaan Van den Eynde:</strong> The second thing is that people who have worked with Group will notice that if they want to create a page inside a Group, they do so on a custom URL — it's something like slash Group slash the ID of the Group slash content slash add slash node column page. It's a very funky URL. And it looks exactly the same as if you were to go to the regular form to add a page, but yet it's at a different URL and it behaves differently and some of the options aren't available. That was because I needed to know what Group you're trying to add this page to. But it's annoying because people who are trying to alter this form now there are like two forms and they need to make sure they change it everywhere.</p>
<p><strong>[00:46:07] Kristiaan Van den Eynde:</strong> So one of the things that I'm trying to do for version four — this will not land in version 4.0, but I'm trying to get it in for version 4.1 — is this notion of context. By taking what I've built for Group Sites, which is very good at detecting what Group, what microsite we're trying to show you — what if we take that same system, we put it in the main module, and we use that so that at any point in time, you as an editor can say, I'm currently working on this microsite, or if you're not using microsite, just I'm working on this Group. And now you can go to all the regular forms anywhere. So if you want to add a page, you go to the form where people on a regular Drupal site would add a page. If you want to add a user, same story. And then Group behind the scenes knows the context that the editor is in. So the editor is currently trying to add something to, let's say, the Belgium website — and I would know that, so when they add a page I would know to put that in the Belgian Group. Imagine not having to train your users: if you want to add a page to the regular site, you have to go here, and if you want to add a page to this special section of the site, you have to go here. No — just if you want to add a page, you go there and it will just work. I think that's pretty awesome compared to what we have now.</p>
<p><strong>[00:50:03] Michael Meyers:</strong> I know one of the things that you've talked about is wanting to raise PHPStan levels and code quality standards on Group over time. Could you give a little bit of backstory and then, why is it that you want to make it even better? What's your focus here?</p>
<p><strong>[00:50:35] Kristiaan Van den Eynde:</strong> So for those who are unaware, PHPStan is a sort of analysis tool that looks at your code and tells you where you are or might be doing things wrong. And it has multiple levels. At the lower levels it complains about very basic things. And as you go up, it starts complaining about all the things. What this means is that you could start at the ground level and add this tool to your module, and it will tell you all the places where you're doing the very basic stuff wrong. And then you clean that up because it leads to more stable code. It also means that people who are using your code can use it in a more stable manner — they can only offer the data that your code expects and they will get data in return that your code promises it will return. And as you go up and increase the level, it will start complaining about more technical stuff.</p>
<p><strong>[00:53:01] Kristiaan Van den Eynde:</strong> While Group has a lot of UI elements and does a lot of magic out of the box, in its core I've always considered it an API module — because there is so much that you can do with it with custom code behind the scenes. I want to make sure that it's an API-first module, that the code comes first and then I build a UI on top of it while calling my own API, so to speak. And this goes back to what I talked about previously — I don't like privileged code. So if I can build a form for my module and someone else can build a form too, how do we achieve that? By having a good API so that my form can call that API and someone else's form could just as similarly call my API. And I think one of the ways to keep getting better is to make sure that we grab all the old code, we clean it up, and we raise the level. And then when we write new code, it has to adhere to that higher standard. And then we raise the level again and again and again until we get close to or actually reach the highest standards. And then once you have code that adheres to that highest standard, it should be so well written and perhaps so abstract that you could look at the pieces that are doing something special, take those out of the module, and put those in core or put those into a PHP library that even non-Drupal projects can use.</p>
<p><strong>[00:56:04] Michael Meyers:</strong> Is there anything that you want to cover about the future of Group that I haven't asked about?</p>
<p><strong>[00:56:19] Kristiaan Van den Eynde:</strong> Yeah. So very recently people have talked to me about some ideas they had, and I got really excited about them. There are two in particular. One of them is public versus private Groups. It's something that I know exists in this distribution called Open Social and I know that they've been sort of struggling with getting that to work correctly. They've managed obviously, but they've had to write a bunch of custom code. Recently, internally at Tag1, a coworker asked me how would you envision this functionality? And obviously when given a blank canvas I go nuts — that's where I have fun. So I thought, let's entertain this thought. Is this possible? Is this manageable? Would this be performant enough to make into something real? And the answer is it's actually very doable. So I gave that coworker some pointers and then I realized it actually wouldn't be that much work to do this myself and put this in the main module. If Open Social or anyone from Open Social happens to listen in, they're probably gonna get a real jump scare here because it might be coming to Group. The cool thing would be that we could just hand out two separate sets of permissions. Like if the Group is public, people from certain roles would get certain permissions, but then if the Group becomes private, they would get different sets of permissions. And with that in mind, we could have something really cool where Groups are hidden and then if you get a sort of access key or beta access, then all of a sudden that Group would be accessible to you.</p>
<p><strong>[00:59:52] Kristiaan Van den Eynde:</strong> And then the other thing is something that I can see ending up in Drupal core. Currently when we build lists in Drupal, we don't have a lot of options to limit access to what you should be seeing in that list. If you take the front page of any Drupal website, you could usually see a list of articles, right? But you don't see all of the articles in the system. You don't see the ones that are still unpublished. You don't see the ones that have some sort of access control, whether that's Group or domain or any other access module. Because of a thing called query access — when we go to the database to ask for all of the articles it can show us, we actually reduce that list to the ones that we know you can see. And the key verb here is "see." We have something similar for two other operations — you can build a list of articles as an editor saying, show me the articles that I can edit or show me the articles that I can delete. And that's pretty cool. But it's very limited. I've been contacted by people who want to build these lists for stuff that resides in a Group, and they don't know how to pull that off because in Drupal we only have those three basic operations: can you view it, can you update it, can you delete it. And they wanted a list of stuff that they can put into a specific Group, or they wanted a list of users who have access to join a particular Group, or a list of Groups that they have access to join. Now I have this idea where instead of just limiting ourselves to those three verbs, we can add a piece of metadata on the query that we send to the database where we say, I'm trying to do something in the context of this Group, show me a list of things that follow those conditions. And this seems like something that I would at first build inside of Group, but then if it works really well, we should probably put that in core. It's 2026 — it really doesn't make sense that you can only retrieve lists from the database for three verbs when Drupal has evolved into a system where people want to do all sorts of things and create all sorts of lists.</p>
<p><strong>[01:04:04] Kristiaan Van den Eynde:</strong> With that reality, I'm just gonna tidy up this one issue that I'm still working on and that's going to be Group 4.0. And then the other features will be 4.1, 4.2, and it will gradually become more awesome. I've had a crazy year — I'm renovating my house, I changed jobs, I started a business, I've had some health issues. 2025 has thrown a lot my way and I lost a lot of time that I wanted to spend on Group and core. But we'll have to settle for all of the stuff that I did in early 2025 plus some efforts that I've done recently, and we'll see how we go from there.</p>
<p><strong>[01:04:58] Michael Meyers:</strong> It's amazing. Thank you for all your hard effort on it. I purposely didn't ask about your roadmaps and deadlines and milestones. Being a maintainer of a module like this takes a tremendous amount of work and effort on top of everything else that you have going on in your life. And people can never appreciate that enough — the sacrifices you make to make this happen. So really, your openness, your honesty, your willingness to be self-critical and critical of the platform in the hopes of making it better — sharing your insights and challenges has been really fascinating to me and hopefully really helpful to other module maintainers. I think there are some really great topics we can follow up on, and hopefully now people have a really good sense of where you're going with this and why and what's important to you. Again, thank you so much for joining me. A big thank you to all our listeners. You can check out past episodes at tag1.com/podcast. Send us your feedback about Group, about the podcast, about future topics you'd like us to cover. You can reach us at info@tag1.com, that's TAG the number one .com. And of course, subscribe so you don't miss out on future conversations. Special thanks to Tracy Cooper and June Gregg for producing today's episode with input from Hank Vanzile and Cassey Bowden. Until next time, take care.</p>
</div>
<!-- FINAL CTA -->
<aside class="callout-cta my-10 p-4 xl:p-8 bg-purple text-white" style="--link-color: var(--color-turquoise); --decoration-color: var(--color-turquoise);">
    <div class="py-11 xl:py-8 px-4 xl:px-8 frame frame-white-35">
        <div class="relative z-10 mt-0 sm:text-xl">Building on Drupal Group? <a class="group underline hover:no-underline focus:no-underline" href="https://www.tag1.com/contact/">Let&#39;s Talk Architecture.<span aria-hidden="true"> ></span></a></div>
    </div>
</aside>
]]></description>
      <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Michael Meyers</dc:creator>
      <guid>https://www.tag1.com/tag1-team-talks/future-of-drupal-group-module/</guid>
    </item>
    
    
    <item>
      <title>Building the Document Summarizer Tooltip Module with AI-Assisted Coding</title>
      <link>https://www.tag1.com/blog/building-document-summarizer-tooltip/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
<p>At Tag1, we believe in proving AI within our own work before recommending it to clients. This post is part of our <a href="https://www.tag1.com/insights/?tag=AI#selected">AI Applied content series</a>, where team members share real stories of how they're using Artificial Intelligence and the insights and lessons they learn along the way. Here, team member Minnur Yunusov explores how AI-assisted coding helped him rapidly prototype the Document Summarizer Tooltip module for Drupal, while adding AI-generated document previews, improving accessibility, and refining code through real-time feedback.</p> 
    </div>
</div>
</div>
<h2>From Idea to working Drupal prototype with AI-assisted coding</h2>
<p>I started with a simple goal: build a working prototype that could summarize linked documents directly in Drupal, without having to spend too much time on it. AI-assisted coding helped me move from idea to an installable module quickly, even though the first versions weren’t perfect. The focus was on getting something functional that I could iterate on, instead of hand-writing every piece from scratch.</p>
<p>The prototype I put together with AI-assisted coding works and can be installed and tested. You can find it on GitHub at <a href="https://github.com/minnur/docs_summarizer_tooltip" target="_blank" rel="noopener noreferrer">https://github.com/minnur/docs_summarizer_tooltip</a>.</p>
<p></p><figure class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/blog/docs-summarizer-tooltip.png" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/blog/docs-summarizer-tooltip.png" alt="Screenshot of the Document Summarizer Tooltip module for Drupal showing a tool tip modal containing the AI-generated summary of a PDF file" />
</a><p></p>
<p></p></figure><p></p>
<p>Initially, I tried using Cline with Claude Sonnet to generate the module. It produced a full module structure, but the result didn’t actually work in Drupal. JavaScript in particular needed refactoring, so I switched over to Claude Code, which became my main tool for debugging and refining the implementation.</p>
<h2>What broke, what worked, and what I fixed with Claude Code</h2>
<p>One of the biggest pain points was the tooltip behavior itself. The tooltip wasn’t positioning correctly, which meant the UX felt off and inconsistent. I used Claude Code iteratively to adjust the JavaScript until the tooltip appeared in the right place and behaved in a way that felt natural.</p>
<p>Another issue was that the tooltip wasn’t showing the title as expected. I tracked down the generated function responsible for rendering the header, wired in my own variables, and then asked Claude Code to include that variable in the header output. After that targeted change, the tooltip finally displayed the title properly and felt much closer to what I wanted.</p>
<h2>Turning document links into smart, AI-powered tooltips</h2>
<p>The core concept of the module is straightforward: detect document links on a page and show an AI-generated summary in a tooltip on hover. It started life as a PDF-only prototype, focused on a single file type so I could validate the idea. Once I had the tooltip behavior working smoothly, with correct positioning, title rendering, and consistent UX, I was ready to expand the scope. I asked Claude Code to refactor the module to support more file types beyond PDFs and rename it to “Document Summarizer Tooltip.”</p>
<p>The refactor mostly worked, but the rename was incomplete. Some files kept the old name and needed manual updates. This was a good reminder that while AI can handle broad changes efficiently, it still needs a human to double-check details across Drupal files and configuration.</p>
<h2>Accessibility, ARIA, and making AI summaries usable for everyone</h2>
<p>Once the basic behavior was there, I wanted to think about accessibility. A tooltip full of AI-generated content is not very helpful if screen readers or keyboard users can’t access it. I asked the AI to help with adding accessibility considerations as a next step, including ARIA attributes and behavior that would work beyond simple mouse hover.</p>
<p>The initial AI-generated settings form went a bit overboard and included more fields than I actually needed. That said, it did a good job of covering a lot of reasonable options. From there, I was able to prune back the form to something simpler and more focused, which also made the UI easier to understand and configure.</p>
<h2>What AI got right (and what still needed review)</h2>
<p>One thing that stood out to me was how well the AI handled some of the integration details. It added Drupal AI integration and CSRF token support with almost no issues, which saved a lot of time. It also recognized variables I introduced and reused them correctly across functions, which made iterations smoother.</p>
<p>At the same time, the generated code was not something I could just drop in without reading. A few Drupal API calls looked right on the surface but weren’t actually real. That required a thorough review and manual fixes. I didn’t have time to add unit tests for this prototype, but in the future I’d like to see how well AI can help suggest or scaffold tests alongside code changes.</p>
<h2>How clients can use AI for prototyping, accessibility, and tests</h2>
<p>There are a few clear ways clients could apply this approach. First, AI-assisted coding is very effective for rapid prototyping, especially when you need to validate a module concept before committing a lot of engineering time. Second, using AI to help with accessibility improvements in templates can speed up the process of making interfaces more inclusive.</p>
<p>Finally, I see a lot of potential in using tools like Claude Code to support test creation and maintenance. While I didn’t get to that stage on this project, generating tests, fixing contributed modules, and experimenting with code improvements all look like strong fits for this kind of workflow. The Document Summarizer Tooltip itself could also be directly useful on content-heavy sites that want instant, inline document previews.</p>
<p>If you’d like to explore the code or try the module yourself, the prototype is available on GitHub at <a href="https://github.com/minnur/docs_summarizer_tooltip" target="_blank" rel="noopener noreferrer">https://github.com/minnur/docs_summarizer_tooltip</a>.</p>
<div class="highlight-box">
<p>This post is part of Tag1’s This post is part of our <a href="https://www.tag1.com/insights/?tag=AI#selected"> AI Applied content series </a> content series, where we share how we're using AI inside our own work before bringing it to clients. Our goal is to be transparent about what works, what doesn’t, and what we are still figuring out, so that together, we can build a more practical, responsible path for AI adoption.</p>
</div>
<p><em>Bring practical, proven AI adoption strategies to your organization, let's start a conversation! <a href="https://www.tag1.com/contact/">We'd love to hear from you.</a></em></p>
]]></description>
      <pubDate>Wed, 18 Feb 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Tag1</dc:creator>
      <guid>https://www.tag1.com/blog/building-document-summarizer-tooltip/</guid>
    </item>
    
    
    <item>
      <title>Contributing to Drupal&#39;s Future At Drupal Pivot</title>
      <link>https://www.tag1.com/blog/contributing-to-drupals-future/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<p>Last week I flew to Belgium for <a href="https://www.linkedin.com/posts/drupal-association_drupal-drupalpivot-unconference-activity-7421879643085115393-MN7T" target="_blank" rel="noopener noreferrer">Drupal Pivot</a>, an unconference for agency leaders, organized by the <a href="https://www.drupal.org/association" target="_blank" rel="noopener noreferrer">Drupal Association</a>. Over sixty of us gathered in Ghent for two days of raw conversation about where Drupal needs to go. No polished presentations or vendor pitches. Just business owners trying to figure out what happens next.</p>
<p>The event operated on Chatham House Rules, which means I can share what I learned but not attribute specific ideas to specific people. What follows is my synthesis of themes that emerged, filtered through my own perspective. Other attendees will have their own takeaways.</p>
<p></p><figure class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/content-feature/drupal-pivot-group.jpg" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/content-feature/drupal-pivot-group.jpg" alt="Group attendee photo for Drupal Pivot 2026 in Ghent." />
</a>
<figcaption class="italic text-[var(--text-color)]">Attendees of the first Drupal Pivot event in Ghent, BE. Photo Credit: <a href="https://www.flickr.com/photos/195339951@N02" target="_blank" rel="noopener noreferrer">Joris Vercammen</a> (flickr)</figcaption>
</figure><p></p>
<h2>The Business Model Problem</h2>
<p>The most intense discussions centered on how AI is challenging traditional agency economics. Some clients are expecting lower prices because &quot;AI makes everything cheaper,&quot; even when actual costs tell a different story. Developers using AI should be able to build complex Drupal sites dramatically faster, but advanced models burn through expensive tokens quickly, and quality still requires senior-level review. The math doesn't simplify the way some assume it will.</p>
<p>Nobody has solved this yet. The most honest framing I heard was: treat clients as partners in figuring it out, because we're all learning what AI actually costs in practice. Various experiments are underway with value-based pricing, fixed price with variable scope, and subscription models. No consensus has emerged.</p>
<p>This reminds me of the &quot;outsource everything&quot; wave from years ago. We still feel downward price pressure from that movement, too. What we learned: cheap and fast often means expensive and slow once you factor in the rework. The clients who came to us after failed projects understood that. I suspect the same pattern will play out with projects that exclusively focus on lowering costs with AI-generated vibe code.</p>
<h2>The Rise of Disposable Code</h2>
<p>Some agencies are leaning hard into &quot;disposable code&quot;. If AI lets you build a complete website in a week, why chase perfection? If the client needs something different next month, just rebuild it. Move fast, and don't look back.</p>
<p>This may be a legitimate strategy for certain markets. I'm not dismissing it.</p>
<p>But it's not ours.</p>
<p>Tag1 builds things that last and work well. The disposable approach may work when what you're building is truly standalone, when nothing else depends on it, when institutional knowledge doesn't live in how it's configured. It quickly breaks down when your system is load-bearing infrastructure, when other systems integrate with it. When the cost of &quot;just rebuild it&quot; includes weeks of re-integration, testing, and rediscovering why the previous version made certain decisions.</p>
<p>Our clients tend to be in that second category. We rescue failed Drupal projects as often as we build greenfield sites. We've seen what breaks when speed of delivery and lower hourly rates are prioritized over a maintainable architecture. Prototypes should be disposable. Production infrastructure should not.</p>
<h2>Repositioning Drupal</h2>
<p>A major debate emerged about Drupal's target audience. Should we keep pitching to marketers, or pivot toward technical decision-makers like CTOs and enterprise architects?</p>
<p>The argument for the shift: marketing websites are increasingly commoditized. AI can vibe-code a marketing microsite. The real stability is in business-critical systems — intranets, support platforms, e-learning, complex integrations. These clients are less likely to cut budgets because the systems are core infrastructure, not discretionary marketing spend. And technical buyers naturally appreciate Drupal's depth.</p>
<p>The emerging consensus wasn't to abandon marketers entirely, but to stop leading with marketing use cases. Lead with business-critical applications where Drupal excels. Let marketing capabilities be a supporting benefit rather than the headline.</p>
<p>This validates where Tag1 has always focused. We do performance engineering and infrastructure for systems that can't go down. That positioning looks stronger, not weaker, as AI commoditizes the simpler work.</p>
<h2>Digital Sovereignty</h2>
<p>With shifting geopolitics and growing concerns about U.S. cloud dependency, digital sovereignty came up repeatedly as an opportunity for Drupal, especially in Europe. Organizations worry about routing data through U.S. jurisdiction. Compliance requirements are tightening.</p>
<p>Drupal's story here is strong: it is open source, it can run on-premises, there are no hidden dependencies on proprietary services, and you have full control over your data. This matters beyond just regulatory compliance. It's about organizations maintaining genuine ownership of their infrastructure and the ability to switch providers without rebuilding from scratch. And it's about being able to invest in your future.</p>
<p>Open source has always been about working together to make things better. When you build on Drupal, you're not just licensing software — you're joining an ecosystem where improvements flow back to everyone. That shared investment model is the opposite of vendor lock-in, and it's increasingly valuable as organizations think harder about long-term technology risk.</p>
<h2>Looking Forward</h2>
<p>Drupal has survived 25 years by adapting and leading. The fact that agency leaders are gathering to honestly confront challenges rather than pretending everything is fine gives me confidence we'll figure this out too.</p>
<p>There are big questions to consider:</p>
<ul>
<li>Will quality continue to command a premium when AI makes mediocre work cheaper and faster?</li>
<li>How do we price work when the hours become unpredictable?</li>
<li>What does junior developer training look like when AI changes the learning curve?</li>
</ul>
<p>Nobody has definitive answers yet. But asking the questions openly, together, is how the Drupal community has always navigated change. I'm glad to be part of that conversation.</p>
<aside class="callout-cta my-10 p-4 xl:p-8 bg-purple text-white" style="--link-color: var(--color-turquoise); --decoration-color: var(--color-turquoise);">
    <div class="py-11 xl:py-8 px-4 xl:px-8 frame frame-white-35">
        <div class="relative z-10 mt-0 sm:text-xl">Conversations like the ones at Drupal Pivot are exactly what keep Drupal strong. At Tag1, we’ve spent years building and supporting some of the most mission‑critical Drupal systems in the world. <a class="group underline hover:no-underline focus:no-underline" href="https://www.tag1.com/contact/">If you want to dig deeper, lets talk.<span aria-hidden="true"> ></span></a></div>
    </div>
</aside>
<p>Image by <a href="https://www.flickr.com/photos/195339951@N02/" target="_blank" rel="noopener noreferrer">Joris Vercammen</a> from <a href="https://www.flickr.com/photos/195339951@N02/55063454721/" target="_blank" rel="noopener noreferrer">flickr</a></p>
]]></description>
      <pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Jeremy Andrews</dc:creator>
      <guid>https://www.tag1.com/blog/contributing-to-drupals-future/</guid>
    </item>
    
    
    <item>
      <title>Using AI to Move a 10-Year-Old Drupal Core Issue Forward</title>
      <link>https://www.tag1.com/blog/moving-10-year-old-drupal-issue-forward-with-ai/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
        <p>At Tag1, we believe in proving AI within our own work before recommending it to clients. This post is part of our <a href="https://www.tag1.com/insights/?tag=AI#selected">AI Applied content series</a>, where team members share real stories of how they're using Artificial Intelligence and the insights and lessons they learn along the way.  Here, <a href="https://www.tag1.com/team/#charles-tanton">Charles Tanton</a> (Software Engineer) explores how AI supported his improvements to the Optional Field Widget Ordering issue in Drupal core by accelerating progress on a long-stalled 10-year old problem.</p>
    </div>
</div>
</div>
<h2>Project Summary</h2>
<p>This project focused on using AI to help resolve a long-standing Drupal core issue: <a href="https://www.drupal.org/project/drupal/issues/2264739" target="_blank">Allow multiple field widgets to not use tabledrag (#2264739)</a>. The goal was simple but impactful: make it possible to disable drag-and-drop ordering for specific multi-value field widgets, instead of forcing all of them to be orderable. For years, the only practical option was a brittle patch that touched many core files and often broke on core updates, creating recurring maintenance work that nobody enjoyed. By pushing this core fix forward with AI as a coding partner, the aim is to remove that maintenance burden for good and give site builders more control over the form UX.</p>
<h2>The Challenge</h2>
<p>The core problem was that Drupal automatically renders multi-value fields in a table with tabledrag behavior, even when reordering is not needed. That table-based structure makes theming harder, complicates responsive layouts, and adds JavaScript overhead for no real benefit in many use cases. Our only workaround was a large, fragile patch from this very issue that had to be kept in sync across Drupal core releases by hand.</p>
<p>This AI Applied project set out to change that by getting a clean, configurable solution into core. The work included writing an improved merge request, updating the issue summary, and adding thorough test coverage, all geared toward making the change easy to understand, review, and eventually commit.</p>
<h2>The Technical Overview</h2>
<p>The first step was using AI to explore alternatives to the existing proposed fix and to see if there was a better architectural direction. After looking at the options together with AI, we confirmed that the original &quot;orderable&quot; setting approach was still the best fit, and then focused on strengthening it, especially around configuration schema. A key enhancement was the introduction of a shared <code>field.widget.settings.base</code> config schema so widgets could inherit the new <code>orderable</code> boolean cleanly instead of each re-defining it.</p>
<p>Across the project, AI helped with:</p>
<ul>
<li>Drafting a clearer, more complete issue summary that explains the problem, motivation, and proposed resolution.</li>
<li>Building out comprehensive test coverage for affected multi-value widgets so regressions are caught automatically.</li>
<li>Updating Twig templates and styling for the new non-orderable rendering path, including a simplified <code>field-multiple-value-without-order-form</code> template.</li>
<li>Getting unstuck on Git and GitLab workflows specific to Drupal core contributions, including issue forks and merge requests.</li>
</ul>
<p>AI also sped up UI work by letting me paste screenshots into the coding environment so it could adjust CSS more accurately, instead of iterating blindly. Here’s an example:</p>
<p></p><figure class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/content-feature/image-to-claude1.png" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/content-feature/image-to-claude1.png" alt="Screenshot of the CSS that was uploaded into Claude to speeed up UI work." />
</a>
<figcaption class="italic text-[var(--text-color)]">Figure 1: Screenshot with instructions uploaded to Claude to speed up UI work</figcaption>
</figure><p></p>
<p></p><figure id="fig2" class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/content-feature/image-from-claude.png" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/content-feature/image-from-claude.png" alt="Screenshot of the CSS that claude delivered as a result of the screenshot upload reflecting the updates made to the UI." />
</a>
<figcaption class="italic text-[var(--text-color)]">Figure 2: The new UI provided by Claude</figcaption>
</figure><p></p>
<p>Over time, I shifted from an earlier extension to a smoother Claude Code setup, supported by installing the <code>ddev-claude-code</code> integration so the assistant could run directly inside the DDEV container with GitLab CLI access.</p>
<h2>AI Workflow</h2>
<p>A few workflow patterns turned out to be especially helpful on this project:</p>
<p><strong>Dedicated context folder</strong><br />
I added a <code>.claude</code> folder in the repo to hold plans, reference snippets, and docs like the Drupal config schema guide. This let me carry context between sessions and ask the AI to &quot;open and update&quot; specific plan files instead of re-explaining everything.</p>
<p><strong>Plan-driven development</strong><br />
For larger theming or testing tasks, I asked the AI to first write a plan to a file (for example, <code>@.claude/theming-plan-22-nov.md</code>), then execute it step by step, pausing after each item for review. That structure made it much easier to course-correct early rather than cleaning up after a big batch of code.</p>
<p><strong>Voice prompts for speed</strong><br />
Using dictation for prompts in the terminal helped reduce friction for small, repetitive questions or instructions. It was surprisingly effective for &quot;talking through&quot; next steps in a more natural way.</p>
<p><strong>Deep reasoning prompts</strong><br />
Adding a keyword like &quot;ultrathink&quot; in key prompts encouraged the AI to reason more thoroughly before proposing code, which was particularly useful for tricky config and test design work.</p>
<p>On top of that, the <code>ddev-claude-code</code> installation was straightforward and lives right alongside normal Drupal tooling:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">composer</span> config extra.drupal-scaffold.allowed-packages <span class="token parameter variable">--json</span> <span class="token parameter variable">--merge</span> <span class="token string">'["drupal/claude_code"]'</span>
<span class="token function">composer</span> require <span class="token parameter variable">--dev</span> drupal/claude_code
ddev add-on get FreelyGive/ddev-claude-code
ddev restart
ddev claude</code></pre>
<h2>Results and Lessons Learned</h2>
<p>The issue is still open, but the state of the work is very different from when this effort began. The implementation now uses a clean configuration schema pattern, has broad widget coverage, and includes extensive tests and documentation in the issue summary. Remaining tasks are mostly about theming and documentation polish before the merge request is ready for final review and potential commit.</p>
<p>This was my first serious use of AI for coding, so it naturally took longer than it would now that I have more experience. The upside is that it gave me a strong foundation for using AI as a regular programming tool, and it has since become part of my daily workflow.</p>
<p>If I were starting this same issue again today, I would:</p>
<ul>
<li>Ask the AI to generate <strong>full test coverage first</strong>, across all relevant widgets, to surface edge cases earlier.</li>
<li>Lean heavily on a <strong>&quot;plan mode&quot;</strong> flow, approving each step in advance before code is written.</li>
<li><strong>Independently run and verify</strong> tests instead of trusting automated &quot;all tests passed&quot; claims.</li>
<li>Keep prompts <strong>short and focused</strong>, especially around test generation, to avoid bloated code that is slow to review.</li>
</ul>
<h2>Real-World Applications</h2>
<p>Contributions to Drupal core like this one tend to benefit every Drupal site over time. In this case, having an &quot;Orderable&quot; toggle for multi-value widgets will simplify maintenance by removing the need for a large, fragile patch and will improve form UX options for site builders. It's the kind of change that quietly pays off for years through cleaner upgrades and more flexible theming.</p>
<p>More broadly, this project is a concrete example of how teams can use AI to move long-standing open-source issues forward. It is especially valuable where the work involves a mix of architecture decisions, broad test coverage, and tedious but important updates across multiple components.</p>
<h2>Expert Oversight Is Non-Negotiable</h2>
<p>One of the most important lessons from this project is that expert oversight is essential. I saw multiple cases where the AI made questionable choices or leaned on weak assumptions in the code, which I only caught because I was reading closely and testing manually. Without that attention, the &quot;help&quot; would have turned into extra rework later.</p>
<p>Used well, AI acts as a powerful accelerator: it drafts, refactors, and suggests, while you stay accountable for direction, quality, and correctness. This project helped me build that mindset and gave me the confidence to make AI a normal part of my engineering toolkit. It also reaffirmed how valuable it is to practice with AI on internal or infrastructure-oriented work like this before applying it in higher-risk contexts.</p>
<div class="highlight-box">
<p>This post is part of Tag1’s <a href="https://www.tag1.com/insights/?tag=AI#selected">AI Applied</a> series, where we share how we're using AI inside our own work before bringing it to clients. Our goal is to be transparent about what works, what doesn’t, and what we are still figuring out, so that together, we can build a more practical, responsible path for AI adoption.</p>
</div>
<p><em>Bring practical, proven AI adoption strategies to your organization, let's start a conversation! <a href="https://www.tag1.com/contact/">We'd love to hear from you.</a></em></p>
<p>Image by <a href="https://www.shutterstock.com/g/FrankFrank96" target="_blank" rel="noopener noreferrer">FranFrank96</a> from <a href="https://www.shutterstock.com/image-photo/clean-futuristic-background-featuring-soft-blue-2620784535" target="_blank" rel="noopener noreferrer">Shutterstock</a></p>
]]></description>
      <pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Charles Tanton</dc:creator>
      <guid>https://www.tag1.com/blog/moving-10-year-old-drupal-issue-forward-with-ai/</guid>
    </item>
    
    
    <item>
      <title>Speed up your testing with Deuteros</title>
      <link>https://www.tag1.com/blog/speed-up-testing-with-deuteros/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=planet_drupal</link>
      <description><![CDATA[<div style="--border-color: rgba(21, 120, 124, 0.5);"> 
<div class="border border-[var(--border-color)] relative my-12 " x-data="{
     boxId: $id('summary-box'),
     buttonId: $id('summary-box-button'),
     contentId: $id('summary-box-content'),
     isCollapsible: true,
     isOpen: false,
     toggle() {
         if (this.isCollapsible) {
             this.isOpen = !this.isOpen;
         }
     },
    }">
    <button type="button" x-bind:id="buttonId" class="w-full py-4 pl-4 flex items-center text-left" x-bind:class="{ 'pr-16': isCollapsible, 'pr-4': !isCollapsible }" x-on:click="toggle()" x-on:keyup.enter.prevent="toggle()" x-on:keyup.space.prevent="toggle()" x-bind:aria-expanded="isOpen" x-bind:aria-controls="contentId" x-bind:disabled="!isCollapsible">
        <h2 class="component text-xl font-text font-medium text-[var(--accent-color)]">Take Away</h2>
        <span x-show="isCollapsible" class="absolute top-1/2 right-4 -translate-y-1/2 flex items-center text-[var(--accent-color)] transition-transform duration-200" x-bind:class="{ 'rotate-180': isOpen }">
            <svg width="16" height="24" viewBox="0 0 16 24" xmlns="http://www.w3.org/2000/svg" class="rotate-90" aria-hidden="true">
                <path d="M0.753906 19.5016L4.49456 23.2456L15.7549 11.7451L4.49456 0.244572L0.753905 3.98965L8.41815 11.7461L0.753906 19.5016Z" fill="currentColor"></path>
            </svg>
        </span>
    </button>
    <div x-bind:id="contentId" role="region" x-bind:aria-labelledby="buttonId" x-show="isOpen || !isCollapsible" x-transition:enter="transition-opacity duration-500 ease-out motion-reduce:duration-0" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="pl-4 pr-20 md:pr-25 2xl:pr-32 pt-4 pb-4 text-lg font-text text-[var(--accent-color)] text-formatted">
        At Tag1, we believe in proving AI within our own work before recommending it to clients.  This post is part of our <a href="https://www.tag1.com/insights/?tag=AI#selected">AI Applied</a> content series, where team members share real stories of how they're using Artificial Intelligence and the insights and lessons they learn along the way.  Here, <a href="https://www.tag1.com/team/#francesco-placella">Francesco Placella</a>, Senior Architect | Technical Lead, explores how AI supported his creation of <a href="https://github.com/deuteros-php/deuteros" target="_blank" rel="noopener noreferrer">Deuteros,</a> an open‑source PHP library that makes Drupal’s entity system far easier to unit test—speeding up test runs by orders of magnitude and now <a href="https://github.com/deuteros-php/deuteros" target="_blank" rel="noopener noreferrer">freely available</a> for anyone to use and contribute to.
    </div>
</div>
</div>
<h2>Speed up your testing with Deuteros</h2>
<p>At <a href="https://www.tag1.com/">Tag1</a> we like performance.</p>
<p>We also like not being yelled at because a recently released production build introduced a major regression and suddenly the dev team has to scramble to hot-fix the issue.</p>
<p>Our industry has settled for quite a while now on a solid strategy to avoid that scenario: <em>automated testing</em>. It’s not a silver bullet but, when <em>done right</em>, it can hugely increase confidence in making changes to large and/or complex codebases, where even minimal tweaks can sometimes trigger very hard to predict side-effects.</p>
<h2>Doing it Right</h2>
<p>Automated testing is a vast and complex subject, with some grey areas, especially around terminology; however, there’s a key principle it’s a good idea to stick to: <em>always run your automated tests after any change.</em></p>
<p>If you are embracing <a href="https://en.wikipedia.org/wiki/Test-driven_development" target="_blank" rel="noopener noreferrer">Test Driven Development</a>, you’ll have written new tests covering the changes you are working on before implementing them: this will require running tests multiple times before the task is over. However, even if you are following a more traditional approach, writing test coverage <em>after</em> implementing changes, you will still end up running tests very often.</p>
<p>Admittedly, in the cases above we are talking about a very limited set of tests; however, if tests run slowly, even those will add a significant overhead to development time.</p>
<p>Another widely accepted best practice is to run the entire test suite before merging a change into the project’s repository. This both ensures that the new code behaves as expected and that no regression was introduced in the process. Once again, if tests run slowly, it may take a long time to get feedback, adding further overhead due to context switching.</p>
<p>Aside from the impact on developer experience, which at Tag1 is taken very seriously, the larger the overhead, the harder it is to justify to clients the amount of time spent implementing a change. I guess I don’t need to describe the cascading effects.</p>
<h2>The Test Pyramid</h2>
<p>An approach that’s very effective in addressing test speed issues is
<a href="https://martinfowler.com/articles/practical-test-pyramid.html#TheTestPyramid" target="_blank" rel="noopener noreferrer">the test pyramid</a>: without going too much into detail, the idea is that a test suite should be organized in a pyramidal shape with multiple layers getting smaller and slower to run as they approach the top.</p>
<p>This commonly translates into having:</p>
<ul>
<li>
<p>A large amount of fast-running <em>unit tests</em>, covering individual aspects of the codebase.</p>
</li>
<li>
<p>A medium amount of integration tests combining multiple units into a consistent (sub)system and testing its behavior.</p>
</li>
<li>
<p>A small amount of <em>end-to-end (E2E) tests</em> pulling everything together and testing the business-critical behaviors of the entire codebase.</p>
<p></p><figure id="fig1" class="component my-10 grid gap-4">
<a href="https://www.tag1.com/img/content-feature/test-pyramid.svg" target="_blank">
<img class="mx-auto  border-[var(--border-color)] border-2" src="https://www.tag1.com/img/content-feature/test-pyramid.svg" alt="A pyramid diagram showing units at the bottom of taking up the largest space, integration with second largest and E2E at the tip and smallest portion of the pyramid" />
</a>
<figcaption class="italic text-[var(--text-color)]">Figure 1: The Test Pyramid</figcaption>
</figure><p></p>
</li>
</ul>
<h2>What about Drupal?</h2>
<p>At Tag1 we do many things, but <a href="https://www.tag1.com/drupal/">Drupal has a special place in our hearts</a>. However, running Drupal tests quickly has often been challenging.</p>
<p>Support for automated tests was initially added to <a href="https://www.drupal.org/about/drupal-7/d7eol/partners" target="_blank" rel="noopener noreferrer">Drupal 7</a> and, as awesome as it was at the time, it involved running a full site installation in the test setup phase before being able to perform a single assertion. The Simpletest framework focused mainly on UI testing and, even if it was sort of possible to use it to write unit tests, it was like driving to your favorite movie theater during rush hour just to check whether the car battery was wired properly. As you can imagine, it took time.</p>
<p>In Drupal 8 and above the situation vastly improved with the adoption of the
<a href="https://phpunit.de/index.html" target="_blank" rel="noopener noreferrer">PhpUnit</a> testing framework. Multiple types of tests were introduced:</p>
<ul>
<li><em>Browser</em> tests (and their companion WebDriver tests) are the successors to the original Simpletests and are primarily meant to perform UI testing. These are a very good fit to implement E2E tests, as they cover the full request/response scope, albeit being slow, as they still require an installed site and a full Drupal bootstrap.</li>
</ul>
<blockquote>
<p>It should be noted that the introduction by <a href="https://www.tag1.com/team/#:~:text=moshe%20weitzman" target="_blank" rel="noopener noreferrer">Moshe Weitzman</a> of <a href="https://medium.com/massgovdigital/introducing-drupal-test-traits-9fe09e84384c" target="_blank" rel="noopener noreferrer">Drupal Test Traits</a> helped to speed up these tests, as it allows them to run with an existing database, instead of installing an empty site from scratch every time.</p>
</blockquote>
<div class="">
<ul>
<li>
<p><em>Kernel</em> tests run much faster, as they only require installing the subsystems needed to run the test code. These are ideal for Integration testing, as they allow you to pick and choose the parts of Drupal available to the test.</p>
</li>
<li>
<p><em>Unit</em> tests, lightning-fast and narrow-scoped, are what should be used to implement the large majority of the test coverage. By default, they do close to nothing in the setup phase, allowing to test individual codebase units in isolation, by providing <a href="https://martinfowler.com/bliki/TestDouble.html" target="_blank" rel="noopener noreferrer">test doubles</a> of all the objects the test subject interacts with.</p>
</li>
</ul>
<p>So we are good now, right? Well, not exactly. The entity subsystem – which powers users, nodes, media items, and taxonomy terms among others – has historically been problematic when it comes to unit testing, because instantiating an entity object involves instantiating a significant amount of dependencies. In most cases this meant that, <a href="https://drupal.stackexchange.com/questions/255511/strategy-for-unit-testing-classes-that-interact-with-the-entity-api" target="_blank" rel="noopener noreferrer">to write an entity unit test, a Kernel test with all its baggage was needed</a>.
Since entities are very prevalent in any Drupal codebase, this meant that a wide range of test scenarios could not rely on fast unit tests.</p>
<h2>Meet Deuteros</h2>
<p>This issue has been bugging me for a long time now, so I decided to experiment with <a href="https://github.com/deuteros-php/deuteros/pulls?q=is%3Apr+Claude+Code" target="_blank" rel="noopener noreferrer">an AI-assisted</a> solution since long and tedious tasks is something LLMs are supposed to be good at.</p>
<p>The result is <a href="https://github.com/deuteros-php/deuteros" target="_blank" rel="noopener noreferrer">Deuteros</a> – Drupal Entity Unit Test Extensible Replacement Object Scaffolding – a PHP library allowing the use of fast unit tests (extending UnitTestCase, for those in the know) when interacting with entities. And, yes, <a href="https://github.com/deuteros-php/deuteros/blob/b839f093f0aeb69c421bcde7935c54bb0c02b06f/docs/archive/naming.md" target="_blank" rel="noopener noreferrer">Claude helped me come up with the name as well.</a></p>
<p>Deuteros is fully open source and available today. You can explore the project, file issues, or contribute improvements on <a href="https://github.com/deuteros-php/deuteros" target="_blank" rel="noopener noreferrer">GitHub</a>. The repository includes examples, docs, and instructions to start using it right away.</p>
<p>The ability to leverage UnitTestCase is achieved by:</p>
<ul>
<li>Allowing the creation of test doubles implementing various entity and field interfaces. These can be passed around in test code as if they were real entity objects.</li>
</ul>
<pre class="language-php"><code class="language-php"><span class="token keyword">use</span> <span class="token package">Deuteros<span class="token punctuation">\</span>Double<span class="token punctuation">\</span>EntityDoubleFactory</span><span class="token punctuation">;</span>
<span class="token keyword">use</span> <span class="token package">Deuteros<span class="token punctuation">\</span>Double<span class="token punctuation">\</span>EntityDoubleDefinitionBuilder</span><span class="token punctuation">;</span>

<span class="token keyword">class</span> <span class="token class-name-definition class-name">MyServiceTest</span> <span class="token keyword">extends</span> <span class="token class-name">TestCase</span> <span class="token punctuation">{</span>

  <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">testMyService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token punctuation">{</span>
    <span class="token comment">// Get a factory (auto-detects PHPUnit or Prophecy)</span>
    <span class="token variable">$factory</span> <span class="token operator">=</span> <span class="token class-name static-context">EntityDoubleFactory</span><span class="token operator">::</span><span class="token function">fromTest</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Create an entity double</span>
    <span class="token variable">$entity</span> <span class="token operator">=</span> <span class="token variable">$factory</span><span class="token operator">-></span><span class="token function">create</span><span class="token punctuation">(</span>
      <span class="token class-name static-context">EntityDoubleDefinitionBuilder</span><span class="token operator">::</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'node'</span><span class="token punctuation">)</span>
        <span class="token operator">-></span><span class="token function">bundle</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'article'</span><span class="token punctuation">)</span>
        <span class="token operator">-></span><span class="token function">id</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">)</span>
        <span class="token operator">-></span><span class="token function">label</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'Test Article'</span><span class="token punctuation">)</span>
        <span class="token operator">-></span><span class="token function">field</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'field_body'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'Article content here'</span><span class="token punctuation">)</span>
        <span class="token operator">-></span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Use it in your test</span>
    <span class="token variable">$myService</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MyService</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$myService</span><span class="token operator">-></span><span class="token function">doStuff</span><span class="token punctuation">(</span><span class="token variable">$entity</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// These assertions would all pass</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertInstanceOf</span><span class="token punctuation">(</span><span class="token class-name static-context">EntityInterface</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token variable">$entity</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'node'</span><span class="token punctuation">,</span> <span class="token variable">$entity</span><span class="token operator">-></span><span class="token function">getEntityTypeId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'article'</span><span class="token punctuation">,</span> <span class="token variable">$entity</span><span class="token operator">-></span><span class="token function">bundle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token number">42</span><span class="token punctuation">,</span> <span class="token variable">$entity</span><span class="token operator">-></span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'Article content here'</span><span class="token punctuation">,</span> <span class="token variable">$entity</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'field_body'</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token punctuation">}</span>
</code></pre>
<ul>
<li>Allowing the instantiation of real entity objects while providing test doubles for dependencies such as field items and Entity Field API services. This allows unit testing of custom entity logic such as the one typically added to <a href="https://www.drupal.org/node/3191609" target="_blank" rel="noopener noreferrer">bundle classes</a>.</li>
</ul>
<pre class="language-php"><code class="language-php"><span class="token keyword">class</span> <span class="token class-name-definition class-name">MyNodeTest</span> <span class="token keyword">extends</span> <span class="token class-name">TestCase</span> <span class="token punctuation">{</span>

  <span class="token keyword">private</span> <span class="token class-name type-declaration">SubjectEntityFactory</span> <span class="token variable">$factory</span><span class="token punctuation">;</span>

  <span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function-definition function">setUp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token punctuation">{</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token property">factory</span> <span class="token operator">=</span> <span class="token class-name static-context">SubjectEntityFactory</span><span class="token operator">::</span><span class="token function">fromTest</span><span class="token punctuation">(</span><span class="token variable">$this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token property">factory</span><span class="token operator">-></span><span class="token function">installContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">protected</span> <span class="token keyword">function</span> <span class="token function-definition function">tearDown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token punctuation">{</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token property">factory</span><span class="token operator">-></span><span class="token function">uninstallContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function-definition function">testNodeCreation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword return-type">void</span> <span class="token punctuation">{</span>
    <span class="token variable">$node</span> <span class="token operator">=</span> <span class="token variable">$this</span><span class="token operator">-></span><span class="token property">factory</span><span class="token operator">-></span><span class="token function">create</span><span class="token punctuation">(</span><span class="token class-name static-context">Node</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>
      <span class="token string single-quoted-string">'nid'</span> <span class="token operator">=></span> <span class="token number">42</span><span class="token punctuation">,</span>
      <span class="token string single-quoted-string">'type'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'article'</span><span class="token punctuation">,</span>
      <span class="token string single-quoted-string">'title'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'Test Article'</span><span class="token punctuation">,</span>
      <span class="token string single-quoted-string">'body'</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token string single-quoted-string">'value'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'Body text'</span><span class="token punctuation">,</span> <span class="token string single-quoted-string">'format'</span> <span class="token operator">=></span> <span class="token string single-quoted-string">'basic_html'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token string single-quoted-string">'status'</span> <span class="token operator">=></span> <span class="token number">1</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token comment">// Real Node instance with DEUTEROS field doubles.</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertInstanceOf</span><span class="token punctuation">(</span><span class="token class-name static-context">Node</span><span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'node'</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-></span><span class="token function">getEntityTypeId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'article'</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-></span><span class="token function">bundle</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'Test Article'</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'title'</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token variable">$this</span><span class="token operator">-></span><span class="token function">assertSame</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'Body text'</span><span class="token punctuation">,</span> <span class="token variable">$node</span><span class="token operator">-></span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string single-quoted-string">'body'</span><span class="token punctuation">)</span><span class="token operator">-></span><span class="token property">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>

<span class="token punctuation">}</span>
</code></pre>
<h2>How AI Enabled a New, Faster Class of Drupal Tests</h2>
<p>This sounds all good but does it actually work? Well, the library is already being adopted by some Tag1 client projects and it seems to be working nicely. To get an idea of the speed gains Deuteros allows, we implemented some benchmark tests: <a href="https://github.com/deuteros-php/deuteros/blob/4f7dcc8b0e21623b696b1a92fb1cf7e8d5e14a36/tests/Performance/NodeOperationsBenchmarkTrait.php" target="_blank" rel="noopener noreferrer">a simple test trait performing a few node manipulation operations</a> was used to measure the performance of multiple test types: in my DDEV local environment, 1000 iterations of the test complete in less than 1 second with Unit tests while they take <em>~12 minutes</em> with Kernel tests – details and replication steps available <a href="https://github.com/deuteros-php/deuteros/pull/1" target="_blank" rel="noopener noreferrer">here</a>. Of course, <a href="https://github.com/paratestphp/paratest" target="_blank" rel="noopener noreferrer">running tests in parallel</a> could likely reduce the gap by a factor of ten, but I’ll still take the former, thanks.</p>
<h2>What’s Next?</h2>
<p>Complex projects very often require patches to implement new functionality not yet available in the Drupal codebase, or apply bug fixes before they are eventually released. Applying patches for all intents and purposes means running a fork of the original project, which means there is no guarantee that, even if individual patches don’t break tests, their combined behavior won’t introduce regressions. Ideally, to be fully confident about the codebase quality, one would need to run the entire Drupal core + contrib test suite and confirm that nothing breaks. Currently, even on the <a href="https://drupal.org/" target="_blank" rel="noopener noreferrer">drupal.org</a> infrastructure – which is fully optimized for the job – on average you need to wait for <a href="https://git.drupalcode.org/project/drupal/-/merge_requests/14052/pipelines" target="_blank" rel="noopener noreferrer">~15 minutes</a> to get test results: adding the test suites of a hundred modules increases that by a lot. Running this on every pull request or commit push does not seem viable at the moment.</p>
<p>Deuteros was written as a standalone PHP library, so that it could easily be adopted without having to make significant changes to the host project. If it were widely adopted both in Drupal core and in contrib-land, running all unit test suites could probably happen in less than one minute, increasing the confidence in our changes by a lot, and possibly reducing our energy footprint as a bonus. Should we try?</p>
<aside class="callout-cta my-10 p-4 xl:p-8 bg-purple text-white" style="--link-color: var(--color-turquoise); --decoration-color: var(--color-turquoise);">
    <div class="py-11 xl:py-8 px-4 xl:px-8 frame frame-white-35">
        <div class="relative z-10 mt-0 sm:text-xl">Try faster, easier testing in your Drupal projects with Deuteros. It’s fully open source and ready to use today, explore the code, run the examples, or contribute your own ideas. <a class="group underline hover:no-underline focus:no-underline" href="https://github.com/deuteros-php/deuteros" target="_blank">Get started on GitHub<span aria-hidden="true"> ></span></a></div>
    </div>
</aside>
<p>Image by
<a href="https://www.pexels.com/@googledeepmind/" target="_blank" rel="noopener noreferrer">Google DeepMind</a>
from
<a href="https://www.pexels.com/photo/an-artist-s-illustration-of-artificial-intelligence-ai-this-image-visualises-the-input-and-output-of-neural-networks-and-how-ai-systems-perceive-data-it-was-created-by-rose-pilkington-17485706/" target="_blank" rel="noopener noreferrer">pexels.</a></p>
</div>
]]></description>
      <pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate>
      
      
      
        
      
      <dc:creator>Francesco Placella</dc:creator>
      <guid>https://www.tag1.com/blog/speed-up-testing-with-deuteros/</guid>
    </item>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
  </channel>
</rss>
