We've developed a huge platform for over half a decade to host the official sites of the United Nations' member countries, and we've never really posted about it. It's 170 Microsites, built on top of a single code base and database. Having the word "Micro" before Microsites is really stretching the definition. There's nothing micro about them. Head over to your favorite UN member country to see it in action. Here are a few completely random ones:
Democratic People's Republic of Korea
People's Democratic Republic of Algeria
Republic of the Congo (Not to be confused with "Democratic Republic of the Congo").
This post is a reflection of why we always try to avoid such architecture, and why we almost always still end up going with that level of effort; how much it should cost (less than having 140 independent sites); the common pitfalls we've gathered over the years (many).
What are Microsites
Microsites are sites that share the same structure and purpose, usually under one organization, but are meant to serve different branches, offices, or audiences. They can have their own content, languages, and editors, and sometimes even a different look. In a setup like the UN's, each country office site is technically similar, but each is managed by a different local team, with its own priorities and style. They're independent enough to feel separate, yet still connected through a shared foundation.
The term "microsite" is misleading. Once you have hundreds of them, each with custom permissions, content, and translations, there's nothing "micro" left about it. It's a big system pretending to be many small ones.
The Options - From Worse To Bad
None of these options are easy. Microsites are the kind of problem where every path feels wrong; you just choose which pain you can live with.
On October 5, 2023, the W3C published WCAG 2.2 as an official web standard. While WCAG 2.1 remains valid and widely referenced, WCAG 2.2 introduces nine new success criteria and removes one obsolete requirement. These changes reflect a deeper understanding of mobile accessibility, cognitive disabilities, and focus management.
Drupal-blue LEDs, controllable through a REST API and a Drupal website. Photo by Phil Norton.
It's Christmas Eve, and Phil Norton is controlling his Christmas lights with Drupal. You can visit his site, pick a color, and across the room, a strip of LEDs changes to match. That feels extra magical on Christmas Eve.
I like how straightforward his implementation is. A Drupal form stores the color value using the State API, a REST endpoint exposes that data as JSON, and MicroPython running on a Pimoroni Plasma board polls the endpoint and updates the LEDs.
I also didn't realize you could buy light strips where each LED can be controlled individually. That alone makes me want to up my Christmas game next year.
But addressable LEDs are useful for more than holiday decorations. You could show how many people are on your site, light up a build as it moves through your CI/CD pipeline, flash on failed logins, or visualize the number of warnings in your Drupal logs. This quickly stops being a holiday decoration and starts looking like a tax-deductible business expense.
Beyond the fun factor, Phil's tutorial does real teaching. It uses Drupal features many of us barely think about anymore: the State API, REST resources, flood protection, even the built-in HTML color field. It's not just a clever demo, but also a solid tutorial.
The Drupal community gets stronger when people share work this clearly and generously. If you've been curious about IoT, this is a great entry point.
Merry Christmas to those celebrating. Go build something that blinks. May your deployments be smooth and your Drupal-powered Christmas lights shine bright.
Advent Calendar day 24 - Listening Like a Dungeon Guidejames
It's Christmas eve, and time to open our final Advent Calendar door. What better way to relax before the holiday festivities than by playing some tabletop games for all the family? Maybe a role playing game like Dungeons and Dragons as suggested by AmyJune!
Listening Like a Dungeon Guide
RPG Skills for Stakeholder Work
At DrupalCamp Colorado 2025, I caught a session by Eric Davila titled Listening Like a Dungeon Master: Leveraging RPG Skills for Stakeholder Magic. The talk explored an idea that feels especially relevant to Drupal projects of all sizes: successful collaboration depends far more on…
You're tabbing through a form, reading field labels before you decide what to enter. You tab into a dropdown menu just to see what options are available. The instant it receives focus - before you've even opened it - the page suddenly redirects to a completely different page. You didn't select anything, didn't press Enter, just tabbed through. Now you're disoriented and have to navigate back to find your place.
Lee Robinson, who works at Vercel, spent $260 in AI coding agent fees to migrate Cursor's marketing site away from Sanity, their headless CMS, to Markdown files. That number should unsettle anyone who builds or sells content management systems for a living. His reasoning: "With AI and coding agents, the cost of an abstraction has never been higher". He argued that a CMS gets in the way of AI coding agents.
Knut Melvær, who works at Sanity, the very CMS Lee abandoned, wrote a rebuttal worth reading. He pointed out that Lee hadn't actually escaped the complexity of a CMS. Lee still ended up with content models, version control, and user permissions. He just moved them out of the CMS and distributed them across GitHub, Vercel, and custom scripts. That reframing is hard to unsee.
Meanwhile, the broader momentum is undeniable. Lovable, the AI-first website builder, went from zero to $200 million in annual recurring revenue in twelve months. Users prompt what they want and Lovable generates complete, production-ready applications.
Ask me again in two years, but today's Lovable is not a CMS replacement. So the real question isn't whether CMSes are becoming obsolete. It's who they're for.
Historically, the visible layer of a CMS, the page builders and content creation workflows, is where most people spend their time. But the invisible layer is what makes organizations trust the system: structured content models, permission systems, audit trails, web service APIs, caching layers, translation workflows, design systems, component libraries, regulatory compliance and more. A useful way to think about a CMS is that roughly 30 percent is visible layer and 70 percent is invisible layer.
For more than twenty years, the visible layer was where the work started. You started from a blank state - a page builder or a content form - then wrote the headline, picked an image, and arranged the layout. The visible layer was like the production floor.
AI changes this dynamic fundamentally. You can now prompt a landing page into existence in under a minute, or generate ten variations and pick the best one. The heavy lifting of content creation is moving to AI.
But AI gets you most of the way, not all the way. The headline is close but not quite right, or there is a claim buried in paragraph three that is technically wrong. Someone still needs to review, adjust, and approve the result.
So the visible layer still matters, but it serves a different purpose. It's where humans set direction at the start and refine the result at the end. AI handles everything in between.
A page builder becomes a refinement tool rather than a production tool. You still need the full UI because someone has to review, adjust, and approve what AI generates. You can try to prompt all the way to the finish line, but for the last mile, many people will still prefer a UI.
What happens to the invisible layer? Its job shifts from "content management" to "context management". It provides what AI needs to do the job right: brand rules, compliance constraints, content relationships, approval workflows. The system becomes more powerful while requiring less manual configuration.
So my base case for the future of CMS is simple: AI handles eighty percent of the work. Humans handle the remaining twenty by setting direction at the start, and refining, approving, and taking responsibility at the end.
This is why Drupal is not standing still. We recently launched Drupal Canvas 1.0 and one of its top priorities for 2026 is maturing AI-driven page generation. As that work progresses, Canvas could become AI-first by default. Watching it come together has been one of the most exciting things I've worked on in years. We're far from done, but the direction feels right.
Lee proved that a skilled developer with AI coding agents can rebuild a marketing site in a weekend for $260. That is genuinely remarkable. But it doesn't prove that every organization will abandon their CMS.
CMSes have to evolve. They have to become a reliable foundation that both humans and AI agents can build on together. The visible layer shifts from where you create to where you refine. The invisible layer does more work but doesn't disappear. Someone still has to direct the system and answer for it when things go wrong.
Drupal's Views module is wonderful for listing content, but what should you show when you have nothing to list? Everybody loses if a journey ends there: your visitor has to start again, and you've missed an opportunity to help them. The likes of Amazon and eBay show alternative results after more precise matches for a search, even if there are some results. Limited results mean a limited chance for your visitor to find what they want, so providing alternative suggestions increases your chance to convert them to satisfied guests.
When search terms on eBay don't produce many exact matches, partial matches are shown to entice users towards relevant products.
Out of the box, you can configure what to show when Drupal can't find any results (sometimes known as the 'empty text') :
Even more usefully, you can include a views listing in this - perhaps to list alternative results with fewer active filters, to maximise the chances of showing visitors something relevant to them:
So far, so good
But I wanted to take this an extra step, to show this alternative set of results, even when the initial view has results, but not enough of them. This required two key changes with custom code, because Drupal will only build and print whatever is configured for the 'No results behaviour' when there really are no results. (Thanks, Captain Obvious! 🫡)
Override the views-view.html.twig template to replace an elseif with distinct if blocks as follows:
Before the change, this has an elseif so when there are any results, the empty text can never show:
After - with two distinct simple if blocks, so both can be output together:
{% if rows -%}
{{ rows }}
{% endif %}
{% if empty -%}
{{ empty }}
{% endif %}
A post-render hook to build the output of that 'No results behaviour' when there are results, but fewer than a desired amount:
/**
* Implements hook_views_post_render().
*/
function MYMODULE_views_post_render(\Drupal\views\ViewExecutableViewExecutable $view, array &$output, \Drupal\views\Plugin\views\cache\CachePluginBase $cache) {
// Add a pre-render that will render the empty area if there are 1-3 results.
if (
$view->id() === 'MY_VIEW_ID' // e.g. 'products'
&& $view->current_display === 'MY_DISPLAY' // e.g. 'page_1'
&& !empty($view->result)
&& count($view->result) <= 6 // Threshold below which to show empty text.
) {
$output['#pre_render'][] = function ($element) {
/** @var \Drupal\views\ViewExecutable $view */
$view = $element['#view'];
// Store this instance, so that the fallback display's equivalent will be
// able to get at what was in the results, to avoid duplicating them.
// @see MYMODULE_views_pre_view()
views_set_current_view($view);
// Build the configured 'No results behaviour'.
$element['#empty'] = $view->display_handler->renderArea('empty', FALSE);
return $element;
};
}
}
This could be in a custom module or theme. I went for building the configured 'No results behaviour', but you could embed something different if, for example, you wanted to show different text for whether there were only a few results, or none at all.
Techy aside: The call toviews_set_current_view()in the code above follows a similar approach to how Attachment displays are aware of their parent display. When views begins executing the display of alternative results, the statically-stored 'current view' is added to an array stack at$view->old_view. We'll make use of that below in the 'Going further' section.
Now when a visitor makes a search on your site but doesn't get anything they like the look of, you present them with potential alternatives. I suggest including some simple text above the alternative suggestions to explain what they are (rather than exact matches for the original search), as demonstrated in the eBay screenshot above. This could just be the first thing set in the 'No results behaviour' of the view, before adding the alternative views display:
Show the right things, and everybody wins! 🏆
We used this idea on a learning platform where users can find content relevant to them. There are only so many lessons available, so to keep visitors engaged it's important to show useful suggestions that didn't match their keywords. The explanatory text in the 'No results behaviour' clarifies which items directly fit the search, and which are extras:
Going further
An optional extension to this idea is to deliberately exclude any results that did come back in the original set. Use a contextual filter (argument) on your view for excluding IDs and a hook_views_pre_view() to act on the alternative results views display. The contextual filter needs to be for the content IDs (or IDs of whatever kind of entity your original list is for), and have both checkboxes in the 'More' section ticked so that it excludes results.
Then the hook_views_pre_view() takes the results from the original ('parent') view and populates that contextual filter with them, so that they can't show up twice. Note that this can only go in a module, not your theme:
/**
* Implements hook_views_pre_view().
*/
function MYMODULE_views_pre_view(\Drupal\views\ViewExecutable $view, $display_id, array &$args) {
if (
$view->id() === 'MY_VIEW_ID'
&& $display_id === 'MY_ALTERNATIVE_DISPLAY' // e.g. 'embed_1'
) {
// Parent view was set by MYMODULE_views_post_render() so that we can
// ensure to exclude any results in the original set, from this fallback.
$parent = end($view->old_view);
if (
$parent
&& $parent->id() === 'MY_VIEW_ID' // e.g. 'products'
&& $parent->current_display === 'MY_DISPLAY' // e.g. 'page_1'
&& !empty($parent->result)
) {
$view->setArguments([
// The 'nid' key should be whatever property identifies results uniquely.
implode('+', array_column($parent->result, 'nid'))
]);
}
}
}
Finally, you might want to check that your alternative display won't just inherit the same exposed filters as your parent display. Tinker with what fits your scenario: perhaps remove some filters entirely, or change their filter identifiers so that they don't get populated from the query string parameters used by the parent display?
This idea probably fits into Dries Buytaert's recently-suggested category of 'Adaptable modules' as it isn't easily generalized. What makes for good alternatives to suggest, and the right empty text configuration, is unique to your situation. But consider this idea a starting point!
Implementing powerful search functionality in Drupal requires more than the default search module. Search API provides flexible search capabilities with support for multiple backends, faceted filtering, and advanced content indexing.
In the video above, you'll learn how to install and configure Search API, create search indexes with custom processors, display search results using Views, implement faceted filtering, and integrate Apache Solr using DDEV for enhanced search.
Advent Calendar day 23 - No more steep learning curve!james
It's the penultimate door of our Advent Calendar, and today we are looking at efforts to make Drupal easier and more intuitive for new users. Emma Horrell - who in addition to her User Experience Manager role the University of Edinburgh, is also UX manager for Drupal Core, and UX research lead for Drupal CMS - explains how UX research is central to Drupal CMS's mission of flattening Drupal's traditionally steep learning curve.
No more steep learning curve! How UX research is making a more user-centric Drupal CMS
Emma outlines how UX in Drupal has evolved from ad-hoc contributions to formalized…
Curious about what makes a CMS migration successful? No, it isn't limited to the transition. Read this blog to learn about ongoing support post-migration.
Today we are talking about The Drupal At-Large Board Seat, What the job entails, and some common misconceptions with guest Fei Lauren. We'll also cover Token Browser as our module of the week.
Versions available: 1.0.0 which works with Drupal core 11.3 or newer
Maintainership
Actively maintained
Security coverage
Test coverage
Number of open issues: 2 open issues, 1 of which is a bug
Usage stats:
3 sites
Module features and usage
The Token Browser module provides a rebuilt version of the standard token browser. Notably, it only renders the first level during the initial request, and then requests deeper levels as needed using all the latest HTMX improvements in Drupal core 11.3
It's worth noting that the initial version requires an alternative theme function be attached to form elements where you want to use the new Token Browser, so it doesn't actually replace the standard version
Also, there seems to be an issue where the HTMX library doesn't load on cached pages, which is the one open issue. I pinged Andy about it and it sounds like he has a fix in the works.
Finally, this module is similar to an older module called Fast Token Browser, but that module was never updated to work with versions of Drupal newer than 7, and relied on jQuery for its AJAX functionality
You're using voice control to navigate a website because typing is painful today. You see a button that says "Submit" - perfect, that's what you need. You say clearly: "Click Submit." Nothing happens. You try again: "Click Submit button." Still nothing. Frustrated, you finally say "Show numbers" to overlay numbers on every interactive element, then speak the number to activate the button.
Did you know that you don't have to build a Drupal site from scratch? Distributions are ready-made starter kits that enable you to launch a functional website in a few hours, rather than weeks. We present 11 proven and full-featured distributions - from e-learning platforms to systems for agriculture. We created two of the distributions at Droptica.
It's the holiday season, and the end of the year always brings a natural pause. People are logging off, taking breaks, and spending time away from screens. Still, it's hard not to feel a small sense of curiosity about what's coming next. Drupal has a way of staying active even during quieter weeks, and that steady rhythm is part of what keeps the community moving forward.
This year brought several key milestones. Drupal Canvas was officially released, offering a new visual editing experience designed to improve usability for site builders. Drupal 11.3.0 introduced performance upgrades, stable navigation tools, and native HTMX support. The first full release of the Drupal CMS distribution made it easier to start with Drupal out of the box. The Drupal AI initiative continued to evolve, with version 1.2 and increased community collaboration on machine learning and automation use cases. And with Drupal 7 officially reaching the end of life, more users migrated forward, strengthening the ecosystem overall.
Looking ahead, early development on Drupal 12 has begun. Conversations are already underway around modernisation, editorial improvements, and more automation. For now, most of us are offline and enjoying a slower pace. But as always, the Drupal community will return with new energy. Let's welcome 2026 and see what the next chapter brings.
We acknowledge that there are more stories to share. However, due to selection constraints, we must pause further exploration for now. To get timely updates, follow us on LinkedIn, Twitter, Bluesky, and Facebook. You can also join us on Drupal Slack at #thedroptimes.
Advent Calendar day 22 - So, I heard we won't need junior devs now we have generative AI?james
Lenny Moskalyk
As we prepare to open the last few doors of our Advent calendar, I think it's a good point to consider our future. A lot of the speakers we've featured so far have many years of experience in Drupal, what about new blood?
As a special treat, today's door features not one talk, but two!
The first one, from DrupalCon Vienna, was a discussion between Hilmar Hallbjörnsson, Jean-Paul Vosmeer, Rachel Lawson, and Lenny Moskalyk, which gave it very much a BoF ("Birds of a feather") feel, which seems appropriate as it was a BoF the previous year that kicked off the initiative they were talking about.
Drupal 11: Controlling LED Lights Using A REST Service
Following on from my article looking at the Pimoroni Plasma 2350 W I decided to do something interesting with the Wifi interface that would connect to a Drupal site.
The firmware of the Plasma 2350 W from Pimoroni comes with an example that connects to a free API to update the colour randomly. Once I saw this in action I realised that it shouldn't be too hard to convert that to pull the data from a Drupal REST service instead.
It is quite easy to create RESTful services in Drupal; it just needs a single class. All I would need to do is create a form to control the colour that is selected in the REST service.
In this article we will look at creating a Drupal module containing a RESTful interface, which we will connect to with the Plasma 2350 W to update the colour of the lights.
Setting Up The Form
In order to allow the colour of the LED lights to be set I needed to create a form that would do just that.
To save the colour to the system we will use the state service, which is a handy little key/value service that allows us to write simple values to the database. This service is a good way of storing values that aren't part of the Drupal configuration system. Ideally, you want values that can be easily recreated by the system if they don't already exist. The colour setting is therefore an ideal candidate for the state service.
Setting the form up with this service injected is simple enough, but we can also simplify the form integration by abstracting away the get and set methods for the state itself.
This is what the basic structure of the form class looks like.