<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mecanik Dev</title>
    <description>The latest articles on DEV Community by Mecanik Dev (mecanik-dev).</description>
    <link>https://dev.to/mecanik-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14267%2Fbd937a40-59d7-4933-983d-716f80e172b4.png</url>
      <title>DEV Community: Mecanik Dev</title>
      <link>https://dev.to/mecanik-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mecanik-dev"/>
    <language>en</language>
    <item>
      <title>WordPress 7.0 and the AI Client in Core</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Thu, 05 Nov 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/wordpress-70-and-the-ai-client-in-core-1kpe</link>
      <guid>https://dev.to/mecanik-dev/wordpress-70-and-the-ai-client-in-core-1kpe</guid>
      <description>&lt;p&gt;WordPress 7.0 shipped on 20 May 2026 under the release name Armstrong, six weeks later than the date on the original 2026 schedule, and it is the most consequential core release for agencies since the block editor. The headline is that core can now talk to generative AI models. The detail that matters more is that core now defines how a plugin is supposed to talk to them, which quietly changes what every plugin on the site is allowed to assume about its own territory.&lt;/p&gt;

&lt;p&gt;For an editor, the visible changes are modest. There is a command palette, a tidier dashboard, a font management screen and better revisions. For anyone who maintains sites professionally, the important changes sit underneath the admin: a credential store in the options table, a registry of things the site can do, and a REST surface that lists them. None of it is optional, because it arrives with core rather than with a plugin somebody chose.&lt;/p&gt;

&lt;p&gt;This is the practitioner's read. What actually landed, what was pulled twelve days before release and why, what the update will and will not break, and what to say to a client who has just read a headline about AI going into WordPress.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Should you update?&lt;/strong&gt; Yes, but go to 7.1 rather than stopping at 7.0. WordPress 7.1 shipped on 19 August 2026 and 7.0 has four maintenance releases behind it. The AI features are inert until an administrator saves a provider key under Settings then Connectors, so the update on its own sends none of your content anywhere. The real risk in this update is ordinary plugin and theme compatibility, not the AI.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Actually Shipped in WordPress 7.0
&lt;/h2&gt;

&lt;p&gt;The release is named after Louis Armstrong, following the project's convention of naming major versions after jazz musicians. The &lt;a href="https://wordpress.org/news/2026/05/armstrong/" rel="noopener noreferrer"&gt;release announcement&lt;/a&gt; credits more than 875 contributors and over 420 enhancements and fixes.&lt;/p&gt;

&lt;p&gt;The list of what landed is short enough to be useful. Core gained an AI Client, a provider-agnostic PHP interface for sending prompts to generative models. It gained a Connectors screen under Settings, where an administrator stores provider credentials. It gained the JavaScript half of the Abilities API, whose PHP half had already shipped in 6.9.&lt;/p&gt;

&lt;p&gt;On the editorial side there is a command palette on Ctrl+K or Cmd+K, a modernised dashboard, a dedicated font management page, visual scrubbing through revisions, and new Heading, Breadcrumbs and Icons blocks alongside a lightbox slideshow for galleries.&lt;/p&gt;

&lt;p&gt;What did not land was the feature the whole release had been built around. Real-time collaborative editing was removed twelve days before launch. That absence, and the reason behind it, tell you more about the current state of core than the feature list does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the date moved
&lt;/h3&gt;

&lt;p&gt;The release was originally scheduled for 9 April 2026. It slipped to 20 May because collaborative editing was not ready and the project would not ship it as it stood. The &lt;a href="https://make.wordpress.org/core/2026/04/02/the-path-forward-for-wordpress-7-0/" rel="noopener noreferrer"&gt;path forward post&lt;/a&gt; is explicit that the delay existed "to allow more time to address testing feedback about the implementation of real-time collaboration", and the cycle went back into beta while keeping release candidate version numbers for technical reasons.&lt;/p&gt;

&lt;p&gt;That is unusual. A major release returning to beta after reaching RC is a strong signal, and it was the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Client: Core Ships the Abstraction, Not the Model
&lt;/h2&gt;

&lt;p&gt;The single most important architectural fact about WordPress 7.0 is that core does not include an AI model, an API key, or a relationship with any vendor. The &lt;a href="https://make.wordpress.org/core/2026/03/24/introducing-the-ai-client-in-wordpress-7-0/" rel="noopener noreferrer"&gt;AI Client dev note&lt;/a&gt; states plainly that "WordPress Core does not bundle any AI providers directly".&lt;/p&gt;

&lt;p&gt;What core ships is a consistent PHP interface. A plugin calls &lt;code&gt;wp_ai_client_prompt()&lt;/code&gt;, which returns a &lt;code&gt;WP_AI_Client_Prompt_Builder&lt;/code&gt; object, chains configuration such as &lt;code&gt;using_temperature()&lt;/code&gt; or &lt;code&gt;using_model_preference()&lt;/code&gt;, and finishes with &lt;code&gt;generate_text()&lt;/code&gt; or &lt;code&gt;generate_image()&lt;/code&gt;. Errors come back as &lt;code&gt;WP_Error&lt;/code&gt;, requests go through the WordPress HTTP transport, and the whole thing is wired into the hooks system.&lt;/p&gt;

&lt;p&gt;The practical effect is that a plugin author no longer writes an HTTP client, a retry loop, a key storage screen and a settings page for every model vendor. They describe what they want and core routes it.&lt;/p&gt;

&lt;p&gt;That is a genuine reduction in duplicated code. It is also a centralisation of trust, which is the part worth thinking about before you switch anything on.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a connector is
&lt;/h3&gt;

&lt;p&gt;A connector is the registered relationship between your site and an external service. In 7.0 the only connector type is AI providers, and there are three flagship provider plugins covering Anthropic, Google and OpenAI, each installed separately.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://make.wordpress.org/core/2026/03/18/introducing-the-connectors-api-in-wordpress-7-0/" rel="noopener noreferrer"&gt;Connectors API dev note&lt;/a&gt; describes how credentials resolve. A key can come from an environment variable, a PHP constant, or a database setting, checked in that order, with option names following the pattern &lt;code&gt;connectors_ai_{$id}_api_key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One detail deserves attention from anyone responsible for a site. The dev note says API keys stored in the database "are not encrypted but are masked in the user interface", with encryption tracked as follow-up work. If you set a key through the admin screen, it sits in &lt;code&gt;wp_options&lt;/code&gt; in plain text, and every backup of that database now contains a billable credential.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means if you do not write code
&lt;/h3&gt;

&lt;p&gt;For a site owner the change is simpler than it sounds. Nothing generates, summarises or rewrites anything until two things are true: a provider plugin is installed, and somebody has pasted a working key into Settings then Connectors.&lt;/p&gt;

&lt;p&gt;Until then the AI Client is a dormant library. Updating to WordPress 7.0 does not send your posts to a model, does not create an account anywhere, and does not incur a bill.&lt;/p&gt;

&lt;p&gt;What it does do is lower the barrier for the next plugin you install. A plugin that previously had to ask you for a key can now find one already configured on the site and use it. That is convenient, and it is exactly the thing to write a policy about before it happens by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Abilities API and Why Plugin Design Changes
&lt;/h2&gt;

&lt;p&gt;The Abilities API is the piece that will still matter in three years, and it has nothing inherently to do with AI. It is a registry. A plugin registers a named unit of functionality with &lt;code&gt;wp_register_ability()&lt;/code&gt;, in the form &lt;code&gt;namespace/ability-name&lt;/code&gt;, with a human-readable description, JSON Schema for its inputs and outputs, an execute callback and an optional permission callback.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.wordpress.org/apis/abilities-api/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; shows the permission callback as an ordinary capability check, for example returning &lt;code&gt;current_user_can( 'manage_options' )&lt;/code&gt;. That is the whole security model, and it is only as good as the plugin author's judgement.&lt;/p&gt;

&lt;p&gt;Once abilities exist, other things can enumerate them. A model can be handed a list of what this specific site can do, in schema form, and can call one. So can a client-side command palette, which is why the JavaScript half of the API landed in the same release as the palette.&lt;/p&gt;

&lt;p&gt;The design consequence for plugin authors is real. A feature that used to be reachable only through your own admin screen, with your own nonce and your own form, is now something you may be expected to expose as an ability with a machine-readable contract. That is a different threat surface and a different documentation burden.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changed in 7.1
&lt;/h3&gt;

&lt;p&gt;WordPress 7.1 tightened the registry rather than expanding it. The &lt;a href="https://make.wordpress.org/core/2026/07/31/abilities-api-improvements-in-wordpress-7-1/" rel="noopener noreferrer"&gt;7.1 abilities dev note&lt;/a&gt; adds validation filters &lt;code&gt;wp_ability_validate_input&lt;/code&gt; and &lt;code&gt;wp_ability_validate_output&lt;/code&gt;, an action &lt;code&gt;wp_ability_invoked&lt;/code&gt; that fires at the start of execution, and a &lt;code&gt;public&lt;/code&gt; metadata flag controlling whether an ability is discoverable over REST at &lt;code&gt;/wp-json/wp-abilities/v1/abilities&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That dev note also carries the sentence every logging plugin author should read. The invocation hook receives raw, unnormalised input, and developers "should avoid logging input indiscriminately, because it may contain credentials, personal information, or other sensitive data".&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-Time Collaboration and the Table It Needed
&lt;/h2&gt;

&lt;p&gt;Collaborative editing is built on Yjs, a conflict-free replicated data type, with a sync provider abstraction. Core ships an HTTP polling provider by default, chosen over WebSockets because it works on any host, and plugins can swap the transport through a filter.&lt;/p&gt;

&lt;p&gt;The problem was never the merge algorithm. It was where the sync data lived. The original implementation persisted it in post meta, which is the obvious choice in WordPress and the wrong one for data that changes several times per second.&lt;/p&gt;

&lt;p&gt;Post meta writes fire cache invalidation. With an editor open, sync data was being written continuously, so every write cleared cached queries for that post. In practice a single person editing a page could keep the site's persistent object cache flushing for the duration of the session.&lt;/p&gt;

&lt;p&gt;That is a good general lesson about post meta. It is a key value store attached to the cache lifecycle of the content it hangs off, and it is fine for attributes that change when the post changes. It is not a scratch space for high-frequency state.&lt;/p&gt;

&lt;h3&gt;
  
  
  The measured fix, and the decision
&lt;/h3&gt;

&lt;p&gt;Contributors tested storage strategies across eight hosting environments. The &lt;a href="https://make.wordpress.org/core/2026/05/08/results-real-time-collaboration-performance-testing-analysis/" rel="noopener noreferrer"&gt;performance analysis&lt;/a&gt; concluded that a dedicated table backed by transients was around 52% faster than the existing implementation, and a plain dedicated table around 37% faster. With a persistent object cache present, both transient-backed strategies dropped to a single database query per dispatch.&lt;/p&gt;

&lt;p&gt;The custom table with transients was chosen. Then, on the same day, the feature was pulled.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://make.wordpress.org/core/2026/05/08/rtc-removed-from-7-0/" rel="noopener noreferrer"&gt;removal notice&lt;/a&gt; cites "concerns around surface area, race conditions, server load, memory efficiency, and recurring bugs found through fuzz testing", and says the decision was made "in service of shipping a stable and reliable WordPress 7.0 release for our users".&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it stands now
&lt;/h3&gt;

&lt;p&gt;It did not ship in 7.1 either. The &lt;a href="https://make.wordpress.org/core/2026/08/05/wordpress-7-1-field-guide/" rel="noopener noreferrer"&gt;7.1 field guide&lt;/a&gt; states that real-time collaborative editing "received extensive testing and feedback during the WordPress 7.1 cycle, but it is not enabled in the final release".&lt;/p&gt;

&lt;p&gt;Notes, which is the related but separate feature for leaving block-level comments, did ship and was improved in 7.1 with rich text formatting and @ mentions. If a client asks for Google Docs style co-editing in WordPress, the honest answer today is that Notes covers the review workflow and simultaneous typing is still not in core.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Admin Changes and the Tickets They Generate
&lt;/h2&gt;

&lt;p&gt;Two changes will produce support requests, and neither is a bug.&lt;/p&gt;

&lt;p&gt;The command palette on Ctrl+K or Cmd+K is genuinely fast once learned, but Ctrl+K is the keyboard shortcut for inserting a link in many editors, and some users will report that link insertion has broken. It has not; focus context decides which handler wins.&lt;/p&gt;

&lt;p&gt;The modernised dashboard is the larger one. Any client whose staff were trained on screenshots now has out of date training material, and any plugin that injected markup into admin screens by assuming particular classes or DOM structure may render oddly. This is cosmetic rather than functional, but it arrives on the day of the update, on every site, for every user, which makes it the most visible part of the release to the people who are not developers.&lt;/p&gt;

&lt;p&gt;Budget an hour per client for a short written note with new screenshots before you update anything. It is cheaper than the same explanation given fifteen times by email.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Compatibility Question That Actually Matters
&lt;/h2&gt;

&lt;p&gt;The version numbering suggests a breaking release. The PHP requirements do not. As the &lt;a href="https://make.wordpress.org/core/2026/05/22/php-support-clarification-2026/" rel="noopener noreferrer"&gt;PHP support clarification&lt;/a&gt; sets out, the minimum supported PHP version is 7.4 since WordPress 7.0, and the minimum recommended version remains 8.3. Support for PHP 7.2 and 7.3 was dropped in this release.&lt;/p&gt;

&lt;p&gt;The clarification also retired the old "beta" label for newer PHP versions, and records full support for PHP 8.5 in WordPress 6.9 and 7.0.&lt;/p&gt;

&lt;p&gt;The gap between supported and sensible is the point. PHP 7.4 reached end of life in November 2022, so a site that only just clears the minimum is running an interpreter that has had no security fixes for close to four years. If your hosting is still on 7.4 in 2026, the WordPress version is not your most urgent problem.&lt;/p&gt;

&lt;p&gt;Breakage in practice follows a predictable pattern. Abandoned plugins fail first, particularly anything that manipulates the admin DOM or the editor iframe. Custom themes with hard-coded admin styling look wrong. Page builders that ship their own React bundles are the usual source of white screens in the editor, and they are also usually the fastest to patch.&lt;/p&gt;

&lt;h3&gt;
  
  
  A concrete update procedure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Stage it.&lt;/strong&gt; Clone production, including the database, to an environment that is not indexable and not sending email. Nothing below is worth doing on a live site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Record the baseline.&lt;/strong&gt; Note the PHP version, the plugin and theme list with versions, and the current WordPress version. Take screenshots of the two or three admin screens the client uses daily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update WordPress alone first.&lt;/strong&gt; Leave plugins and themes untouched, then walk the front end, the post editor, the site editor, WooCommerce checkout if present, and any custom admin screens. A failure here belongs to core or to an incompatible extension, and separating that from a plugin update is the whole reason to do it in this order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then update plugins in small batches&lt;/strong&gt;, retesting between batches, so a regression has a short list of suspects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check error logs rather than eyeballing pages.&lt;/strong&gt; PHP notices from a deprecated call do not always render visibly and will fill a log quietly for months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leave the Connectors screen empty.&lt;/strong&gt; Ship the update with no AI provider configured, and treat enabling one as a separate, deliberate change with its own approval. Our &lt;a href="https://mecanik.dev/en/posts/wordpress-security-hardening-checklist-2026/" rel="noopener noreferrer"&gt;WordPress security hardening checklist&lt;/a&gt; covers the surrounding controls, and the &lt;a href="https://mecanik.dev/en/posts/wordpress-performance-audit-core-web-vitals/" rel="noopener noreferrer"&gt;performance audit guide&lt;/a&gt; covers what to measure afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance: What a Connector Is Permitted To Do
&lt;/h2&gt;

&lt;p&gt;Here is the question that the abilities layer creates and that no plugin author can answer for you. A third party plugin can register an ability that reads customer records, exports users, or edits published content, and a model with access to the registry can call it. The permission callback is a capability check, so the model acts with the permissions of whoever is signed in.&lt;/p&gt;

&lt;p&gt;If that person is an administrator, the model can do administrator things. That is not a defect in the design; it is the design working as documented. It does mean the decision about which connectors exist on a site is a data protection decision, not an IT preference.&lt;/p&gt;

&lt;p&gt;Content in a CMS is rarely just marketing copy. Comments, form submissions, order records and user profiles are personal data, and sending them to an external model is a processing operation you have to be able to justify. The ICO's &lt;a href="https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/artificial-intelligence/guidance-on-ai-and-data-protection/" rel="noopener noreferrer"&gt;guidance on AI and data protection&lt;/a&gt; sets out the accountability and transparency expectations, including demonstrating data protection by design and keeping governance proportionate to the use.&lt;/p&gt;

&lt;p&gt;The practical minimum for a client site is a short written policy: which connectors are permitted, who may add one, which abilities are exposed publicly over REST, and what the retention position is with the provider. Write it before somebody pastes a key in, because afterwards it is an incident report rather than a policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Queued Next
&lt;/h2&gt;

&lt;p&gt;WordPress 7.1 arrived on 19 August 2026 with responsive styling controls in Global Styles, a persistent admin bar across editors, a proper media editing modal, Playlist and Tabs blocks, and the Notes improvements already mentioned. It also completed the move to an iframed post editor, including for sites registering legacy meta boxes, which is the change most likely to expose an old plugin.&lt;/p&gt;

&lt;p&gt;WordPress 7.2 is planned as the final major release of 2026. The &lt;a href="https://make.wordpress.org/core/7-2/" rel="noopener noreferrer"&gt;7.2 release page&lt;/a&gt; puts the final release in the window of 8 to 10 December 2026, with betas from late October. That schedule is planned rather than shipped, and this project has already moved one major release date this year.&lt;/p&gt;

&lt;p&gt;Collaborative editing remains the obvious candidate for a future release, but it has now missed two, and nobody should promise a client a date for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Changes Commercially
&lt;/h2&gt;

&lt;p&gt;Three client conversations change, and only one of them is about AI.&lt;/p&gt;

&lt;p&gt;The first is the update conversation. WordPress 7.0 and 7.1 are worth charging for as a managed update with a staging pass, because the iframed editor and the admin redesign genuinely surface old extensions. Selling that as a fixed-price package with a written test plan is more honest and more profitable than absorbing it into a retainer and discovering a broken page builder at 6pm.&lt;/p&gt;

&lt;p&gt;The second is governance. Connector policy, ability review and credential handling are billable advisory work that did not exist before May 2026, and they suit an agency far better than they suit an in-house marketing team.&lt;/p&gt;

&lt;p&gt;The third is build work. The AI Client removes the boring half of building an AI feature into a site, which lowers the price of the plumbing and raises the value of knowing what is worth building. If you are weighing that against a bespoke build, our comparison of &lt;a href="https://mecanik.dev/en/posts/wordpress-vs-custom-web-development-what-uk-businesses-need-to-know/" rel="noopener noreferrer"&gt;WordPress and custom development&lt;/a&gt; covers where the line usually falls, and our notes on &lt;a href="https://mecanik.dev/en/posts/hire-wordpress-developer-rates-what-to-ask/" rel="noopener noreferrer"&gt;WordPress developer rates&lt;/a&gt; cover what the work should cost.&lt;/p&gt;

&lt;p&gt;Mecanik handles core version upgrades, connector governance and AI feature work as part of our &lt;a href="https://mecanik.dev/en/wordpress-developer-for-hire/" rel="noopener noreferrer"&gt;WordPress development&lt;/a&gt; and &lt;a href="https://mecanik.dev/en/ai-integration-services/" rel="noopener noreferrer"&gt;AI integration&lt;/a&gt; services. The pattern we see is consistent: the update itself is routine, and the expensive surprises come from extensions nobody had reviewed in three years.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-ai/" rel="noopener noreferrer"&gt;Drupal AI: What the AI Modules Actually Do&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/wordpress-plugin-development/" rel="noopener noreferrer"&gt;WordPress Plugin Development That Survives Core Updates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/ai-agent-payments/" rel="noopener noreferrer"&gt;AI Agent Payments: What Agentic Commerce Means for Merchants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/elementor-vs-custom-theme/" rel="noopener noreferrer"&gt;Elementor vs a Custom Theme: The Real Cost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When was WordPress 7.0 released and why was it delayed?&lt;/strong&gt;&lt;br&gt;
WordPress 7.0 "Armstrong" was released on 20 May 2026, six weeks after the 9 April date on the original schedule. The delay was to address testing feedback on the real-time collaborative editing implementation, and the cycle returned to beta after reaching release candidate. Collaborative editing was ultimately removed from the release on 8 May 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does WordPress 7.0 send my content to an AI provider?&lt;/strong&gt;&lt;br&gt;
No. Core ships an AI Client but bundles no AI providers, no models and no API keys. Nothing is sent anywhere until an administrator installs a provider plugin and saves a working credential under Settings then Connectors. Until that happens the AI Client is a dormant library that costs nothing and transmits nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the Abilities API for?&lt;/strong&gt;&lt;br&gt;
It is a registry that lets a plugin declare a named unit of functionality with JSON Schema inputs and outputs, a permission callback and an execute callback. Other software, including AI models and the command palette, can then enumerate what a site can do and call it. The PHP half shipped in WordPress 6.9 and the JavaScript half in 7.0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What PHP version does WordPress 7.0 require?&lt;/strong&gt;&lt;br&gt;
The minimum supported version is PHP 7.4 since WordPress 7.0, which dropped support for PHP 7.2 and 7.3. The minimum recommended version remains PHP 8.3. Since PHP 7.4 reached end of life in November 2022, running only the minimum means running an unsupported interpreter, so treat 8.3 or higher as the real requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is real-time collaborative editing available yet?&lt;/strong&gt;&lt;br&gt;
Not in core. It was removed from WordPress 7.0 twelve days before release over concerns about race conditions, server load and memory efficiency, and it is not enabled in WordPress 7.1 either. The separate Notes feature, which allows block-level comments with mentions, did ship and covers review workflows rather than simultaneous typing.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>ai</category>
      <category>webdev</category>
      <category>php</category>
    </item>
    <item>
      <title>CMS Migration Without Losing Your Traffic</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Thu, 05 Nov 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/cms-migration-without-losing-your-traffic-5fpp</link>
      <guid>https://dev.to/mecanik-dev/cms-migration-without-losing-your-traffic-5fpp</guid>
      <description>&lt;p&gt;A CMS migration is one of the few projects where the technical work can go perfectly and the outcome still be a disaster. The site launches on time, looks better, loads faster, and traffic falls by half, because a few hundred URLs changed shape and nobody built the map.&lt;/p&gt;

&lt;p&gt;The traffic loss is not caused by the new platform. It is caused by discontinuity: addresses that used to resolve now do not, pages that used to be identifiable now look new, and accumulated history attached to old URLs has nowhere to go.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The single decision that determines the outcome: are you keeping the URL structure?&lt;/strong&gt; If yes, the migration is mostly a content and template exercise and the risk is modest. If no, every changed address needs a redirect to its specific equivalent, and the completeness of that map is what decides whether the migration is invisible or expensive. There is no third option where the URLs change and it works out.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What a CMS Migration Actually Breaks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URLs&lt;/strong&gt;, which is the big one. Different platforms have different conventions for paths, pagination, categories and dates, and adopting the new platform's defaults silently rewrites every address on the site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metadata.&lt;/strong&gt; Titles and descriptions written over years frequently do not survive an export, and the new platform generates them from templates instead. This is easy to miss because the pages look fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structured data.&lt;/strong&gt; Markup added by a plugin on the old platform disappears with the plugin, and the new equivalents rarely emit identical output. Our guide to &lt;a href="https://mecanik.dev/en/posts/schema-markup-for-llms-ai-search/" rel="noopener noreferrer"&gt;how AI search engines read schema markup&lt;/a&gt; covers what needs to survive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internal links.&lt;/strong&gt; Body content full of absolute links to old paths will keep pointing at them, which after a URL change means every one of them travels through a redirect at best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images and media.&lt;/strong&gt; Different storage paths, different file names, and alternative text that lived in the old database and was not in the export.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anything a plugin was doing.&lt;/strong&gt; Redirects configured over years, canonical rules, feed formats, and functionality nobody documented because it was a checkbox.&lt;/p&gt;

&lt;p&gt;Inventory all of this before the migration, not after. The list of what the old platform quietly does for you is always longer than expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Redirect Map Decides Everything
&lt;/h2&gt;

&lt;p&gt;If URLs change, this is the deliverable that matters, and it needs to be complete rather than nearly complete.&lt;/p&gt;

&lt;p&gt;Build the source list from more than one place. A crawl of the live site finds what is linked. Your analytics and Search Console find pages that receive traffic but may be orphaned. Server logs find what is actually requested, including by other sites linking to you. Any single source misses things, and the pages missed are disproportionately the old ones with links attached.&lt;/p&gt;

&lt;p&gt;Map each old URL to its specific new equivalent. Not to the homepage, and not to a category page, because a redirect to something that does not answer the original request is treated as a soft error and passes little value. &lt;a href="https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes" rel="noopener noreferrer"&gt;Google's site move documentation&lt;/a&gt; sets out the mapping it expects. If genuinely nothing corresponds, returning a not-found response is the honest answer and better than a misleading redirect.&lt;/p&gt;

&lt;p&gt;Use permanent redirects, keep chains to a single hop by pointing old addresses directly at final destinations, and remember that redirect rules are evaluated in order, so a broad pattern placed above a specific rule will swallow it.&lt;/p&gt;

&lt;p&gt;Then test the map before launch, against the full list rather than a sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before You Switch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Crawl and archive the old site.&lt;/strong&gt; A complete record of every URL with its title, description, canonical, status code and word count. This is your comparison baseline and you cannot produce it after the fact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export and verify the content.&lt;/strong&gt; Check counts, not just that the export ran. Missing categories, dropped custom fields and truncated posts are all common and all silent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage it on a blocked environment.&lt;/strong&gt; A staging site that gets indexed creates duplicates of your entire site, which is a worse problem than the one you were solving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check the new templates emit what the old ones did.&lt;/strong&gt; Titles, descriptions, canonicals, structured data, hreflang if you run multiple languages, as covered in our &lt;a href="https://mecanik.dev/en/posts/multilingual-seo/" rel="noopener noreferrer"&gt;multilingual SEO guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan the timing.&lt;/strong&gt; Not before your busiest period, and not on a Friday. You want several working days of attention immediately afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  After You Switch
&lt;/h2&gt;

&lt;p&gt;The first forty-eight hours matter more than the following month, because that is when a fixable error is still cheap.&lt;/p&gt;

&lt;p&gt;Watch server logs for not-found responses. This is the fastest way to find the URLs your map missed, and it finds them from real requests rather than from your assumptions. Submit the new sitemap, and check that crawling is actually happening rather than assuming it.&lt;/p&gt;

&lt;p&gt;Compare against the baseline crawl. Every URL that was indexable before should now either resolve or redirect somewhere specific. Any that does neither is a hole.&lt;/p&gt;

&lt;p&gt;Expect a dip. Some fluctuation for a few weeks is normal even on a well-executed migration, because the new addresses have to be recrawled and reassessed. What is not normal is a sustained fall that does not recover, and that almost always traces back to redirects that were missed or pointed somewhere generic.&lt;/p&gt;

&lt;p&gt;Keep the redirects permanently. They are not a transitional measure, they are the only thing connecting years of accumulated links to your current pages, and removing them a year later reproduces the original loss.&lt;/p&gt;

&lt;p&gt;Mecanik handles migrations of this kind as part of our &lt;a href="https://mecanik.dev/en/services/website-development/" rel="noopener noreferrer"&gt;website development&lt;/a&gt; work. The pattern is consistent: the platform change is routine, and the difference between a good outcome and a bad one is entirely in the completeness of the map.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/website-migration-without-losing-traffic/" rel="noopener noreferrer"&gt;Website Migration Without Losing Traffic: 2026 Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/b2b-seo-agency-pipeline-not-traffic/" rel="noopener noreferrer"&gt;B2B SEO Agency: Pipeline, Not Traffic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/technical-seo-audit-cost-deliverables-pricing/" rel="noopener noreferrer"&gt;Technical SEO Audit Cost: 2026 Price and Deliverables Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/content-pruning/" rel="noopener noreferrer"&gt;Content Pruning: When Deleting Pages Raises Traffic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why does traffic drop after a CMS migration?&lt;/strong&gt;&lt;br&gt;
Almost always because URLs changed and the redirect map was incomplete. Old addresses stop resolving, so years of accumulated links and crawl history have nowhere to go. The platform itself rarely causes the loss; discontinuity between old and new addresses does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I keep my URL structure when changing CMS?&lt;/strong&gt;&lt;br&gt;
If you can, yes. Keeping the structure reduces the migration to a content and template exercise with modest risk. Changing it means every altered address needs a redirect to its specific equivalent, and the completeness of that mapping determines whether the migration is invisible or expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I get the list of URLs to redirect?&lt;/strong&gt;&lt;br&gt;
From several sources, because each one misses things. A crawl of the live site finds linked pages, analytics and Search Console find pages receiving traffic that may be orphaned, and server logs find what is actually requested including from external links. Pages missed by a single source tend to be old ones with links attached.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I redirect old pages to the homepage?&lt;/strong&gt;&lt;br&gt;
No. A redirect to something that does not answer the original request is treated as a soft error and passes little value. Map each old URL to its specific equivalent, and where nothing genuinely corresponds, returning a not-found response is more honest and more useful than a misleading redirect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long should migration redirects stay in place?&lt;/strong&gt;&lt;br&gt;
Permanently. They are not a transitional measure but the only connection between years of accumulated inbound links and your current pages, so removing them a year later reproduces exactly the traffic loss the migration was supposed to avoid.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>seo</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Password Storage: What to Use in 2026</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Wed, 04 Nov 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/password-storage-what-to-use-in-2026-5fjh</link>
      <guid>https://dev.to/mecanik-dev/password-storage-what-to-use-in-2026-5fjh</guid>
      <description>&lt;p&gt;Password storage is one of the few areas in software with a genuine right answer, published, maintained and free. It is also one of the most consistently got wrong, because the wrong answers were correct at some point and nobody revisited them.&lt;/p&gt;

&lt;p&gt;The failure is rarely exotic. It is a system built in 2016 against advice that was current in 2012, still running, still accepting logins, and nobody has looked at that function since the person who wrote it left.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you take one thing: use Argon2id, and if you cannot, use bcrypt.&lt;/strong&gt; Everything else in this article is detail. A general-purpose hash such as SHA-256 is not a password hash regardless of how many times you apply it, because its speed is a design goal, and speed is precisely what you are trying to deny an attacker who has your database.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Password Storage Has Exactly One Correct Shape
&lt;/h2&gt;

&lt;p&gt;Store a hash produced by a deliberately slow, memory-hard function, with a unique random salt per password, using parameters tuned to your hardware.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP password storage guidance&lt;/a&gt; states the current recommendations precisely, and they are worth quoting as numbers rather than as principles.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Recommended parameters&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Argon2id&lt;/td&gt;
&lt;td&gt;19 MiB memory, 2 iterations, 1 degree of parallelism&lt;/td&gt;
&lt;td&gt;First choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scrypt&lt;/td&gt;
&lt;td&gt;N=2^17 (128 MiB), r=8, p=1&lt;/td&gt;
&lt;td&gt;If Argon2id is unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bcrypt&lt;/td&gt;
&lt;td&gt;Work factor 10 or more, 72 byte password limit&lt;/td&gt;
&lt;td&gt;Legacy systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PBKDF2-HMAC-SHA256&lt;/td&gt;
&lt;td&gt;600,000 iterations&lt;/td&gt;
&lt;td&gt;Where FIPS-140 compliance is required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two notes on that table. The Argon2id figure is a minimum, and higher memory with fewer iterations is an equivalent trade, so a configuration of 47 MiB with 1 iteration provides comparable security to the baseline. And bcrypt's 72 byte limit is a real truncation, not a validation rule: anything past 72 bytes is silently ignored, which matters if you accept long passphrases or pre-hash the input.&lt;/p&gt;

&lt;p&gt;These parameters are revised as hardware improves. Read the source rather than this table when you implement.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Salt Is Not Optional and Not Sufficient
&lt;/h2&gt;

&lt;p&gt;A salt is a unique random value stored alongside each hash. Its purpose is narrow and important: it ensures two users with the same password produce different hashes, which defeats precomputed rainbow tables and stops an attacker cracking one password and getting every account that shared it.&lt;/p&gt;

&lt;p&gt;What a salt does not do is slow anybody down. An attacker with your database has the salts too, because they must be readable to verify a login. Salting alone against a fast hash means an attacker attacks each password individually at enormous speed, which is exactly the situation you were trying to avoid.&lt;/p&gt;

&lt;p&gt;The slowness has to come from the algorithm. That is the whole point of a memory-hard function: it deliberately consumes resources so that an attacker with specialised hardware gains far less advantage than they would against a fast hash.&lt;/p&gt;

&lt;p&gt;A pepper, a secret value applied to every password and stored outside the database, adds a real layer, because a database dump alone becomes insufficient. It also adds an operational burden, since rotating it requires rehashing on next login, and losing it locks out every account. Worth it for high-value systems and skippable for most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mistakes That Survive for Years
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A fast hash with a salt.&lt;/strong&gt; SHA-256 with a salt looks responsible and is not. Speed is the vulnerability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encryption instead of hashing.&lt;/strong&gt; Encryption is reversible by design, which means a key exists, and anybody who obtains it obtains every password in plaintext. Passwords should never be recoverable, only verifiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Work factors set once and never raised.&lt;/strong&gt; A bcrypt cost chosen in 2015 is substantially weaker against 2026 hardware. Raise it on next successful login, when you have the plaintext momentarily and can rehash.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Truncation or character restrictions.&lt;/strong&gt; A maximum length below about 64 characters, or a ban on particular characters, usually signals that the password is being stored in a way that cares what it contains. It should not; you are hashing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comparison that leaks timing.&lt;/strong&gt; Verification must use a constant-time comparison. Standard library functions do this and hand-rolled equality checks do not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging the password.&lt;/strong&gt; It reaches an error log, a request trace or a monitoring system, and then it is in three places with weaker access controls than the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating an Existing System
&lt;/h2&gt;

&lt;p&gt;You cannot rehash what you do not have, because you only hold hashes. The workable approach is to upgrade at login.&lt;/p&gt;

&lt;p&gt;When a user authenticates successfully, you have their plaintext password in memory for a moment. Verify against the old hash, then immediately compute a new one with the current algorithm and replace the stored value. Over a few months most active accounts migrate with no user-visible change.&lt;/p&gt;

&lt;p&gt;Track the algorithm per record so both can coexist, and set a date after which remaining old hashes are invalidated and those users must reset. Dormant accounts with weak hashes are exactly the ones an attacker wants.&lt;/p&gt;

&lt;p&gt;Do not wrap the old hash in the new algorithm as a shortcut. It works, and it leaves the weak hash as the effective security boundary while looking modern.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Matters More Than the Algorithm
&lt;/h2&gt;

&lt;p&gt;Correct hashing protects you when the database leaks. It does nothing about the more common attack, which is somebody logging in with a real password obtained elsewhere.&lt;/p&gt;

&lt;p&gt;Rate limiting on authentication, multi-factor availability, checking new passwords against known-breached lists, and alerting on credential stuffing patterns all address that, and all matter more day to day than the choice between Argon2id and bcrypt. The &lt;a href="https://mecanik.dev/en/posts/api-security-protect-public-api/" rel="noopener noreferrer"&gt;API security&lt;/a&gt; controls are the same family of problem.&lt;/p&gt;

&lt;p&gt;Both are worth doing, and the order is worth being honest about: get the hashing right once because it is cheap and permanent, then spend the ongoing effort on the login path, which is where the attacks actually arrive. Mecanik reviews both as part of our &lt;a href="https://mecanik.dev/en/services/application-security/" rel="noopener noreferrer"&gt;application security&lt;/a&gt; work.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/supply-chain-security/" rel="noopener noreferrer"&gt;Software Supply Chain Security for Small Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/how-to-build-a-web-app-in-2026-the-uk-developers-guide/" rel="noopener noreferrer"&gt;How to Build a Web App in 2026 - The UK Developer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/database-performance-slow-queries/" rel="noopener noreferrer"&gt;Database Performance: Finding the Query That Is Killing You&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/api-versioning/" rel="noopener noreferrer"&gt;API Versioning: When to Break and How Not To&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the best password hashing algorithm in 2026?&lt;/strong&gt;&lt;br&gt;
Argon2id, at a minimum of 19 MiB of memory, 2 iterations and 1 degree of parallelism. If it is unavailable, scrypt at N=2^17, r=8, p=1 is an alternative, bcrypt with a work factor of 10 or more suits legacy systems, and PBKDF2-HMAC-SHA256 at 600,000 iterations covers FIPS-140 requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is salting passwords enough?&lt;/strong&gt;&lt;br&gt;
No. A salt ensures identical passwords produce different hashes, defeating rainbow tables and stopping one cracked password unlocking every account that shared it. It does not slow an attacker down, because salts must be readable to verify logins. The slowness has to come from a deliberately memory-hard algorithm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not use SHA-256 for passwords?&lt;/strong&gt;&lt;br&gt;
Because its speed is a design goal, and speed is exactly what you are trying to deny an attacker holding your database. A general-purpose hash is not a password hash regardless of how many times it is applied or whether it is salted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does bcrypt have a password length limit?&lt;/strong&gt;&lt;br&gt;
Yes, 72 bytes, and anything beyond that is silently truncated rather than rejected. This matters if you accept long passphrases or pre-hash input before passing it to bcrypt, since the truncation happens without any error to indicate it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I upgrade password hashes without user passwords?&lt;/strong&gt;&lt;br&gt;
At login. When a user authenticates you hold their plaintext momentarily, so verify against the old hash and immediately rehash with the current algorithm. Track the algorithm per record so both coexist, and set a date after which remaining old hashes are invalidated and those users must reset.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>Disaster Recovery for Small Software Teams</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Wed, 04 Nov 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/disaster-recovery-for-small-software-teams-49oi</link>
      <guid>https://dev.to/mecanik-dev/disaster-recovery-for-small-software-teams-49oi</guid>
      <description>&lt;p&gt;Disaster recovery in a small team is usually one line in a document nobody has opened: backups are enabled. That statement is true and it is not an answer, because it says nothing about how old the data would be, how long the restore takes, or whether anyone has ever performed one.&lt;/p&gt;

&lt;p&gt;The gap between having backups and being able to recover is where most outages become incidents. A backup that exists, is current, and has never been restored is a hypothesis, and the moment you test it is a bad moment to discover it was wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Two numbers turn this from an opinion into a plan.&lt;/strong&gt; How much data can you afford to lose, and how long can you afford to be down? Those are your recovery point and recovery time objectives. Until somebody in the business states them, every technical argument about backup frequency is unresolvable, because there is no criterion to argue against.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  RPO and RTO in Plain Terms
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recovery point objective&lt;/strong&gt; is how much data you can lose. Nightly backups mean an RPO of twenty-four hours: a failure at 5pm loses everything since the previous night. If that is unacceptable, the backup schedule is wrong, and no amount of care elsewhere compensates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recovery time objective&lt;/strong&gt; is how long you can be down. This includes everything: noticing, deciding, provisioning, restoring, verifying and switching traffic back. Teams estimate the restore step and forget the other five, which is why real recoveries take several times longer than expected.&lt;/p&gt;

&lt;p&gt;Both are business decisions rather than technical ones, and both cost money as they tighten. An RPO of five minutes needs continuous replication. An RTO of one hour needs infrastructure that can be rebuilt automatically, because nobody is going to configure a server by hand inside an hour under pressure.&lt;/p&gt;

&lt;p&gt;The productive conversation is the trade. Tell the business what each level costs and let it choose, rather than picking a number in engineering and hoping it matches expectations that were never stated.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Untested Backup Is Not a Backup
&lt;/h2&gt;

&lt;p&gt;The failures that hurt are rarely the absence of backups. They are backups that turned out to be unusable.&lt;/p&gt;

&lt;p&gt;Common causes, all of which are only visible on restore: the job has been failing silently for weeks because nobody watches success as well as failure; the dump captured the schema but not the data, or a subset of tables somebody added to an exclusion list; the file is encrypted with a key stored on the machine that died; the backup lives in the same account or region as the thing it protects.&lt;/p&gt;

&lt;p&gt;That last one matters more than it used to. A backup in the same environment as production is protected against hardware failure and against nothing else. It does not survive an account compromise, an accidental deletion at the account level, or ransomware that reaches everything the same credentials can reach.&lt;/p&gt;

&lt;p&gt;The only way to know a backup works is to restore it. Schedule it, do it quarterly at minimum, and time it. That timing is your real RTO, as opposed to the one in the document.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Disaster Recovery Has to Cover
&lt;/h2&gt;

&lt;p&gt;Data is the obvious part and rarely the slow part. The things that extend recoveries are usually the ones nobody listed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The database&lt;/strong&gt;, which everybody remembers. &lt;strong&gt;Uploaded files&lt;/strong&gt;, which live somewhere separate and often have no backup at all. &lt;strong&gt;Configuration and secrets&lt;/strong&gt;, which frequently exist only on the running machine. &lt;strong&gt;DNS and certificates&lt;/strong&gt;, which cannot be recreated quickly if the account holding them is the thing you lost. &lt;strong&gt;The infrastructure itself&lt;/strong&gt;, meaning the servers, networks and rules, which is fast to rebuild if it is defined as code and slow if it was clicked into existence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the knowledge of how it all fits together&lt;/strong&gt;, which is the single largest determinant of recovery time in a small team. A system one person can rebuild from memory has a recovery time equal to that person's availability, which is not a plan.&lt;/p&gt;

&lt;p&gt;Write the inventory before the plan. Most teams discover at least one component with no backup at all while making the list, and finding it that way is considerably cheaper than the alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Plan That Fits on One Page
&lt;/h2&gt;

&lt;p&gt;Long documents do not get read during an incident. Aim for something a tired person can follow at three in the morning.&lt;/p&gt;

&lt;p&gt;It needs: who decides that recovery has begun, because hesitating over whether this counts as a disaster is a common source of delay. Where the backups are and how to reach them, including credentials that do not depend on the failed system. The restore steps in order, specific enough to follow without improvisation. How to verify it worked, which means a concrete check rather than the site appearing to load. And who to tell, internally and externally.&lt;/p&gt;

&lt;p&gt;Keep it somewhere that survives the failure. A recovery plan stored only in the system it recovers is a recurring and entirely avoidable mistake.&lt;/p&gt;

&lt;p&gt;The same reasoning applies to access. If one person holds the credentials for the hosting account, your recovery time is bounded by whether they answer the phone. Our &lt;a href="https://mecanik.dev/en/posts/linux-server-hardening-guide-2026/" rel="noopener noreferrer"&gt;Linux server hardening guide&lt;/a&gt; covers the access controls this depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proportionate, Not Perfect
&lt;/h2&gt;

&lt;p&gt;A small team does not need a hot standby in a second region. The cost is real and the complexity introduces its own failure modes.&lt;/p&gt;

&lt;p&gt;What most small teams need is considerably cheaper: backups in a separate account with credentials that production cannot use, a restore tested and timed on a schedule, infrastructure defined as code so it can be recreated without archaeology, and a one-page plan somebody has read.&lt;/p&gt;

&lt;p&gt;That set covers hardware failure, accidental deletion, account compromise and ransomware, which is most of what actually happens. Everything beyond it is a decision about how much a further reduction in downtime is worth, and that is a question for the business rather than for engineering.&lt;/p&gt;

&lt;p&gt;Mecanik reviews and builds this as part of our &lt;a href="https://mecanik.dev/en/services/server-security/" rel="noopener noreferrer"&gt;server security&lt;/a&gt; work. The first question is always the same, and it is not about technology: how long can you be down, and who decided that?&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/uptime-sla/" rel="noopener noreferrer"&gt;Uptime SLAs That Mean Something&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/supply-chain-security/" rel="noopener noreferrer"&gt;Software Supply Chain Security for Small Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/ai-agents-for-business-cost-failure-modes/" rel="noopener noreferrer"&gt;AI Agents for Business: What They Cost and Where They Fail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/fintech-software-development-uk/" rel="noopener noreferrer"&gt;Fintech Software Development UK: FCA, Rails and Cost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What do RPO and RTO mean?&lt;/strong&gt;&lt;br&gt;
Recovery point objective is how much data you can afford to lose, so nightly backups imply an RPO of twenty-four hours. Recovery time objective is how long you can afford to be down, including noticing, deciding, provisioning, restoring, verifying and switching traffic back. Both are business decisions, and both cost more as they tighten.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is an untested backup not a backup?&lt;/strong&gt;&lt;br&gt;
Because the failure modes are only visible on restore. Jobs fail silently for weeks when nobody monitors success as well as failure, dumps capture schema without data or omit excluded tables, encryption keys sit on the machine that died, and copies live in the same account as the thing they protect. Restore quarterly and time it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where should backups be stored?&lt;/strong&gt;&lt;br&gt;
In a separate account or region, with credentials production cannot use. A backup in the same environment protects against hardware failure and nothing else: it does not survive an account compromise, an account-level deletion, or ransomware that reaches everything the same credentials can reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What do people forget to include in disaster recovery?&lt;/strong&gt;&lt;br&gt;
Uploaded files stored separately from the database, configuration and secrets that exist only on the running machine, DNS and certificates held in an account that may itself be lost, infrastructure that was clicked into existence rather than defined as code, and the knowledge of how it fits together when only one person has it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a small team need a hot standby?&lt;/strong&gt;&lt;br&gt;
Usually not. The cost is real and the added complexity brings its own failure modes. Backups in a separate account, a tested and timed restore, infrastructure defined as code, and a one-page plan somebody has read will cover hardware failure, accidental deletion, account compromise and ransomware, which is most of what happens.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>backend</category>
      <category>automation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Uptime SLAs That Mean Something</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Tue, 03 Nov 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/uptime-slas-that-mean-something-584f</link>
      <guid>https://dev.to/mecanik-dev/uptime-slas-that-mean-something-584f</guid>
      <description>&lt;p&gt;An uptime SLA looks like a promise and behaves like a refund policy. Suppliers know this. Customers frequently do not, and sign a service level agreement believing they have bought availability when what they have bought is a small discount in the event they do not get it.&lt;/p&gt;

&lt;p&gt;That is not necessarily a bad deal. It is a different deal from the one most people think they are making, and the difference matters when the system is down and somebody asks what the contract says.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Three nines sounds close to perfect and allows 43 minutes of downtime a month.&lt;/strong&gt; Four nines allows four. If your business can absorb a 43 minute outage during a working afternoon, 99.9% is fine and you should not pay for more. If it cannot, no number of nines helps, because the agreement pays you a credit rather than preventing the outage.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What an Uptime SLA Promises in Minutes
&lt;/h2&gt;

&lt;p&gt;The percentages compress a large range into a small-looking difference. Written as time, the differences are obvious.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Availability&lt;/th&gt;
&lt;th&gt;Downtime per year&lt;/th&gt;
&lt;th&gt;Downtime per month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;99%&lt;/td&gt;
&lt;td&gt;3.65 days&lt;/td&gt;
&lt;td&gt;7.2 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;td&gt;8.76 hours&lt;/td&gt;
&lt;td&gt;43.2 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.95%&lt;/td&gt;
&lt;td&gt;4.38 hours&lt;/td&gt;
&lt;td&gt;21.6 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.99%&lt;/td&gt;
&lt;td&gt;52.6 minutes&lt;/td&gt;
&lt;td&gt;4.32 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.999%&lt;/td&gt;
&lt;td&gt;5.26 minutes&lt;/td&gt;
&lt;td&gt;26 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things follow. The step from 99.9% to 99.99% is a tenfold reduction in permitted downtime and typically a much larger increase in cost, because it requires redundancy that removes every single point of failure including the deployment process. And anything at or beyond five nines is below the time it takes a human to notice and respond, so it can only be met by systems that fail over without anybody involved.&lt;/p&gt;

&lt;p&gt;Decide the figure from what an outage actually costs you per hour, not from what sounds impressive. Most business systems are honestly served by 99.9%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service Credits Are Not Compensation
&lt;/h2&gt;

&lt;p&gt;The remedy in nearly every uptime SLA is a credit against future fees, usually a percentage of the monthly charge, and often capped at that month's total. &lt;a href="https://cloud.google.com/compute/sla" rel="noopener noreferrer"&gt;Google Cloud's compute SLA&lt;/a&gt; is a representative example: credits only, claimed by the customer, capped.&lt;/p&gt;

&lt;p&gt;That means the maximum you can recover is roughly what you paid for the period, which for most services is far less than an outage costs. A platform charged at a few hundred pounds a month cannot compensate a day of lost trading, and no supplier will agree to terms where it could.&lt;/p&gt;

&lt;p&gt;Credits are also frequently claim-based rather than automatic. You must notice the breach, calculate it, and submit a claim inside a window, sometimes as short as thirty days. Suppliers are not obliged to volunteer that they missed the target, and most do not.&lt;/p&gt;

&lt;p&gt;The practical conclusion is to stop treating the credit as protection. It is a signal of how seriously the supplier takes their own number. What protects you is architecture, and the recovery planning covered in our &lt;a href="https://mecanik.dev/en/posts/disaster-recovery-small-teams/" rel="noopener noreferrer"&gt;disaster recovery guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Clauses That Decide Everything
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What counts as downtime.&lt;/strong&gt; Total unavailability, or degraded performance too? A service responding in thirty seconds is unusable and, under most definitions, available. Get a latency threshold into the definition if response time matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it is measured, and by whom.&lt;/strong&gt; Almost always by the supplier, using their own monitoring, from their own network. That is measurement of their infrastructure rather than of your experience. Agree the measurement point, and run independent monitoring regardless, because you cannot dispute a number you did not observe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exclusions.&lt;/strong&gt; Planned maintenance is usually excluded entirely, which means a supplier can be unavailable for hours a month without breaching anything. Check how much notice is required and whether there is a cap. Also excluded: anything attributed to your configuration, to a third party, or to force majeure, and those categories can absorb a lot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The measurement window.&lt;/strong&gt; Monthly is standard and favours you. Annual measurement lets a supplier take a long outage and still meet the target, because a full day down is inside the yearly allowance at 99.9%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response versus resolution.&lt;/strong&gt; Many agreements commit to responding within a period and say nothing about fixing. A one hour response and an unbounded fix is a common shape and is much weaker than it reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Ask For Instead
&lt;/h2&gt;

&lt;p&gt;Push on the things that change behaviour rather than on the headline number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cap on planned maintenance&lt;/strong&gt;, with notice and a window outside your peak hours. This often affects real availability more than the percentage does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status transparency&lt;/strong&gt;, meaning a public status page and a commitment to post incidents promptly. A supplier who reports their own outages is telling you something about how they operate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A defined escalation path&lt;/strong&gt; with names or roles and times, so an incident does not begin with finding out who to contact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incident reports after significant outages&lt;/strong&gt;, describing what happened and what changed as a result. This is the single most informative document a supplier can give you, and a reluctance to provide one is itself informative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Termination rights on repeated failure.&lt;/strong&gt; Chronic underperformance should let you leave without penalty. That right is worth more than any credit, because it is the only remedy proportionate to the harm.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Are the One Offering an SLA
&lt;/h2&gt;

&lt;p&gt;Offer a number you can meet on your worst month, not your average one, and measure it yourself before you commit to it. Suppliers who commit to a figure they have never verified discover the gap in public.&lt;/p&gt;

&lt;p&gt;Define planned maintenance honestly and keep to the window. Publish a status page. Write the incident report even when nobody asks, because the customers who stay are the ones who believe you know what happened.&lt;/p&gt;

&lt;p&gt;And price the tiers by what they actually cost you to deliver. Four nines is not a marketing decision, it is redundant infrastructure, automated failover and a deployment process that cannot take the system down, and all of that has a bill attached. The operational groundwork in our &lt;a href="https://mecanik.dev/en/posts/ci-cd-pipeline-best-practices-for-uk-development-teams-in-2026/" rel="noopener noreferrer"&gt;CI/CD pipeline guide&lt;/a&gt; is where most of it starts.&lt;/p&gt;

&lt;p&gt;Mecanik builds and operates systems under availability commitments as part of our &lt;a href="https://mecanik.dev/en/services/server-security/" rel="noopener noreferrer"&gt;server security&lt;/a&gt; work. The commitment worth making is nearly always the one you can hold during a bad week.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/supply-chain-security/" rel="noopener noreferrer"&gt;Software Supply Chain Security for Small Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/how-to-build-a-web-app-in-2026-the-uk-developers-guide/" rel="noopener noreferrer"&gt;How to Build a Web App in 2026 - The UK Developer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-migration-cost-options-deadlines/" rel="noopener noreferrer"&gt;Drupal Migration in 2026: Costs, Options and Deadlines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/ai-agents-for-business-cost-failure-modes/" rel="noopener noreferrer"&gt;AI Agents for Business: What They Cost and Where They Fail&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How much downtime does 99.9% uptime allow?&lt;/strong&gt;&lt;br&gt;
8.76 hours a year, or 43.2 minutes a month. The step to 99.99% cuts that to 52.6 minutes a year and 4.32 minutes a month, a tenfold reduction that typically costs far more than ten times as much to deliver because it requires removing every single point of failure including the deployment process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are service credits real compensation for downtime?&lt;/strong&gt;&lt;br&gt;
No. Credits are usually a percentage of the monthly fee and capped at that month's charge, so the most you can recover is roughly what you paid, which is far less than an outage typically costs. They are also often claim-based with a short window, and suppliers rarely volunteer that they missed the target.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should I check in an uptime SLA?&lt;/strong&gt;&lt;br&gt;
What counts as downtime and whether degraded performance is included, who measures it and from where, what the exclusions cover, whether planned maintenance is capped and noticed, whether the window is monthly or annual, and whether the commitment is to respond or to resolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the measurement window matter?&lt;/strong&gt;&lt;br&gt;
Monthly measurement favours the customer. Annual measurement lets a supplier take one long outage and still meet the target, because a full day of downtime sits inside the yearly allowance at 99.9%. The same failure would breach a monthly agreement outright.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is worth negotiating instead of a higher number?&lt;/strong&gt;&lt;br&gt;
A cap on planned maintenance with notice outside peak hours, a public status page with prompt incident posting, a named escalation path with times, written incident reports after significant outages, and termination rights on repeated failure. The last is the only remedy proportionate to the harm.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>backend</category>
      <category>architecture</category>
      <category>automation</category>
    </item>
    <item>
      <title>Vibe Coding Technical Debt: Repair or Rebuild Your App?</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Tue, 03 Nov 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/vibe-coding-technical-debt-repair-or-rebuild-your-app-17do</link>
      <guid>https://dev.to/mecanik-dev/vibe-coding-technical-debt-repair-or-rebuild-your-app-17do</guid>
      <description>&lt;p&gt;Vibe coding technical debt becomes visible when a useful prototype turns into software that other people depend on. The app still looks convincing, but a small change breaks a different screen, nobody can explain which database is authoritative, and the person buying maintenance cannot tell whether the next proposal is a repair or a fresh start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Repair a vibe-coded application when its important workflows can be understood, reproduced and improved without unacceptable disruption. Consider a staged replacement when structural problems prevent that progress, and include data migration and operational continuity in the comparison. Decide from evidence about the application, rather than from how its code was originally written.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Interest in building apps this way is real. In its &lt;a href="https://blog.google/company-news/outreach-and-initiatives/grow-with-google/vibe-coding-course/" rel="noopener noreferrer"&gt;vibe coding curriculum announcement&lt;/a&gt;, Google reports increased US search interest and describes planning, testing and debugging as part of the learning path. That is evidence of attention to the approach, not proof that your particular application is defective or that a rebuild will pay for itself.&lt;/p&gt;

&lt;p&gt;This guide is for founders and operations teams with a working app and an uncertain next step. It offers a way to commission an assessment, compare proposals and preserve what the prototype has already taught you. The intake application below is hypothetical; it is not a customer case study or a measured return on investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recognise vibe coding technical debt by its consequences
&lt;/h2&gt;

&lt;p&gt;Technical debt is useful shorthand for design and implementation choices that make later changes more expensive or risky. Some choices are deliberate: a prototype may use a simple manual process while the team learns whether customers want the service. The problem is continuing to depend on that choice after its limits become material.&lt;/p&gt;

&lt;p&gt;Look for observable consequences. Can a developer reproduce a reported failure? Can your team release a change without editing the live database by hand? Do records have stable identifiers, or do different screens interpret the same customer differently? These questions turn an emotional argument about code quality into a reviewable maintenance problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate symptoms from an explanation
&lt;/h3&gt;

&lt;p&gt;A hypothetical customer-intake app sometimes creates duplicate enquiries. The symptom is a repeated record. Possible causes include repeated form submissions, duplicated integration events or inconsistent record matching. Asking an assistant to remove the visible duplicates might improve the screen while leaving the underlying operation unchanged.&lt;/p&gt;

&lt;p&gt;Record what happened, which path produced it and how the team detected it. Then ask for a reproducible explanation. The fact that AI wrote some code is not enough to identify the cause, and a large number of generated files is not by itself a reason to discard them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Establish what you actually own and can reproduce
&lt;/h2&gt;

&lt;p&gt;Before comparing repair and replacement, inventory the repository, hosting, database, domain, third-party integrations and administrative accounts. Confirm who controls each item and whether another developer can obtain the necessary access through an agreed process. Keep credentials out of the assessment document.&lt;/p&gt;

&lt;p&gt;Ask the assessor to build and run the application from the recorded source and documented configuration. If the live service depends on changes that exist only inside a hosting dashboard, identify them explicitly. A repository that looks complete may still fail to describe what customers are using.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://12factor.net/config" rel="noopener noreferrer"&gt;Twelve-Factor guidance on configuration&lt;/a&gt; separates deploy-specific configuration from application code. Use that distinction to request a configuration inventory without putting secrets into the repository or review brief. Record who controls each setting and how a replacement team obtains access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;What it tells the buyer&lt;/th&gt;
&lt;th&gt;What its absence means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reproducible build and configuration inventory&lt;/td&gt;
&lt;td&gt;Another team can work from the source&lt;/td&gt;
&lt;td&gt;The handover has unresolved dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow and integration map&lt;/td&gt;
&lt;td&gt;Records and business effects have an owner&lt;/td&gt;
&lt;td&gt;Repair scope cannot yet be trusted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Representative defect reproduction&lt;/td&gt;
&lt;td&gt;A proposed fix addresses an observed cause&lt;/td&gt;
&lt;td&gt;Estimates rely on assumptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore and release evidence&lt;/td&gt;
&lt;td&gt;Changes can be introduced and recovered deliberately&lt;/td&gt;
&lt;td&gt;Operational continuity needs investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;a href="https://12factor.net/dev-prod-parity" rel="noopener noreferrer"&gt;Twelve-Factor guidance on development and production parity&lt;/a&gt; explains why differences between environments create deployment problems. Apply the principle to the app's actual services and backing systems. A local demonstration using different storage or configuration is weak evidence that a production repair will behave the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make one valuable workflow understandable
&lt;/h2&gt;

&lt;p&gt;Choose a path the business already relies on. In the intake example, a visitor submits an enquiry, a colleague reviews it and an approved record reaches the CRM. Map the inputs, record identifiers, transformations and external effects along that path. Include rejected submissions and manual intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trace the whole path
&lt;/h3&gt;

&lt;p&gt;The app's success message might appear before the CRM accepts the record. If the business treats that message as proof of a completed handoff, the defect is partly an outcome-design problem. Repair should establish what success means and what the user sees when the destination has not confirmed it.&lt;/p&gt;

&lt;p&gt;Agree how uncertain cases are detected and who owns them. A record that might have reached the destination needs inspection before replay. This kind of clarification can be more valuable than a cosmetic rewrite of the interface because it changes how the team handles real work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmecanik.dev%2Fcdn-cgi%2Fimagedelivery%2FxqiSCiPBXNEeBkfBRiRspQ%2Fe6fa64aa-f9ca-4d96-d134-4ca418207c00%2F960x915" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmecanik.dev%2Fcdn-cgi%2Fimagedelivery%2FxqiSCiPBXNEeBkfBRiRspQ%2Fe6fa64aa-f9ca-4d96-d134-4ca418207c00%2F960x915" alt="An enquiry moves from submission through validation and review to CRM acknowledgement, with uncertain handoffs held for investigation." width="960" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Trace a business outcome across every boundary, including the point where the destination acknowledges the work.&lt;/em&gt; &lt;a href="https://mecanik.dev/cdn-cgi/imagedelivery/xqiSCiPBXNEeBkfBRiRspQ/f55621cb-3fad-4e06-e772-84e128cca600/diagrammaster" rel="noopener noreferrer"&gt;View full-size diagram&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Turn the path into acceptance evidence
&lt;/h3&gt;

&lt;p&gt;Use realistic examples that cover permitted, rejected and uncertain outcomes. An acceptance test should demonstrate the business rule, such as retaining the original enquiry reference when the same submission is received again. Avoid tests that simply repeat the implementation's current behaviour without deciding whether that behaviour is right.&lt;/p&gt;

&lt;p&gt;Preserve useful product learning. The wording that customers understand, the fields staff actually use and the exception routes already discovered may be worth keeping even when a component needs replacement. A rebuild that discards this knowledge can reproduce old mistakes in a cleaner repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare targeted repair with staged replacement
&lt;/h2&gt;

&lt;p&gt;Ask the assessor to explain alternatives against the same acceptance requirements. Repair may be appropriate when a problematic boundary is isolated and the surrounding workflow is understandable. Replacement may be justified when the required change repeatedly crosses inconsistent data models or inaccessible platform constraints.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Repair can be reasonable when&lt;/th&gt;
&lt;th&gt;Replacement deserves consideration when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate records&lt;/td&gt;
&lt;td&gt;A bounded matching or retry defect is reproducible&lt;/td&gt;
&lt;td&gt;Several incompatible record models prevent consistent identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unreliable releases&lt;/td&gt;
&lt;td&gt;Configuration and deployment can be documented and tested&lt;/td&gt;
&lt;td&gt;Essential behaviour cannot be reproduced in a supported environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Difficult changes&lt;/td&gt;
&lt;td&gt;Responsibilities can be separated incrementally&lt;/td&gt;
&lt;td&gt;Every change crosses unresolved structural dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing permissions&lt;/td&gt;
&lt;td&gt;Access checks can be enforced at the right boundary&lt;/td&gt;
&lt;td&gt;The underlying data model cannot support the required isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform limitations&lt;/td&gt;
&lt;td&gt;A supported export or integration meets the need&lt;/td&gt;
&lt;td&gt;Required ownership or operating controls are unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are assessment criteria, not an automatic scoring system. A serious defect in a critical workflow can outweigh several minor improvements elsewhere. The decision also depends on how much interruption the business can tolerate and whether the application is still proving its market.&lt;/p&gt;

&lt;p&gt;Be wary of a proposal that announces a rebuild before inspecting the data and release process. Be equally wary of a repair promise that avoids explaining how the next developer will verify the changes. Both approaches need a credible path from the current application to an accepted operational state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat security review as a separate workstream
&lt;/h2&gt;

&lt;p&gt;Maintainability and security overlap, but they answer different questions. Code that is easy to change can still expose another customer's records. A permission fix can reduce a security risk without making deployment or maintenance predictable. Specify both scopes when the app needs them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://owasp.org/projects/asvs" rel="noopener noreferrer"&gt;OWASP's Application Security Verification Standard&lt;/a&gt; provides a basis for verifying application security controls and defining requirements. It can help structure the security part of an assessment. A completed checklist does not by itself demonstrate that the app's business workflow is maintainable or that every risk has been removed.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://mecanik.dev/en/posts/vibe-coding-security-audit/" rel="noopener noreferrer"&gt;vibe coding security audit guide&lt;/a&gt; explains that review in more detail. For the repair decision here, request findings that distinguish urgent containment, targeted remediation and longer-term design changes. A report that calls every finding a reason to rewrite the whole application makes priorities harder to assess.&lt;/p&gt;

&lt;p&gt;Preserve investigation evidence if there are signs of an actual incident. Routine maintenance planning should not overwrite logs or reset environments that the incident owner needs to examine. Establish the current situation before treating every unexplained behaviour as ordinary technical debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan migration before approving replacement
&lt;/h2&gt;

&lt;p&gt;A replacement estimate is incomplete without a data and cutover plan. Identify authoritative records, attachments, customer accounts, external identifiers and the state of work in progress. Decide which history must move and how the team will verify its meaning after migration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/repositories/archiving-a-github-repository/backing-up-a-repository" rel="noopener noreferrer"&gt;GitHub's repository backup guidance&lt;/a&gt; distinguishes a mirror clone from additional work needed for Git LFS objects and wikis. A source backup is therefore only part of the inventory. Separately account for the application's live database, uploaded files and external services before treating a code export as a recovery plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prove the destination can preserve the workflow
&lt;/h3&gt;

&lt;p&gt;Run a rehearsal using controlled data and compare source and destination records. Check relationships and operational states as well as totals. An enquiry linked to the wrong organisation is wrong even if the record count matches. Document intentional transformations so they are not mistaken for lost information.&lt;/p&gt;

&lt;p&gt;Avoid running competing writers against the same business process without an explicit coordination plan. During a staged replacement, define which system owns each operation and how changes cross the boundary. Otherwise the migration can create the duplicate work the project was intended to remove.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmecanik.dev%2Fcdn-cgi%2Fimagedelivery%2FxqiSCiPBXNEeBkfBRiRspQ%2F8368d36c-6207-4629-71f6-4428d6492b00%2F960x915" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmecanik.dev%2Fcdn-cgi%2Fimagedelivery%2FxqiSCiPBXNEeBkfBRiRspQ%2F8368d36c-6207-4629-71f6-4428d6492b00%2F960x915" alt="Assessment evidence leads to a bounded repair or a staged replacement, with both routes requiring acceptance checks and operational handover." width="960" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Repair and replacement should meet the same business acceptance requirements. Replacement also needs a tested migration and cutover path.&lt;/em&gt; &lt;a href="https://mecanik.dev/cdn-cgi/imagedelivery/xqiSCiPBXNEeBkfBRiRspQ/8ff765dc-cd6c-4c50-5e08-a9924142c600/diagrammaster" rel="noopener noreferrer"&gt;View full-size diagram&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Define a stopping condition
&lt;/h3&gt;

&lt;p&gt;Agree what evidence permits the cutover and what would stop it. Consider missing records, incorrect access, unacknowledged integrations and unacceptable manual work. Define who makes the decision and how work continues if the release is delayed.&lt;/p&gt;

&lt;p&gt;Rollback is not simply redeploying yesterday's interface. Once the new system has changed records or triggered external operations, the previous application may not understand the resulting state. Ask the supplier to explain recovery after real business activity, including any changes that cannot be automatically reversed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare costs across the whole change
&lt;/h2&gt;

&lt;p&gt;Request a scoped GBP proposal separating assessment, repair, testing, migration and handover. Include assumptions about access to the source and the ability to export data. A fixed price based on an unexplored repository can conceal uncertainty rather than remove it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost area&lt;/th&gt;
&lt;th&gt;Targeted repair&lt;/th&gt;
&lt;th&gt;Staged replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Investigation&lt;/td&gt;
&lt;td&gt;Reproduce the failure and locate its boundary&lt;/td&gt;
&lt;td&gt;Establish current behaviour and replacement requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Correct or isolate the affected component&lt;/td&gt;
&lt;td&gt;Build the required destination behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;Demonstrate the repair and nearby regressions&lt;/td&gt;
&lt;td&gt;Demonstrate equivalence and intentional changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transition&lt;/td&gt;
&lt;td&gt;Release safely into the existing service&lt;/td&gt;
&lt;td&gt;Rehearse migration, coordinate cutover and reconcile data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ownership&lt;/td&gt;
&lt;td&gt;Document the improved system and remaining debt&lt;/td&gt;
&lt;td&gt;Transfer the replacement and retire dependencies deliberately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Include the business's own work: reviewing examples, preparing data, training colleagues and managing exceptions during transition. Those activities consume capacity even if they do not appear in a developer's estimate. Describe them explicitly instead of inventing a universal rebuild price or a promised productivity gain.&lt;/p&gt;

&lt;p&gt;The cheapest credible option is the one that meets the agreed outcome with acceptable ongoing ownership. Sometimes that is a focused repair. Sometimes a platform constraint makes replacement the practical choice. The assessment should give you enough evidence to understand why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commission a maintainable next step
&lt;/h2&gt;

&lt;p&gt;Prepare the application URL, a redacted example of the failing workflow, repository ownership details and the business consequence of the problem. Explain which changes you need next and which parts of the current product already work well. This gives an assessor something concrete to investigate.&lt;/p&gt;

&lt;p&gt;Ask for an initial deliverable that includes a reproducible finding, repair and replacement alternatives, assumptions, acceptance criteria and an account of remaining uncertainty. That is a better buying decision than commissioning an open-ended cleanup whose end state nobody can describe.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://mecanik.dev/en/services/software-development/" rel="noopener noreferrer"&gt;software development service&lt;/a&gt; is the starting point for discussing application repair, integration changes or a staged replacement. Where access, code and operational knowledge are being transferred between teams, our &lt;a href="https://mecanik.dev/en/posts/software-project-takeover/" rel="noopener noreferrer"&gt;software project takeover guide&lt;/a&gt; also explains what to request in the handover.&lt;/p&gt;

&lt;p&gt;To discuss your app, &lt;a href="https://mecanik.dev/en/contact/" rel="noopener noreferrer"&gt;send us the workflow that is becoming difficult to operate or change&lt;/a&gt;. We can use that brief to scope an assessment and the evidence needed for a repair-or-rebuild decision. The objective is software your team can own and operate, with its remaining limitations understood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/vibe-coding-security-audit/" rel="noopener noreferrer"&gt;Vibe Coding Security Audit: Before You Launch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/technical-documentation/" rel="noopener noreferrer"&gt;Technical Documentation That Gets Read&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/software-testing-strategies/" rel="noopener noreferrer"&gt;Software Testing Strategies That Survive Real Users&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/developer-onboarding/" rel="noopener noreferrer"&gt;Developer Onboarding That Ships in Week One&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is vibe coding technical debt?&lt;/strong&gt;&lt;br&gt;
Vibe coding technical debt describes implementation and design choices in an AI-assisted application that make later changes harder or riskier. Assess it through reproducibility, workflow behaviour, data integrity and ownership rather than assuming all generated code needs replacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does a vibe-coded app always need rebuilding?&lt;/strong&gt;&lt;br&gt;
No. Targeted repair can be appropriate when the important workflows are understandable and the defects can be isolated and verified. A replacement recommendation should explain the structural limitations, alternatives, migration work and operational consequences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should we check before hiring a developer to repair the app?&lt;/strong&gt;&lt;br&gt;
Confirm control of the source, hosting, data and integrations, then provide a reproducible example of the problem. Ask for acceptance criteria, a safe release approach and a handover that another developer can use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we compare repair and rebuild costs?&lt;/strong&gt;&lt;br&gt;
Compare scoped GBP proposals against the same business outcome. Include investigation, implementation, verification, migration where needed, internal staff time and ongoing ownership. A replacement price that omits data transition is not a complete comparison.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is a security audit enough to establish maintainability?&lt;/strong&gt;&lt;br&gt;
No. A security audit evaluates controls within an agreed scope. Maintainability also requires evidence that the application can be reproduced, changed, released, recovered and handed over, with its important business workflows understood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should we stop using the app while it is assessed?&lt;/strong&gt;&lt;br&gt;
Not automatically. Agree containment or restrictions when a specific risk warrants them. The assessment should explain which operations can continue and who owns exceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we keep the existing design when replacing the backend?&lt;/strong&gt;&lt;br&gt;
Often that is a useful option to investigate. Keep the interface decisions that customers understand, then test whether the replacement preserves their meaning. A familiar screen can still behave differently when validation, record ownership or integration timing changes. Include those behaviours in acceptance tests rather than treating visual similarity as proof of equivalence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the original platform cannot export everything?&lt;/strong&gt;&lt;br&gt;
Establish the missing records, files and relationships before agreeing a replacement scope. Ask for an evidence-based export rehearsal and document what remains manual or inaccessible. The business may need a staged transition, temporary access to the old system or a narrower replacement. Do not schedule a final cutover on the assumption that an untested export will become available. Any unresolved limitation should appear in the proposal and acceptance conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should the first assessment deliver?&lt;/strong&gt;&lt;br&gt;
A reproducible finding, repair and replacement options, explicit assumptions, acceptance criteria and a scoped next step. It should explain what remains unknown and what evidence would change the recommendation.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>vibecoding</category>
      <category>security</category>
      <category>automation</category>
    </item>
    <item>
      <title>Postmortems That Actually Change Something</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Mon, 02 Nov 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/postmortems-that-actually-change-something-43lf</link>
      <guid>https://dev.to/mecanik-dev/postmortems-that-actually-change-something-43lf</guid>
      <description>&lt;p&gt;A postmortem is easy to hold and hard to make useful. The meeting happens, a document is written, four action items are recorded, and six months later the same failure occurs and somebody finds the old document while searching for something else.&lt;/p&gt;

&lt;p&gt;The word "blameless" gets most of the attention in discussions of this, and it is genuinely important, but it is not where the failure is. Plenty of organisations run scrupulously blameless reviews that change nothing, because the review was treated as the deliverable rather than as the thing that produces one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The test of whether yours are working is embarrassingly simple: what proportion of action items from the last six months are done?&lt;/strong&gt; If the answer is most of them, the process is working whatever it looks like. If it is under half, you are holding meetings rather than running a process, and adding better templates will not fix it. Fewer items, owned and scheduled, beats a thorough list nobody has time for.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What a Postmortem Is Actually For
&lt;/h2&gt;

&lt;p&gt;Not to explain what happened. The people in the room usually know that by the end of the incident.&lt;/p&gt;

&lt;p&gt;It exists to find the conditions that allowed a small problem to become a large one, and to change some of them. That is a different question from what broke, and the distinction shows up immediately in the write-up. "The database ran out of connections" is what broke. "Nothing alerted on connection pool saturation, and the dashboard that would have shown it is not on the on-call runbook" is a condition, and it is actionable.&lt;/p&gt;

&lt;p&gt;The second thing it produces, which is undervalued, is a shared account of the event. Without one, everybody keeps their own version, and those versions diverge in the direction of whatever each person already believed about the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blameless Means Something Specific
&lt;/h2&gt;

&lt;p&gt;It does not mean nobody made a mistake. It means the analysis assumes that a person acting reasonably on the information available took an action that turned out to be wrong, and asks why that action looked correct at the time. &lt;a href="https://sre.google/sre-book/postmortem-culture/" rel="noopener noreferrer"&gt;Google's SRE book sets out the practice&lt;/a&gt; and the failure modes that come with it.&lt;/p&gt;

&lt;p&gt;The reason this is not merely kindness is that blame destroys information. In an organisation where incidents attach to individuals, people describe events carefully, omit the detail that looks bad, and delay reporting until they understand it well enough to present it safely. The review then works from an account that has been edited for self-protection, which is the worst possible input.&lt;/p&gt;

&lt;p&gt;Practically, blameless is a property of language. "Why did you restart the service?" invites defence. "What did the dashboard show when you decided to restart?" invites information. The second question is also the more useful one, because the answer is a fact about your monitoring rather than about a colleague.&lt;/p&gt;

&lt;p&gt;If a genuine performance problem exists, address it separately and directly. Doing it inside the incident review corrupts the review and is, in any case, a poor way to have that conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Timeline That Teaches
&lt;/h2&gt;

&lt;p&gt;The timeline is the most valuable part of the document and the part most often written badly, as a sequence of technical events with the humans removed.&lt;/p&gt;

&lt;p&gt;Include what people knew and when. Not just "the service was restarted at 14:32", but what the person could see at 14:32 and what they believed was happening. That is where the actionable material is, because a fifteen-minute delay caused by an ambiguous dashboard is a fixable problem and a fifteen-minute delay recorded as a gap is not.&lt;/p&gt;

&lt;p&gt;Mark the moments that mattered: when it started, when anybody first noticed, when the right person was involved, when the cause was understood, when it was mitigated, when it was resolved. The gaps between those are where the improvement lives, and detection time is very often the largest one.&lt;/p&gt;

&lt;p&gt;Write it while it is fresh, ideally within a day or two. Memory of what somebody believed at a particular moment decays much faster than memory of what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  There Is Rarely One Root Cause
&lt;/h2&gt;

&lt;p&gt;The phrase encourages stopping at the first plausible explanation, and incidents of any size are chains where several things had to hold simultaneously.&lt;/p&gt;

&lt;p&gt;A useful review lists contributing factors instead: the change that triggered it, the test that would have caught it and did not exist, the alert that fired to a channel nobody watches, the runbook that was accurate for a previous architecture. Each is a separate opportunity, and you can choose to fix the cheapest two rather than the one that happened to be first in the chain.&lt;/p&gt;

&lt;p&gt;This also reduces a common distortion. When a review names one root cause, that cause is very often "human error", which is a description rather than an explanation and closes the investigation exactly where it should have started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Follow-Up Happen
&lt;/h2&gt;

&lt;p&gt;This is where postmortems live or die, and the fixes are unglamorous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fewer items.&lt;/strong&gt; Two that get done beat eight that do not. Rank them by whether they reduce detection time, blast radius or likelihood, and cut the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A named owner and a date&lt;/strong&gt;, in your normal work tracker rather than in the document. Items that live only in the postmortem are invisible during planning, which is when work actually gets scheduled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review the open items at the next postmortem.&lt;/strong&gt; This single habit does more than any template. It makes incompletion visible and it turns the sequence of reviews into a process rather than a series of unrelated meetings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accept some items honestly.&lt;/strong&gt; A team that consistently completes half of what it commits to should commit to half as much. Recording an intention nobody will fund is worse than recording a decision not to act, because the second is at least true.&lt;/p&gt;

&lt;p&gt;The improvements that come out of this are usually the same family covered in our &lt;a href="https://mecanik.dev/en/posts/disaster-recovery-small-teams/" rel="noopener noreferrer"&gt;disaster recovery guide&lt;/a&gt; and in &lt;a href="https://mecanik.dev/en/posts/software-testing-strategies/" rel="noopener noreferrer"&gt;software testing strategies&lt;/a&gt;: faster detection, smaller blast radius, and a system somebody other than the original author can operate.&lt;/p&gt;

&lt;p&gt;Mecanik runs incident reviews on systems we maintain as part of our &lt;a href="https://mecanik.dev/en/services/server-security/" rel="noopener noreferrer"&gt;server security&lt;/a&gt; work. The pattern across most of them is that the technical cause was understood quickly and the detection gap was the expensive part.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/developer-onboarding/" rel="noopener noreferrer"&gt;Developer Onboarding That Ships in Week One&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/technical-documentation/" rel="noopener noreferrer"&gt;Technical Documentation That Gets Read&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/claude-ai-for-code-review-a-developers-guide-for-2026/" rel="noopener noreferrer"&gt;Claude AI for Code Review - A Developer's Guide for 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/the-software-development-life-cycle-explained-in-2026/" rel="noopener noreferrer"&gt;The Software Development Life Cycle Explained 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the point of a postmortem?&lt;/strong&gt;&lt;br&gt;
To find the conditions that let a small problem become a large one and change some of them, which is a different question from what broke. It also produces a shared account of the event, without which everyone keeps a private version that drifts toward what they already believed about the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does blameless actually mean?&lt;/strong&gt;&lt;br&gt;
That the analysis assumes a person acting reasonably on the information available took an action that turned out wrong, and asks why it looked correct at the time. It matters because blame destroys information: people edit their accounts for self-protection and the review then works from a corrupted input.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should a postmortem identify a single root cause?&lt;/strong&gt;&lt;br&gt;
No. Incidents of any size are chains where several conditions had to hold at once, so listing contributing factors is more useful. It lets you fix the cheapest two rather than whichever came first in the chain, and it avoids concluding with "human error", which is a description rather than an explanation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should an incident timeline include?&lt;/strong&gt;&lt;br&gt;
What people knew and when, not only what the systems did. Record when it started, when anybody first noticed, when the right person was involved, when the cause was understood, when it was mitigated and when it resolved. The gaps between those are where improvement lives, and detection time is usually the largest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do postmortem action items never get done?&lt;/strong&gt;&lt;br&gt;
Because they live in the document rather than in the work tracker, so they are invisible when work is scheduled. Fix it with fewer items, a named owner and a date in the normal tracker, and a review of open items at the start of the next postmortem, which makes incompletion visible.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>Drupal Hosting: What Actually Runs Drupal Well</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/drupal-hosting-what-actually-runs-drupal-well-5gnd</link>
      <guid>https://dev.to/mecanik-dev/drupal-hosting-what-actually-runs-drupal-well-5gnd</guid>
      <description>&lt;p&gt;Drupal hosting is where most of the "Drupal is slow" reputation comes from, and it is almost always a purchasing decision rather than a software problem. A site gets built properly, then goes live on a plan priced for a brochure site with a handful of PHP files. The result is a content management system with a serious render pipeline running inside a memory limit it cannot change, on an opcode cache it does not control, with no shell to run its own tooling.&lt;/p&gt;

&lt;p&gt;The comparison people make in their heads is with WordPress, and it is the wrong one. WordPress runs acceptably on almost anything because its market share forced hosts to make it run acceptably on almost anything. Drupal assumes a current PHP, a recent database, a real cache backend, a command line, and a deployment process that treats the codebase as a build artefact rather than a folder you edit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What does Drupal actually need from a host?&lt;/strong&gt; A PHP version at or above the floor for your release, a current MySQL, MariaDB or PostgreSQL, 256MB of PHP memory in practice, OPcache, shell access for Composer and Drush, a real cron entry, and an external object cache once you have logged-in users. Cheap shared hosting fails three or four of those at once.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Drupal Actually Requires From a Server
&lt;/h2&gt;

&lt;p&gt;The published requirements are short, specific and public, and almost nobody reads them before buying. They are also version-specific in a way that matters right now, because two of the floors move in December 2026.&lt;/p&gt;

&lt;h3&gt;
  
  
  The PHP version floor is not negotiable
&lt;/h3&gt;

&lt;p&gt;Drupal 11 requires PHP 8.3 as a minimum and supports 8.3, 8.4 and 8.5. Drupal 10 requires 8.1 and supports up to 8.4, and Drupal 12 raises the floor again to PHP 8.5. Those numbers come from &lt;a href="https://www.drupal.org/docs/getting-started/system-requirements/php-requirements" rel="noopener noreferrer"&gt;Drupal's own PHP requirements documentation&lt;/a&gt;, which is the only version of the list worth trusting.&lt;/p&gt;

&lt;p&gt;This matters more than it looks, because the PHP branches themselves expire. &lt;a href="https://www.php.net/supported-versions.php" rel="noopener noreferrer"&gt;php.net's supported versions page&lt;/a&gt; puts the end of security support for PHP 8.2 at 31 December 2026, with 8.3 running to 31 December 2027 and 8.4 to 31 December 2028. PHP 8.1 is already past it. A host advertising "PHP 8.1 and 8.2 available" is offering a stack that is unpatched now or unpatched within months.&lt;/p&gt;

&lt;p&gt;The two dates collide. Drupal 10 reaches end of life on 9 December 2026 and Drupal 12 ships the same week, according to the &lt;a href="https://www.drupal.org/about/core/policies/core-release-cycles/schedule" rel="noopener noreferrer"&gt;core release schedule&lt;/a&gt;. If your host cannot serve PHP 8.3 or newer, you cannot run a supported Drupal after that. Our guide to &lt;a href="https://mecanik.dev/en/posts/drupal-migration-cost-options-deadlines/" rel="noopener noreferrer"&gt;Drupal migration costs, options and deadlines&lt;/a&gt; covers what that means if you are still on 10.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database engines and their real minimums
&lt;/h3&gt;

&lt;p&gt;Drupal 11 wants MariaDB 10.6 or later, MySQL 8.0 or later, PostgreSQL 16 or later, or SQLite 3.45 or later, per the &lt;a href="https://www.drupal.org/docs/getting-started/system-requirements/database-server-requirements" rel="noopener noreferrer"&gt;database server requirements&lt;/a&gt;. Drupal 10 is more forgiving at MariaDB 10.3.7, MySQL 5.7.8, PostgreSQL 12 and SQLite 3.26, which is exactly why an upgrade sometimes forces a database upgrade the client was not expecting.&lt;/p&gt;

&lt;p&gt;Two details get skipped. InnoDB must be the storage engine on MySQL and MariaDB, because Drupal relies on transactions and row-level locking. On PostgreSQL, the &lt;code&gt;pg_trgm&lt;/code&gt; extension has to be created on the Drupal database before installation, and a managed database service that does not let you run &lt;code&gt;CREATE EXTENSION&lt;/code&gt; is not usable.&lt;/p&gt;

&lt;p&gt;SQLite is genuinely supported and fine for local development, but it is not a production answer once several editors are saving content, because write concurrency becomes the ceiling first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Memory, extensions and the web server
&lt;/h3&gt;

&lt;p&gt;Drupal's documented minimum is 64MB of PHP memory, and it shows warnings below that. The same page notes that 128MB or 256MB is typical on production and that media-heavy installations need more. In practice, treat 256MB as the working figure and expect to raise it for the command line, because Composer and large migrations are the memory-hungry operations, not page serving.&lt;/p&gt;

&lt;p&gt;The extension list is unremarkable and worth checking anyway: PDO with a database driver, XML, JSON, mbstring, cURL, OpenSSL for outbound HTTPS, and either GD or ImageMagick for image derivatives. Drupal 12 adds Argon2 for password hashing, which is one more thing a very old PHP build will not have.&lt;/p&gt;

&lt;p&gt;On the web server, Drupal supports Apache 2.4.7 or later and Nginx 1.1 or later, and &lt;a href="https://www.drupal.org/docs/getting-started/system-requirements/web-server-requirements" rel="noopener noreferrer"&gt;does not support Microsoft IIS as of Drupal 11.0.0&lt;/a&gt;. Apache needs &lt;code&gt;mod_rewrite&lt;/code&gt; for clean URLs and &lt;code&gt;AllowOverride All&lt;/code&gt; so the shipped &lt;code&gt;.htaccess&lt;/code&gt; applies. That last point catches people: some of Drupal's protective rules exist only in &lt;code&gt;.htaccess&lt;/code&gt;, so an Nginx deployment has to reproduce them in the server configuration by hand. It is a routine step that is routinely skipped, and it is one of the first things we look for in a &lt;a href="https://mecanik.dev/en/server-security-audit/" rel="noopener noreferrer"&gt;server security audit&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Shared Hosting Fails Drupal
&lt;/h2&gt;

&lt;p&gt;Shared hosting is not bad hosting. It is hosting optimised for a different application shape, and Drupal breaks against its constraints in four predictable places.&lt;/p&gt;

&lt;h3&gt;
  
  
  No shell means no Composer and no Drush
&lt;/h3&gt;

&lt;p&gt;Modern Drupal is a Composer project. Core, contributed modules and their PHP dependencies are all resolved by Composer, and once Composer manages one module it has to manage core as well. Mixing Composer with manual file updates is how a site ends up unable to update at all.&lt;/p&gt;

&lt;p&gt;A control panel with a file manager cannot do that. Neither can an FTP client. Without SSH you also lose Drush, which is how cache rebuilds, configuration imports, database updates and user password resets are actually performed. A site you cannot run &lt;code&gt;drush cr&lt;/code&gt; against is a site where every recovery step becomes a support ticket.&lt;/p&gt;

&lt;h3&gt;
  
  
  The limits you cannot see, let alone change
&lt;/h3&gt;

&lt;p&gt;A shared account gives you a &lt;code&gt;memory_limit&lt;/code&gt; set by someone else, usually 128MB and sometimes lower, with no route to raise it for the one migration that needs 512MB.&lt;/p&gt;

&lt;p&gt;OPcache is the bigger problem. It stores precompiled script bytecode in shared memory so PHP does not reparse files on every request, and on a shared host that memory pool is shared across hundreds of accounts. Drupal has thousands of PHP files, so it is a heavy tenant of a pool it is competing for, and it gets evicted. The symptom is a site that is quick for a minute after a visit and slow again an hour later.&lt;/p&gt;

&lt;p&gt;Then there is what is missing entirely: no Redis, no Memcached, no control over the PHP process manager, and no way to run a long queue worker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cron that never really runs
&lt;/h3&gt;

&lt;p&gt;Drupal's Automated Cron module runs &lt;a href="https://www.drupal.org/docs/administering-a-drupal-site/cron-automated-tasks/cron-automated-tasks-overview" rel="noopener noreferrer"&gt;every three hours by default and is triggered by end users visiting the site&lt;/a&gt;. On a busy site that means a visitor occasionally pays for search indexing with their page load. On a quiet site it means cron effectively does not run, so the search index goes stale, log tables never get trimmed and available security updates are never checked.&lt;/p&gt;

&lt;p&gt;The documentation recommends triggering cron externally instead, because it always runs on schedule and uses fewer resources. That needs a real crontab entry, which the cheapest tier does not provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Caching Layers and Which One Your Visitor Hits
&lt;/h2&gt;

&lt;p&gt;Drupal has more caching than most people realise, and the layers are not alternatives. They stack, and each one catches what the layer above it could not.&lt;/p&gt;

&lt;h3&gt;
  
  
  OPcache sits below Drupal entirely
&lt;/h3&gt;

&lt;p&gt;OPcache is not a Drupal feature. It caches compiled PHP bytecode at the interpreter level, so it applies to every request whether or not any Drupal cache hits. Get this wrong and nothing above it can compensate, because every request pays to recompile the framework before it reaches the router. Size the shared memory generously and disable timestamp validation in production, where the file set only changes on deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal Page Cache and Dynamic Page Cache
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.drupal.org/docs/administering-a-drupal-site/internal-page-cache" rel="noopener noreferrer"&gt;Internal Page Cache&lt;/a&gt; is a core module, enabled by default, and it serves anonymous users only. It assumes every anonymous visitor sees the same page, stores the whole response on first request, and reuses it. For a marketing site with no personalisation, this is the layer that does almost all the work.&lt;/p&gt;

&lt;p&gt;Dynamic Page Cache is also core and also enabled by default, and it caches for anyone, including authenticated users. It works by having the render system turn the genuinely personal parts of a page into placeholders, caching everything around them. That is why a logged-in Drupal page can still be mostly cached: only the user menu and a few blocks are actually dynamic.&lt;/p&gt;

&lt;h3&gt;
  
  
  BigPipe and the render cache
&lt;/h3&gt;

&lt;p&gt;Below both sits the render cache, which caches individual blocks, fields, views results and entity renders. A page that misses the page cache is usually assembled largely from render cache hits rather than rebuilt from the database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.drupal.org/docs/8/core/modules/big-pipe/overview" rel="noopener noreferrer"&gt;BigPipe&lt;/a&gt; handles what is left. It has been in core since Drupal 8.1, stable since 8.3, and in the standard install profile since 8.5. Instead of waiting for every placeholder to resolve, it flushes the cacheable page immediately and streams the personalised fragments in afterwards. It needs no configuration, and it helps authenticated users far more than anonymous ones.&lt;/p&gt;

&lt;p&gt;So the anonymous visitor on a well-configured site hits Internal Page Cache, or the CDN in front of it, and never touches most of this. The logged-in editor hits Dynamic Page Cache, the render cache and BigPipe on every request, which is why authenticated traffic costs so much more.&lt;/p&gt;

&lt;h3&gt;
  
  
  The external object cache
&lt;/h3&gt;

&lt;p&gt;Every layer above needs somewhere to store its entries. By default that is the database, in cache tables, which means your cache reads compete with your content queries on the same server.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.drupal.org/project/redis" rel="noopener noreferrer"&gt;Redis module&lt;/a&gt; moves cache, lock, flood and queue backends onto Redis or a compatible store such as Valkey, using either the PhpRedis extension, the Relay extension or the pure-PHP Predis library. Memcached is the equivalent alternative. For a small anonymous site this changes little. For a site with logged-in users it is usually the single largest available improvement, because it removes the noisiest write load from the database and makes locking cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse Proxy and CDN in Front of Drupal
&lt;/h2&gt;

&lt;p&gt;A reverse proxy such as Varnish or Nginx, or a CDN, answers requests before PHP is involved at all. For anonymous traffic this is the difference between serving a page in single-digit milliseconds and serving it in a few hundred. It is also the layer people are most afraid of, because a stale cache on a news site or a shop is a visible failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache tags are what make a CDN safe
&lt;/h3&gt;

&lt;p&gt;Drupal's answer is cache tags. &lt;a href="https://www.drupal.org/docs/develop/drupal-apis/cache-api/cache-tags" rel="noopener noreferrer"&gt;Cache tags describe data dependencies&lt;/a&gt;, written as strings like &lt;code&gt;node:5&lt;/code&gt;, &lt;code&gt;user:3&lt;/code&gt; or &lt;code&gt;node_list&lt;/code&gt;. Every cached item records which tags it depends on, so editing node 5 invalidates every cached fragment, page and view that referenced it, wherever it appeared.&lt;/p&gt;

&lt;p&gt;The important part is that Drupal can publish those tags outward. Contributed modules emit them as a &lt;code&gt;Surrogate-Key&lt;/code&gt; header for Fastly or a &lt;code&gt;Cache-Tag&lt;/code&gt; header for Cloudflare, and the CDN then purges by tag when Drupal says so. That converts a CDN from a time-based gamble into an event-driven cache: you can set a long time to live, because an edit purges exactly the affected URLs within seconds.&lt;/p&gt;

&lt;p&gt;Watch the header budget. Cloudflare's &lt;a href="https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-tags/" rel="noopener noreferrer"&gt;purge by cache tag documentation&lt;/a&gt; caps the aggregate &lt;code&gt;Cache-Tag&lt;/code&gt; header at 16KB after the field name, roughly 1,000 unique tags, with a maximum of 1,024 characters per tag in an API call and 100 tags per dashboard purge. A Drupal view listing many entities can generate far more tags than that, so a page that lists everything will silently overflow the header unless the module is configured to trim or hash them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Drupal Hosting: Four Tiers, Honestly
&lt;/h2&gt;

&lt;p&gt;There are four real options, and the correct one is decided by whether you have authenticated traffic and whether you have anyone to operate the server. The bands below are what UK clients typically pay in our experience, exclusive of VAT, and are indicative rather than quoted from any vendor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Typical monthly cost&lt;/th&gt;
&lt;th&gt;What it buys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;GBP 3 to GBP 15&lt;/td&gt;
&lt;td&gt;Nothing Drupal needs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unmanaged VPS or cloud&lt;/td&gt;
&lt;td&gt;GBP 20 to GBP 120&lt;/td&gt;
&lt;td&gt;Full control, no operator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed Drupal platform&lt;/td&gt;
&lt;td&gt;GBP 40 to GBP 800 and up&lt;/td&gt;
&lt;td&gt;Opinionated stack and workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom infrastructure&lt;/td&gt;
&lt;td&gt;GBP 400 upwards&lt;/td&gt;
&lt;td&gt;Everything, plus the obligation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Shared hosting
&lt;/h3&gt;

&lt;p&gt;Suits nobody running Drupal in production. It fails on shell access, memory, opcode caching, object caching and cron simultaneously. If budget genuinely caps here, a small unmanaged instance at a similar price is a better use of the money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unmanaged VPS or cloud instance
&lt;/h3&gt;

&lt;p&gt;Roughly GBP 20 to GBP 120 a month buys a machine with full root, which suits the large majority of Drupal sites. You choose the PHP version, size OPcache, install Redis, set a crontab and configure Nginx properly. What you do not get is anyone to do it, patch it, monitor it or restore it at 2am. This tier is correct when you have a developer or agency on retainer, and the retainer is the real cost, not the instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed Drupal specialist platforms
&lt;/h3&gt;

&lt;p&gt;Managed Drupal hosting starts around GBP 40 a month for a small site and rises quickly with traffic, environments and support tier. What you buy is an opinionated stack that is already correct, plus Git-based deployment, staging environments, backups and someone who understands Drupal answering the phone. It is good value when the alternative is nobody, and poor value when you pay platform prices for a brochure site that gets 4,000 visits a month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full custom infrastructure
&lt;/h3&gt;

&lt;p&gt;Separate database, dedicated cache nodes, application servers behind a load balancer, object storage for files. This starts to make sense above roughly GBP 400 a month, and only when authenticated traffic, integrations or compliance requirements make the managed tiers awkward. It is the most capable tier and the most demanding, because you now own patching, monitoring and disaster recovery. Our &lt;a href="https://mecanik.dev/en/posts/drupal-web-development-a-2026-guide/" rel="noopener noreferrer"&gt;Drupal web development guide&lt;/a&gt; covers where that complexity comes from on the application side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment: You Do Not Edit Files on the Server
&lt;/h2&gt;

&lt;p&gt;Because Composer resolves the entire dependency tree, the server's codebase is an output, not a workspace. Editing a module file in place means the next &lt;code&gt;composer update&lt;/code&gt; overwrites it, and it means your production code no longer matches anything in Git.&lt;/p&gt;

&lt;p&gt;A sane release process builds the artefact somewhere else. Run &lt;code&gt;composer install&lt;/code&gt; from the committed &lt;code&gt;composer.lock&lt;/code&gt; in CI, so the build is reproducible and the production host never needs Composer, PHP memory for dependency resolution, or write access to &lt;code&gt;vendor&lt;/code&gt;. Ship the result, run database updates, import configuration, rebuild caches. Roll back by pointing at the previous artefact.&lt;/p&gt;

&lt;p&gt;That also settles the hosting question quietly. A host that expects you to edit files over FTP is incompatible with the way Drupal is maintained, whatever its specification sheet says.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where configuration synchronisation fits
&lt;/h3&gt;

&lt;p&gt;Drupal keeps active configuration in the database and exports it to YAML files, which is how content types, fields, views and settings move between environments. The &lt;a href="https://www.drupal.org/docs/configuration-management/managing-your-sites-configuration" rel="noopener noreferrer"&gt;configuration management documentation&lt;/a&gt; explains that the site UUID has to match between source and destination, which is the usual reason a first import fails.&lt;/p&gt;

&lt;p&gt;Practically, this means configuration is code. It is committed, reviewed and deployed with the rest, and the import step runs as part of the release rather than being clicked through in the admin interface afterwards. Hosting has to support that: you need a place to run the import, and an environment where a failed import can be rolled back rather than half-applied to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Files, Media and Backups
&lt;/h2&gt;

&lt;p&gt;Drupal has two file systems and the difference is load-bearing. The public one lives under the web root and is served directly by the web server. The private one lives outside the web root, and every request for a private file passes through Drupal so that access can be checked.&lt;/p&gt;

&lt;p&gt;Private files are therefore far more expensive than public ones, because each download bootstraps PHP, so a site serving large private documents needs headroom a static file server would not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Offloading media to object storage
&lt;/h3&gt;

&lt;p&gt;Once files live on the application server, horizontal scaling and rebuilds become painful, and every backup carries the whole media library. Moving the public file system to S3-compatible object storage separates the two, lets the CDN serve media directly, and makes an application server genuinely disposable.&lt;/p&gt;

&lt;h3&gt;
  
  
  A restore test proves what a backup cannot
&lt;/h3&gt;

&lt;p&gt;A backup proves a file exists. A restore test proves the file is complete, that the database and the files are from the same moment, that your credentials still work, and that you know how long it takes. Those are four separate failure modes and none of them are visible from a green tick in a control panel.&lt;/p&gt;

&lt;p&gt;Test it against a real timer at least twice a year and write the number down. If the restore takes six hours and your tolerance is one, the architecture is the problem, not the backup. Recovery time is a hosting requirement, and it belongs in the specification rather than in an incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sizing a Drupal Host Properly
&lt;/h2&gt;

&lt;p&gt;Pageviews are the wrong unit. A site with 200,000 anonymous monthly pageviews behind a CDN can be comfortable on a small instance, while a site with 8,000 pageviews can struggle if most of them are logged in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticated traffic is the real driver
&lt;/h3&gt;

&lt;p&gt;Anonymous requests can be answered by the page cache or the CDN without touching PHP. Authenticated requests cannot. Every one of them runs the render pipeline, checks access on each entity, resolves placeholders and writes session data. The practical question is not how many visits you get, it is how many concurrent logged-in users you have, and what those users are allowed to see.&lt;/p&gt;

&lt;p&gt;Editors are the extreme case. Content administration screens are among the heaviest pages in Drupal, and they are uncacheable by definition, so a site with twelve editors working simultaneously has a genuine concurrency requirement that its public traffic never suggests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Views, taxonomy and queue work
&lt;/h3&gt;

&lt;p&gt;Beyond authenticated traffic, the reliable cost centres are large views with many filters and relationships, which generate expensive joins; deep taxonomy trees, where term hierarchies are traversed on every render; and cron or queue work such as search indexing, feed imports and media derivative generation.&lt;/p&gt;

&lt;p&gt;Queue workers should not share the web server's resources with visitors if you can avoid it. On a larger site they belong on a separate process or instance, so an import backlog cannot make the front end slow. That is an architecture decision made at buying time, and it is the point at which the managed tiers stop fitting and custom infrastructure starts to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the Decision Right
&lt;/h2&gt;

&lt;p&gt;The pattern is consistent. The hosting is not undersized, it is the wrong shape: no shell, no object cache, an opcode cache someone else controls, and a PHP version about to fall out of support. Moving the same site onto a correctly specified instance at a similar price usually beats any amount of front-end optimisation.&lt;/p&gt;

&lt;p&gt;Mecanik specifies, builds and maintains Drupal infrastructure as part of our &lt;a href="https://mecanik.dev/en/services/website-development/" rel="noopener noreferrer"&gt;website development&lt;/a&gt; work, and reviews existing stacks through a &lt;a href="https://mecanik.dev/en/server-security-audit/" rel="noopener noreferrer"&gt;server security audit&lt;/a&gt; when the concern is exposure rather than speed. If you need people rather than a platform, our guide to &lt;a href="https://mecanik.dev/en/posts/hire-drupal-developer-rates-skills-vetting/" rel="noopener noreferrer"&gt;hiring a Drupal developer&lt;/a&gt; covers what to look for.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-ai/" rel="noopener noreferrer"&gt;Drupal AI: What the AI Modules Actually Do&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/wordpress-7-ai-client/" rel="noopener noreferrer"&gt;WordPress 7.0 and the AI Client in Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/wordpress-plugin-development/" rel="noopener noreferrer"&gt;WordPress Plugin Development That Survives Core Updates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/how-to-build-a-web-app-in-2026-the-uk-developers-guide/" rel="noopener noreferrer"&gt;How to Build a Web App in 2026 - The UK Developer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-seo/" rel="noopener noreferrer"&gt;Drupal SEO: The Technical Setup That Actually Ranks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What are the minimum server requirements for Drupal 11?&lt;/strong&gt;&lt;br&gt;
PHP 8.3 or newer, and MariaDB 10.6, MySQL 8.0, PostgreSQL 16 or SQLite 3.45 as the database. Apache 2.4.7 or Nginx 1.1 and later on the web server side, with Microsoft IIS unsupported from Drupal 11.0.0 onwards. Drupal warns below 64MB of PHP memory, but 256MB is the realistic production figure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can Drupal run on shared hosting?&lt;/strong&gt;&lt;br&gt;
It will install and it will serve pages, but shared hosting usually fails three or four requirements at once: no shell access for Composer or Drush, a PHP memory limit you cannot raise, no Redis or Memcached, and cron that only fires when a visitor happens to load a page. Those conditions are what produce the reputation for Drupal being slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much should Drupal hosting cost?&lt;/strong&gt;&lt;br&gt;
A small Drupal site on a properly configured unmanaged instance typically sits around GBP 20 to GBP 120 a month before anyone manages it. Managed Drupal platforms commonly start near GBP 40 and rise into the hundreds with traffic and environments. Custom infrastructure starts to make sense above roughly GBP 400. The cheapest tier is rarely the cheapest outcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Drupal need Redis?&lt;/strong&gt;&lt;br&gt;
Not for a small anonymous site, where the internal page cache and the database are adequate. Once you have logged-in users, editors or a members area, an external object cache such as Redis or Memcached moves cache reads, locks and queues off the database, and it is usually the single largest improvement available for the money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is a CDN safe in front of a dynamic Drupal site?&lt;/strong&gt;&lt;br&gt;
Yes, provided you invalidate by cache tag rather than by time. Drupal records tags such as node:5 for everything a response depends on, and modules translate those into Cache-Tag or Surrogate-Key headers so the CDN purges exactly the pages an edit affected. Without tag invalidation you are choosing between stale pages and a cache that never helps.&lt;/p&gt;

</description>
      <category>php</category>
      <category>drupal</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Drupal 12: What Changes and When to Move</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Sun, 01 Nov 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/drupal-12-what-changes-and-when-to-move-7na</link>
      <guid>https://dev.to/mecanik-dev/drupal-12-what-changes-and-when-to-move-7na</guid>
      <description>&lt;p&gt;Drupal 12 is scheduled for the week of 7 December 2026, and Drupal 10 reaches end of life on 9 December 2026. Both dates sit on the same page of the &lt;a href="https://www.drupal.org/about/core/policies/core-release-cycles/schedule" rel="noopener noreferrer"&gt;Drupal core release schedule&lt;/a&gt;, two lines apart, and most people running a Drupal 10 site have noticed neither. The first alpha of the new major was tagged on 2 September 2026, so the shape of the release is now a matter of record rather than speculation.&lt;/p&gt;

&lt;p&gt;That collision is the whole story. A major version arriving is not usually urgent for a site owner, because you can sit on the previous major for a year or two while the ecosystem catches up. This time the previous major stops receiving security advisories in the same week the new one ships, which converts a technical event into a deadline with a compliance edge on it.&lt;/p&gt;

&lt;p&gt;What follows is the calendar as drupal.org publishes it, what actually changes in the code, what the new platform floors force on your hosting, the three realistic upgrade paths with cost bands, and a plan working backwards from December. The reassuring part arrives early: most of a Drupal major version bump is deletion, not reinvention.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When does Drupal 12 come out, and do I have to move?&lt;/strong&gt; Drupal 12.0.0 is scheduled for the week of 7 December 2026, released alongside Drupal 11.5.0. Drupal 10 reaches end of life two days later on 9 December 2026, after which no further security advisories are issued for it. A Drupal 11 site faces a small upgrade. A Drupal 10 site has to pass through Drupal 11.4 or later first, so the work is two hops rather than one.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Two Dates That Decide Your Next Six Months
&lt;/h2&gt;

&lt;p&gt;The release managers publish the whole cycle in advance, and the current one is unusually tidy. Drupal 11.4.0 landed in the week of 29 June 2026, and that release ended security support for both Drupal 11.2.x and Drupal 10.5.x. The 12.0.0-alpha1 tag went out on 2 September 2026. Beta requirements had to be complete by 11 September 2026, with 12.0.0-beta1 and 11.5.0-beta1 in the week of 14 September and release candidates in the week of 9 November.&lt;/p&gt;

&lt;p&gt;Then the week of 7 December does three things at once. Drupal 12.0.0 ships, Drupal 11.5.0 ships alongside it, and security support ends for both Drupal 11.3.x and Drupal 10.6.x. Two days later, on 9 December 2026, Drupal 10 as a whole reaches end of life and no further releases of it will be made at all.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Week of 29 June 2026&lt;/td&gt;
&lt;td&gt;Drupal 11.4.0 released, security support ends for 11.2.x and 10.5.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 September 2026&lt;/td&gt;
&lt;td&gt;Drupal 12.0.0-alpha1 tagged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Week of 14 September 2026&lt;/td&gt;
&lt;td&gt;Drupal 12.0.0-beta1 and 11.5.0-beta1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Week of 9 November 2026&lt;/td&gt;
&lt;td&gt;Drupal 12.0.0-rc1 and 11.5.0-rc1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Week of 7 December 2026&lt;/td&gt;
&lt;td&gt;Drupal 12.0.0 and 11.5.0 released, security support ends for 11.3.x and 10.6.x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 December 2026&lt;/td&gt;
&lt;td&gt;Drupal 10 end of life&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why Drupal 10 and Drupal 12 Land in the Same Week
&lt;/h2&gt;

&lt;p&gt;This is policy, not coincidence. The &lt;a href="https://www.drupal.org/about/core/policies/core-release-cycles/release-process-overview" rel="noopener noreferrer"&gt;release process overview&lt;/a&gt; states that major versions arrive every two years in even years, and that each major is supported for a minimum of four years, until two further majors have been released. Drupal 10.0.0 came out on 15 December 2022. Drupal 11 arrived in August 2024 and Drupal 12 arrives in December 2026, which is the second further major, and four years have passed. The clock ran out exactly on schedule.&lt;/p&gt;

&lt;p&gt;The same policy governs minors. Each minor version is supported for one year, with bug fixes and security fixes for the first six months and security fixes only for the last six. That is why only 10.6.x still receives advisories today, and why 11.3.x loses its coverage the moment 11.5.0 ships.&lt;/p&gt;

&lt;p&gt;Drupal 11 does not stop when Drupal 12 starts. Releasing 11.5.0 in the same week begins what the policy calls the Long Term Support phase, in which the previous major keeps an API-matched minor, moves onto an LTS release of Symfony, and receives a maintenance minor every six months with a shrinking scope. Drupal.org does not publish a firm Drupal 11 end of life date, though its own &lt;a href="https://www.drupal.org/docs/core-modules-and-themes/deprecated-and-obsolete" rel="noopener noreferrer"&gt;deprecated extensions documentation&lt;/a&gt; says Drupal 11 will be supported until mid to late 2028.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Many Sites Are Still Sitting on Drupal 10
&lt;/h2&gt;

&lt;p&gt;The numbers are public and they are not comfortable. Drupal.org's &lt;a href="https://www.drupal.org/project/usage/drupal" rel="noopener noreferrer"&gt;core usage statistics&lt;/a&gt; for the week beginning 23 August 2026 record 468,877 sites reporting a core version. Of those, 205,568 are on some branch of Drupal 10 and 168,857 are on Drupal 11. Roughly 44 per cent of the reporting installed base is on the version that stops receiving advisories in December.&lt;/p&gt;

&lt;p&gt;The sharper number is inside that figure. Only 10.6.x still has security coverage, and 10.6.x accounts for 139,911 of those sites. The remaining 65,657 are on 10.0 through 10.5, which means they are running an unsupported minor today, in September, without waiting for December to arrive.&lt;/p&gt;

&lt;p&gt;These counts come from sites that voluntarily report through the Update Status module, so the real population is larger and skews the same way. The practical reading is that a very large number of organisations are going to try to book the same upgrade work in the same quarter, and agency capacity in October and November will be the constraint rather than the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What End of Life Actually Means for a Drupal Site
&lt;/h2&gt;

&lt;p&gt;End of life is not a switch that breaks the site. Your Drupal 10 install will keep serving pages on 10 December exactly as it did on 8 December. What changes is that the Drupal Security Team stops publishing advisories and patches for that code, so from that date every newly discovered vulnerability in Drupal 10 core is permanent.&lt;/p&gt;

&lt;p&gt;The second effect is slower and does more damage. Contributed module security coverage depends on the module having a stable release on a supported core branch, so as maintainers drop Drupal 10 compatibility, the modules on your site quietly leave the advisory process too. You do not get a notification when this happens. The module simply stops appearing in security releases, and the available updates report on your own site keeps looking green.&lt;/p&gt;

&lt;p&gt;The third effect is that the exit gets more expensive the longer you wait. A Drupal 10 site upgraded in November is upgraded against a maintained core branch with a working update path. The same site upgraded in the following June is a recovery project, because the contributed modules it depends on have had six more months to move on without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cyber Essentials, Insurance and Contract Clauses
&lt;/h2&gt;

&lt;p&gt;This is where an unsupported CMS stops being an engineering concern. The NCSC's &lt;a href="https://www.ncsc.gov.uk/files/cyber-essentials-requirements-for-it-infrastructure-v3-3.pdf" rel="noopener noreferrer"&gt;Cyber Essentials requirements for IT infrastructure v3.3&lt;/a&gt;, dated April 2026, states that all software on in-scope devices must be licensed and supported, and must be removed from devices when it becomes unsupported, or removed from scope by using a defined subset that prevents all traffic to or from the internet. The control applies to servers, IaaS, PaaS and SaaS, so a Drupal install on a server in scope is squarely covered.&lt;/p&gt;

&lt;p&gt;A public-facing Drupal 10 site cannot be firewalled off the internet, so after 9 December 2026 the two available answers are to upgrade it or to accept that it fails that control. If your organisation holds Cyber Essentials or Cyber Essentials Plus and renews it annually, that is a question you will be asked in writing on your next assessment.&lt;/p&gt;

&lt;p&gt;Be careful about what you claim beyond that. Whether a specific cyber insurance policy or client contract is affected depends entirely on its wording, and the clauses that matter are usually the ones requiring supported software or vendor-supported versions rather than anything naming Drupal. Read your own policy and your own master services agreements before December rather than after an incident, because that is the cheap time to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Actually Different in Drupal 12
&lt;/h2&gt;

&lt;p&gt;Very little, and that is the honest and useful answer. The &lt;a href="https://www.drupal.org/project/drupal/releases/12.0.0-alpha1" rel="noopener noreferrer"&gt;12.0.0-alpha1 release notes&lt;/a&gt; put it plainly: 12.0.x will be nearly identical to 11.5.x except that deprecated code is removed including entire deprecated modules, dependencies are updated to new major versions, and system requirements are raised. For every other change, the notes tell you to read the 11.5.x branch.&lt;/p&gt;

&lt;p&gt;There are a handful of genuine behaviour changes worth knowing. The default password hashing algorithm switches to argon2id, with bcrypt available through kernel parameters where argon2 is unavailable. The core robots.txt now blocks search result pages carrying query parameters, which stops search engines crawling infinite faceted combinations, and sites with a customised robots.txt need to add those disallow rules by hand. HTMX, which core already ships, moves from version 2 to version 4 for beta1.&lt;/p&gt;

&lt;p&gt;One more is easy to miss. Hosting Drupal directly on Windows in a production environment is deprecated in Drupal 12, on the grounds that there is no automated Windows test environment and few developers testing on it. Windows remains supported for local development. If you run production on Windows, that is a hosting decision to make in the next few months rather than a code change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extensions That Leave Core
&lt;/h2&gt;

&lt;p&gt;Drupal has spent years moving narrow modules out of core into contributed projects, and Drupal 12 continues that. The alpha1 notes list Ban, Contact, Field Layout, History, Settings Tray, Shortcut and Telephone as removed, along with the Stable 9 theme. The Text with Summary field plugin has also moved to its own contributed module. Ban was deprecated back in 11.3, Contact, Field Layout, History and Telephone in 11.4, and Settings Tray, Shortcut and Text with Summary in 11.5.&lt;/p&gt;

&lt;p&gt;Two of those will surprise people. Shortcut and Settings Tray are administrative features that a great many editorial teams use daily without ever thinking of them as optional, and Settings Tray in particular underpins the in-place block configuration that content editors rely on.&lt;/p&gt;

&lt;p&gt;The mechanics of handling this matter more than the list. The correct move is to add the contributed version to your Composer requirements before you upgrade, not to uninstall the module. Uninstalling destroys the extension's configuration, and Drupal's module discovery looks in core last, so once the contributed project is present Drupal simply uses it. Note also that Drush can bypass the warnings on update.php about missing extensions, so the failure surfaces afterwards as errors on the status report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Losing Migrate Drupal Is the Change That Bites Hardest
&lt;/h2&gt;

&lt;p&gt;The Migrate Drupal and Migrate Drupal UI modules are removed in Drupal 12 and, unlike the rest, they are not being moved to a contributed project. Drupal 12 keeps the Migrate API and the destination plugins for modern Drupal, but it does not keep the source plugins for Drupal 6 and Drupal 7.&lt;/p&gt;

&lt;p&gt;Read that again if you own a Drupal 7 site. The tooling that reads a legacy Drupal database and writes it into a modern one exists in Drupal 11 and does not exist in Drupal 12. Drupal.org's guidance is explicit: sites on Drupal 6 or Drupal 7 that intend to use the migration API should continue to migrate to Drupal 11, and then use the ordinary update process to move from Drupal 11 to Drupal 12.&lt;/p&gt;

&lt;p&gt;That turns a vague intention into a hard sequencing constraint. A Drupal 7 rebuild that lands after Drupal 11 goes out of support has to either build its own source plugins, restore an older core to run the migration in a throwaway environment, or export and re-import the content by other means. All three are more expensive than doing the migration into Drupal 11 while Drupal 11 is a current, maintained target. Our guide to &lt;a href="https://mecanik.dev/en/posts/drupal-migration-cost-options-deadlines/" rel="noopener noreferrer"&gt;Drupal migration costs, options and deadlines&lt;/a&gt; covers the shape of that work in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Dependency Floors
&lt;/h2&gt;

&lt;p&gt;A major version is where Drupal is allowed to raise its platform requirements, and Drupal 12 uses that allowance across the board. These floors are the part of the upgrade you cannot negotiate with, because they are enforced at install time.&lt;/p&gt;

&lt;h3&gt;
  
  
  PHP 8.5, and nothing older
&lt;/h3&gt;

&lt;p&gt;Drupal 12 requires PHP 8.5. The &lt;a href="https://www.drupal.org/docs/getting-started/system-requirements/php-requirements" rel="noopener noreferrer"&gt;PHP requirements table&lt;/a&gt; shows Drupal 12.0 supporting PHP 8.5 and refusing everything below it, while Drupal 11.3 and 11.4 accept 8.3, 8.4 and 8.5. That overlap is your migration route: move the site to PHP 8.5 while still on Drupal 11.4, confirm it behaves, then change Drupal.&lt;/p&gt;

&lt;p&gt;The floor is generous rather than punishing. PHP 8.5 was released on 20 November 2025, and &lt;a href="https://www.php.net/supported-versions.php" rel="noopener noreferrer"&gt;php.net's supported versions page&lt;/a&gt; puts its active support to 31 December 2027 and security support to 31 December 2029. Landing on it buys three years before this conversation happens again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Databases and Symfony
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.drupal.org/docs/getting-started/system-requirements/database-server-requirements" rel="noopener noreferrer"&gt;database server requirements&lt;/a&gt; for Drupal 12 are MySQL 8.0 or later, MariaDB 10.11 or later, PostgreSQL 18 or later, and SQLite 3.45 with the json1 extension. PostgreSQL sites should treat that floor as the one to verify carefully, because the alpha1 release notes say PostgreSQL 19 while the requirements page and the installer code both say 18. Check it again at beta1 before you book database work.&lt;/p&gt;

&lt;p&gt;Underneath, Symfony moves from 7.4 to 8.1 and Guzzle from 7 to 8. Support is dropped for several older library majors including doctrine/lexer 2, egulias/email-validator 3 and guzzlehttp/psr7 2. Custom code that type hints Symfony classes directly is where this shows up.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the floors force on your hosting
&lt;/h3&gt;

&lt;p&gt;The MariaDB jump is the one that catches shared and managed hosts. Drupal 11 accepts MariaDB 10.6, whose community maintenance ended on 6 July 2026 according to the &lt;a href="https://mariadb.org/about/maintenance-policy/" rel="noopener noreferrer"&gt;MariaDB maintenance policy&lt;/a&gt;, so a Drupal 11 site can currently sit on an unsupported database engine quite legitimately. Drupal 12 lifts the floor to 10.11, which is maintained until 16 February 2028. If your host cannot offer PHP 8.5 and MariaDB 10.11 today, the hosting move has to happen before the Drupal move, and that reordering is what turns a two-week job into a two-month one. Our note on &lt;a href="https://mecanik.dev/en/posts/drupal-hosting/" rel="noopener noreferrer"&gt;what actually runs Drupal well&lt;/a&gt; covers the platform side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Deprecation Model Makes Drupal 12 Tractable
&lt;/h2&gt;

&lt;p&gt;Here is the mechanism most site owners have never had explained, and it is the reason Drupal majors are no longer frightening. The &lt;a href="https://www.drupal.org/about/core/policies/core-change-policies/continuous-upgrades-between-major-versions" rel="noopener noreferrer"&gt;continuous upgrades policy&lt;/a&gt; commits core to a simple promise: the next major release has the same public API as the last minor release of the previous major. New APIs are added in minors, old ones are marked deprecated in minors, and the deletion happens only at the major boundary.&lt;/p&gt;

&lt;p&gt;The practical consequence is worth stating in plain terms. If your custom code and your contributed modules run on Drupal 11.5 with no deprecation warnings, they run on Drupal 12. The upgrade stops being a rewrite and becomes a dependency bump plus a database update, because everything that would have broken was already reported to you months earlier as a warning you could have fixed at leisure.&lt;/p&gt;

&lt;p&gt;This is also why the release notes tell you to update to 11.4 or later first, and strongly recommend 11.5. The database update path from releases prior to 11.4.0 has been removed from Drupal 12 outright, so a site on 11.3 or earlier has no route into 12 until it moves up the 11 branch first. That is not advice, it is a missing code path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tools That Report Deprecations
&lt;/h2&gt;

&lt;p&gt;Two projects do the work, and both are currently maintained. &lt;a href="https://www.drupal.org/project/upgrade_status" rel="noopener noreferrer"&gt;Upgrade Status&lt;/a&gt; is the site-wide scanner. You install it on the site you are upgrading from, not the one you are upgrading to, because the deprecated APIs have to exist for it to find calls to them. It checks whether your environment meets the next major's system requirements, cross-references your contributed projects against available updates, runs PHPStan for deprecated PHP API use, and reads Twig templates, info.yml files, composer.json and deprecated configuration keys. Release 5.0.0-alpha3, from 2 July 2026, declares compatibility with Drupal 10.4, 11 and 12.&lt;/p&gt;

&lt;p&gt;It also categorises what it finds, which is the part that saves money. Problems are sorted into those a machine can fix and those a person has to, so you can cost the manual half before committing to a date. It runs under Drush as &lt;code&gt;upgrade_status:analyze&lt;/code&gt;, and its Code Climate JSON output plugs into GitLab CI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/palantirnet/drupal-rector" rel="noopener noreferrer"&gt;Drupal Rector&lt;/a&gt; is the other half. It rewrites the mechanically fixable deprecations in your custom modules and themes, with a &lt;code&gt;--dry-run&lt;/code&gt; flag to preview the diff first. Version 1.1.2 was released on 7 August 2026. Between the two, a competent developer can produce a defensible readiness report for a mid-sized site in two to three days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path One: Drupal 11 to Drupal 12
&lt;/h2&gt;

&lt;p&gt;If you are on Drupal 11.4 or 11.5 with current contributed modules, this is a small piece of work. The &lt;a href="https://www.drupal.org/docs/upgrading-drupal/upgrading-drupal/how-to-upgrade-from-drupal-11-to-drupal-12" rel="noopener noreferrer"&gt;official upgrade guide&lt;/a&gt; is mostly Composer commands: require the version 12 metapackages with &lt;code&gt;--no-update&lt;/code&gt;, drop any explicit &lt;code&gt;drupal/core&lt;/code&gt; requirement, run &lt;code&gt;composer update --dry-run&lt;/code&gt;, then run it for real and apply database updates with &lt;code&gt;drush updatedb&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The real work sits either side of that. Beforehand, run Upgrade Status, add contributed replacements for any removed core extension you actually use, and confirm your host offers PHP 8.5. Afterwards, expect every core scaffold file to have changed, including .htaccess, so any customisation you made to those has to be reapplied deliberately rather than merged blindly.&lt;/p&gt;

&lt;p&gt;When a dependency refuses to resolve, &lt;code&gt;composer why-not drupal/core ^12&lt;/code&gt; names the blocker. Allowing two majors of a module in composer.json, for instance &lt;code&gt;"^6.1 || ^7.0"&lt;/code&gt;, is the standard way to bridge a project mid-transition. If you need a module that has a working patch but no tagged release, the Drupal Lenient Composer endpoint exists for exactly that and is still maintained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path Two: Drupal 10 to Drupal 12 Is Two Hops
&lt;/h2&gt;

&lt;p&gt;There is no direct Drupal 10 to Drupal 12 upgrade. The Upgrade Status documentation says so explicitly, and the removal of the pre-11.4 database update path is what enforces it. You go from Drupal 10.6 to Drupal 11.4 or 11.5, verify the site, and then go from there to Drupal 12.&lt;/p&gt;

&lt;p&gt;Planned properly, that is not twice the work. The Drupal 10 to Drupal 11 hop carries almost all of the risk, because that is where the contributed module compatibility problems live and where custom code meets removed APIs. The second hop is the small one described above. Teams that try to compress both into a single change window usually end up unable to tell which of the two broke something.&lt;/p&gt;

&lt;p&gt;The sequencing that works is to do the Drupal 11 hop now, run the site on 11.4 or 11.5 for several weeks so real editorial and traffic behaviour surfaces anything odd, and take Drupal 12 in the new year once contributed modules have tagged stable releases against it. Doing the first hop before December is what matters, because it is the hop that takes you off unsupported code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path Three: Drupal 7 or 8 Is a Rebuild, Not an Upgrade
&lt;/h2&gt;

&lt;p&gt;Anything older than Drupal 9 is a different exercise. Drupal 7 reached end of life on 5 January 2025 and Drupal 6 in February 2016. Neither upgrades in place at all, because they predate the modern architecture entirely. They are migrated, which means a new site is built on current Drupal and the content is moved into it with the Migrate API. A Drupal 8 site technically does have an in-place route, but it runs through four consecutive majors and every contributed module has to survive each one, so it is normally cheaper to treat it as a rebuild too.&lt;/p&gt;

&lt;p&gt;The cost is dominated by everything that is not content. The theme is rebuilt, custom modules are rewritten against an entirely different API, and integrations are reconnected. In our experience the content migration itself is usually the smaller half of the budget, which is the opposite of what most owners expect when they ask for a quote, and it is the reason we scope these as &lt;a href="https://mecanik.dev/en/services/website-development/" rel="noopener noreferrer"&gt;website development&lt;/a&gt; projects rather than upgrades.&lt;/p&gt;

&lt;p&gt;For these sites the December deadline works differently but still bites, because of the Migrate Drupal removal. Your target has to be Drupal 11, not Drupal 12, and Drupal 11 is supported until mid to late 2028 according to drupal.org's own documentation. That gives a Drupal 7 owner a genuine window, but it is a window with a hard end, and starting a six-month rebuild in 2028 to hit a 2028 target is not a plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Each Path Costs in the UK
&lt;/h2&gt;

&lt;p&gt;These are house estimates from our own delivery, not published rates, and the spread inside each band is driven almost entirely by contributed module health rather than by site size. UK agencies bill roughly £600 to £900 a day for this work. A Drupal 11 to 12 upgrade on a maintained site is three to eight days including testing, which lands around £2,000 to £6,000. Where the same site has stale contributed modules, allow two to four weeks and £6,000 to £12,000.&lt;/p&gt;

&lt;p&gt;A Drupal 10 site costs both hops. Well maintained, the Drupal 10 to 11 hop is £6,000 to £15,000 and the Drupal 12 hop adds £2,000 to £6,000, so £8,000 to £21,000 in total across four to eight weeks. Neglected, the first hop alone runs £15,000 to £35,000 and the total lands between £17,000 and £41,000. A Drupal 7 rebuild is three to six months and commonly £40,000 to £120,000, higher for large or heavily customised sites.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Starting point&lt;/th&gt;
&lt;th&gt;Realistic effort&lt;/th&gt;
&lt;th&gt;House band&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Drupal 11.4 or 11.5, maintained&lt;/td&gt;
&lt;td&gt;3 to 8 days&lt;/td&gt;
&lt;td&gt;£2,000 to £6,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drupal 11.x, stale contributed modules&lt;/td&gt;
&lt;td&gt;2 to 4 weeks&lt;/td&gt;
&lt;td&gt;£6,000 to £12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drupal 10, well maintained&lt;/td&gt;
&lt;td&gt;4 to 8 weeks, two hops&lt;/td&gt;
&lt;td&gt;£8,000 to £21,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drupal 10, neglected&lt;/td&gt;
&lt;td&gt;8 to 14 weeks, two hops&lt;/td&gt;
&lt;td&gt;£17,000 to £41,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drupal 7 or 8&lt;/td&gt;
&lt;td&gt;3 to 6 months&lt;/td&gt;
&lt;td&gt;£40,000 to £120,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Contributed Module Audit That Sets Your Date
&lt;/h2&gt;

&lt;p&gt;Upgrade projects rarely die on core. They die on the fourteenth module in the list, the one nobody remembers installing, which has no release compatible with the next major and a maintainer who last commented in 2023. Do this audit before you commit to a date, because it is the audit that produces the date.&lt;/p&gt;

&lt;p&gt;Run Upgrade Status and export the report, then sort your modules into four buckets. The first is projects with a stable release supporting the target major, which cost nothing. The second is projects with a patch or a development release in the issue queue, which cost a small amount of integration work and carry the risk that the patch never lands. The third is projects with an open issue and no patch, which need somebody to write one. The fourth is projects with no activity at all.&lt;/p&gt;

&lt;p&gt;The fourth bucket sets your timeline, and its size is knowable today rather than in November. A site with thirty contributed modules and nothing in bucket four is a straightforward job. The same site with four modules in bucket four is a different engagement with a different budget, and the difference between those two quotes is a day of scanning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do About an Abandoned Module
&lt;/h2&gt;

&lt;p&gt;There are four honest options and the right one depends on what the module does. Remove it, if the feature it provides is no longer used, which is more often true than teams expect after several years of editorial drift. Replace it with a maintained project that does the same job, accepting the configuration migration that comes with that.&lt;/p&gt;

&lt;p&gt;Take over maintenance, which is a real option in Drupal and less daunting than it sounds. Drupal.org has a documented process for becoming the maintainer of an unsupported project, and for a small module that your business depends on, adopting it can be cheaper than replacing it. The cost is ongoing rather than one-off, so account for it honestly.&lt;/p&gt;

&lt;p&gt;Or reimplement the behaviour in a custom module scoped to what you actually use. A contributed module solves the general case for everybody, while you usually need one narrow slice of it. Reimplementing that slice against current APIs is frequently a two-day job against a two-week port, and it removes the dependency permanently. Our guidance on &lt;a href="https://mecanik.dev/en/posts/hire-drupal-developer-rates-skills-vetting/" rel="noopener noreferrer"&gt;hiring a Drupal developer&lt;/a&gt; covers how to vet somebody for exactly this kind of judgement.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Timeline Working Backwards From 9 December 2026
&lt;/h2&gt;

&lt;p&gt;Start from the end date and the plan writes itself. By late September, run Upgrade Status against Drupal 12 on a copy of production and get the four buckets on paper. That is a two to three day exercise and it is the only artefact that lets you cost anything else honestly.&lt;/p&gt;

&lt;p&gt;By mid October, confirm your hosting can deliver PHP 8.5 and the database floors, and start the move if it cannot. Also decide the contributed module questions, because every one of them has a lead time attached. By early November, have the Drupal 11.4 or 11.5 upgrade complete on a Drupal 10 site, with the site running on the new branch in production.&lt;/p&gt;

&lt;p&gt;By early December you are watching the 12.0.0 release rather than reacting to it. If you are on Drupal 11 at that point, take Drupal 12 in January or February 2027, once contributed projects have tagged stable releases against it. There is no prize for upgrading in release week, and Drupal 11.5 will still be supported. The prize is for not being on Drupal 10 when the advisories stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Costs to Do Nothing
&lt;/h2&gt;

&lt;p&gt;The direct cost is that every Drupal core vulnerability disclosed after 9 December 2026 stays open on your site permanently. Drupal's advisory history includes remote code execution issues serious enough to be exploited within hours of publication, and an unpatched CMS on a public IP is found by automated scanning, not by a targeted attacker who chose you.&lt;/p&gt;

&lt;p&gt;The indirect costs arrive sooner and are usually larger. Failing the supported-software control on a Cyber Essentials assessment can affect eligibility for contracts that require the certification, which in UK public sector procurement is common. Contributed modules stop shipping fixes for your branch. And the upgrade itself gets more expensive every month, because the gap between your codebase and the maintained ecosystem widens without anybody touching anything.&lt;/p&gt;

&lt;p&gt;There is a quieter cost too. A site nobody is allowed to upgrade tends to become a site nobody is allowed to change, and feature work stops because every change has to be built against an API that is going away. That is how a five-year-old Drupal site turns into a rebuild rather than an upgrade. If you are weighing that decision, our &lt;a href="https://mecanik.dev/en/posts/drupal-web-development-a-2026-guide/" rel="noopener noreferrer"&gt;Drupal development guide&lt;/a&gt; is a better starting point than a quote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Legitimate Reasons to Wait
&lt;/h2&gt;

&lt;p&gt;Waiting is defensible in two situations, and only if you wait deliberately. The first is that you are already on Drupal 11.4 or 11.5. Those branches are supported, they are the designated launchpad for Drupal 12, and there is no advantage in taking a brand new major in its first weeks while contributed projects are still tagging releases. Waiting until the first quarter of 2027 is the professional choice, not the lazy one.&lt;/p&gt;

&lt;p&gt;The second is a Drupal 7 site with a funded rebuild already scheduled. Migrating Drupal 7 into Drupal 11 and then immediately into Drupal 12 is wasted motion. Land on Drupal 11, run it, and take Drupal 12 as ordinary maintenance later.&lt;/p&gt;

&lt;p&gt;What is not defensible is sitting on Drupal 10 without a booked plan. If that is you, the minimum acceptable position by the end of September is a scan report, a named target branch, and a date in a calendar. Everything else can move. If you want that assessment done by somebody who has run it before, our &lt;a href="https://mecanik.dev/en/services/software-development/" rel="noopener noreferrer"&gt;software development&lt;/a&gt; team does version audits as a fixed-scope piece of work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Start
&lt;/h2&gt;

&lt;p&gt;Run the scan first. Nearly every bad Drupal upgrade quote in existence was produced without one, which is why so many of them are wrong in both directions. Two to three days of Upgrade Status and Drupal Rector output tells you which of the five cost bands above you are actually in, and that single number changes the conversation with your board more than any amount of general advice about major versions.&lt;/p&gt;

&lt;p&gt;Mecanik runs those audits and the upgrades that follow, on Drupal 10 sites facing December and on Drupal 11 sites planning a calmer move in 2027. Custom module work, integrations and the deprecation clean-up sit with our &lt;a href="https://mecanik.dev/en/services/software-development/" rel="noopener noreferrer"&gt;software development&lt;/a&gt; practice, while a rebuild or a hosting move belongs with &lt;a href="https://mecanik.dev/en/services/website-development/" rel="noopener noreferrer"&gt;website development&lt;/a&gt;. If security posture is the reason this landed on your desk, start with our note on &lt;a href="https://mecanik.dev/en/posts/drupal-security/" rel="noopener noreferrer"&gt;Drupal security advisories and real risk&lt;/a&gt;, and if organic traffic is the worry during a version move, the &lt;a href="https://mecanik.dev/en/posts/drupal-seo/" rel="noopener noreferrer"&gt;Drupal SEO setup&lt;/a&gt; piece covers what to protect.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-commerce/" rel="noopener noreferrer"&gt;Drupal Commerce: When It Beats Shopify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-support/" rel="noopener noreferrer"&gt;Drupal Support: What a Retainer Should Cover&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/how-to-build-a-web-app-in-2026-the-uk-developers-guide/" rel="noopener noreferrer"&gt;How to Build a Web App in 2026 - The UK Developer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/salesforce-vs-hubspot/" rel="noopener noreferrer"&gt;Salesforce vs HubSpot: Choosing a CRM in 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;When is Drupal 12 released and when does Drupal 10 reach end of life?&lt;/strong&gt;&lt;br&gt;
Drupal 12.0.0 is scheduled for the week of 7 December 2026, released alongside Drupal 11.5.0, and Drupal 10 reaches end of life on 9 December 2026. Both dates are published on the Drupal core release schedule. In the same week, security support ends for the 11.3.x and 10.6.x minor branches. Drupal 12.0.0-alpha1 was tagged on 2 September 2026.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I upgrade directly from Drupal 10 to Drupal 12?&lt;/strong&gt;&lt;br&gt;
No. The database update path from releases prior to Drupal 11.4.0 has been removed from Drupal 12, so a Drupal 10 site has to move to Drupal 11.4 or later first and then to Drupal 12. Drupal.org recommends 11.5.0 or higher before the major move. Plan it as two hops, with the Drupal 10 to 11 hop carrying nearly all the risk and cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the Drupal 12 system requirements?&lt;/strong&gt;&lt;br&gt;
Drupal 12 requires PHP 8.5 and drops support for PHP 8.4 and earlier. The database floors are MySQL 8.0, MariaDB 10.11, PostgreSQL 18 and SQLite 3.45 with the json1 extension. Symfony moves to 8.1 and Guzzle to 8.0. Hosting Drupal directly on Windows in production is deprecated, though Windows remains supported for local development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is actually new in Drupal 12 compared to Drupal 11.5?&lt;/strong&gt;&lt;br&gt;
Almost nothing, by design. The alpha1 release notes state that 12.0.x is nearly identical to 11.5.x apart from removed deprecated code, updated dependency majors and raised system requirements. The behaviour changes worth noting are argon2id as the default password hashing algorithm, HTMX moving to version 4, and a core robots.txt that blocks search result pages with query parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does a Drupal 12 upgrade cost in the UK?&lt;/strong&gt;&lt;br&gt;
On a maintained Drupal 11 site, three to eight days of work at typical UK agency rates of £600 to £900 a day, so roughly £2,000 to £6,000. A Drupal 10 site pays for both hops, landing between £8,000 and £21,000 when well maintained and £17,000 to £41,000 when neglected. A Drupal 7 rebuild is three to six months and commonly £40,000 to £120,000. These are house estimates, not published rates.&lt;/p&gt;

</description>
      <category>php</category>
      <category>drupal</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>WordPress Plugin Development That Survives Core Updates</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Sun, 01 Nov 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/wordpress-plugin-development-that-survives-core-updates-5bk4</link>
      <guid>https://dev.to/mecanik-dev/wordpress-plugin-development-that-survives-core-updates-5bk4</guid>
      <description>&lt;p&gt;Most WordPress plugin development follows the same arc. Somebody needs a booking form, a feed importer or an extra field at checkout, a developer writes it, it works, everyone moves on. Two years later the site is stuck on an old version of WordPress because nobody is confident that plugin will survive an update, and whoever wrote it has gone.&lt;/p&gt;

&lt;p&gt;The cause is rarely that core moves too fast. WordPress is conservative about breaking things, and plenty of well written plugins from five years ago still run unchanged on WordPress 7.1. Plugins break because of decisions taken in the first week: functionality put in the theme, core files edited instead of hooked, data stored in whichever structure was nearest, and nobody ever testing against a release candidate.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What makes a custom WordPress plugin survive core updates?&lt;/strong&gt; Four things. The code lives in a plugin rather than the theme. It extends WordPress through actions and filters instead of editing core files. It stores each piece of data in the structure that matches that data's shape. And somebody tests it against every release candidate before that release ships.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why WordPress Plugin Development Belongs in a Plugin, Not the Theme
&lt;/h2&gt;

&lt;p&gt;The default home for custom code is the theme's &lt;code&gt;functions.php&lt;/code&gt;, because it is already there and it already runs. It is also the file that disappears at the next redesign.&lt;/p&gt;

&lt;p&gt;A theme is presentation. Change themes and everything the old theme was doing stops. Custom post types stop being registered, so the content sits in the database with no admin screen and no permalink. Shortcodes render as raw text mid page. The analytics snippet, the schema markup and the nightly call to the CRM all go, and nothing raises an error.&lt;/p&gt;

&lt;p&gt;The rule is simple enough to put in a brief. Anything that must still be true after a redesign belongs in a plugin: custom post types and taxonomies, integrations with anything external, shortcodes and blocks, business rules, scheduled jobs, and anything that writes to the database. The theme keeps templates, styles and template parts.&lt;/p&gt;

&lt;p&gt;The price arrives late. At the next redesign you either pay again to rebuild what already existed, or copy the old &lt;code&gt;functions.php&lt;/code&gt; forward. On a site with years of accumulated snippets that is several thousand pounds of avoidable work, and it is why a redesign quote comes back at twice what the client expected. A child theme is still a theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Extension Model, and the One Rule That Matters
&lt;/h2&gt;

&lt;p&gt;WordPress is built to be changed from the outside. The mechanism is hooks, and the &lt;a href="https://developer.wordpress.org/plugins/hooks/" rel="noopener noreferrer"&gt;hooks documentation&lt;/a&gt; describes them as the predefined spots where one piece of code can interact with or modify another. An action fires at a defined moment and lets you do something: send a notification after a post publishes, or register a post type. A filter hands you a value, expects you to change it or leave it alone, and expects you to hand it back.&lt;/p&gt;

&lt;p&gt;The rule that follows is absolute. If you are editing a file inside &lt;code&gt;wp-admin&lt;/code&gt;, &lt;code&gt;wp-includes&lt;/code&gt; or another plugin's directory, you have already lost. Those edits are erased by the next update, with no warning, no error and usually no discovery until a client reports that something stopped working. Ask a developer this directly before hiring them.&lt;/p&gt;

&lt;p&gt;When the hook you need does not exist, wrap the behaviour rather than replacing it, move up to a broader hook, fork the third party plugin under version control with the divergence documented, or ask upstream for the hook, which is how most of them got added.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming, Prefixes and One Very Crowded Namespace
&lt;/h2&gt;

&lt;p&gt;PHP in WordPress runs in a single global namespace shared with core, the active theme and every other active plugin. Two plugins that both declare a function called &lt;code&gt;get_settings()&lt;/code&gt; do not compete politely: the second is a fatal error and the site is white.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefixes are longer than you think
&lt;/h3&gt;

&lt;p&gt;The handbook's &lt;a href="https://developer.wordpress.org/plugins/plugin-basics/best-practices/" rel="noopener noreferrer"&gt;plugin best practices page&lt;/a&gt; asks for a unique prefix on everything globally accessible, at least four characters and preferably five, avoiding common English words and never &lt;code&gt;wp_&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt; or &lt;code&gt;WordPress&lt;/code&gt; itself. With tens of thousands of plugins in circulation, three letters from the client's initials is a coin toss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Namespaces and autoloading
&lt;/h3&gt;

&lt;p&gt;Modern practice solves half of the problem. Declare a PHP namespace, put one class in one file, and let a PSR-4 autoloader find them, so there are no manual &lt;code&gt;require&lt;/code&gt; statements and no chance of a class name colliding with another plugin's. It also makes the code testable, because classes with constructor dependencies can be instantiated without WordPress being loaded.&lt;/p&gt;

&lt;p&gt;Autoloading does not fix two plugins shipping different versions of the same library. Whichever loads first wins. Prefix vendor namespaces at build time for anything distributed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The strings namespaces cannot help with
&lt;/h3&gt;

&lt;p&gt;A namespace covers PHP symbols. Much of what a plugin registers is not a PHP symbol but a string in a shared registry, and those still need the old prefix convention: hook names, option and transient keys, post meta keys, post type and taxonomy names, shortcode tags, cron event names, REST namespaces and custom table names. They live in a flat space where the last registration wins or two plugins silently share state.&lt;/p&gt;

&lt;p&gt;Two limits are worth knowing before naming anything. A post type key must not exceed 20 characters and a taxonomy key must not exceed 32, both lowercase alphanumeric with dashes and underscores. A five character prefix leaves 15 characters for a post type name, which is less room than it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing Where the Data Lives
&lt;/h2&gt;

&lt;p&gt;This is the decision with the longest tail. Get it wrong and the plugin works fine at launch, gets slower every month as the data grows, and by the time anybody notices the fix is a migration rather than an edit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Options and transients
&lt;/h3&gt;

&lt;p&gt;Options are for site wide settings: a handful of keys, small values, read on most requests. The trap is autoloading, because every autoloaded option is fetched on every single request, including admin-ajax and REST calls, whether or not anything uses it.&lt;/p&gt;

&lt;p&gt;WordPress 6.6 changed the mechanics, as set out in the &lt;a href="https://make.wordpress.org/core/2024/06/18/options-api-disabling-autoload-for-large-options/" rel="noopener noreferrer"&gt;Make WordPress Core post on disabling autoload for large options&lt;/a&gt;. The stored value is now &lt;code&gt;on&lt;/code&gt;, &lt;code&gt;off&lt;/code&gt; or &lt;code&gt;auto&lt;/code&gt;, and an option larger than 150,000 bytes is not autoloaded by default, with the threshold adjustable through the &lt;code&gt;wp_max_autoloaded_option_size&lt;/code&gt; filter. Treat that as a ceiling, not a target. Transients are options with an expiry, and are the right home for anything fetched from elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Post meta is not a key value store
&lt;/h3&gt;

&lt;p&gt;Post meta is for attributes of one post: a subtitle, a price, a supplier reference. It is not a general purpose key value store, and the reason is visible in the table definition. The &lt;code&gt;wp_postmeta&lt;/code&gt; table has four columns and three keys. Only &lt;code&gt;post_id&lt;/code&gt; and the first 191 characters of &lt;code&gt;meta_key&lt;/code&gt; are indexed. The &lt;code&gt;meta_value&lt;/code&gt; column is a &lt;code&gt;longtext&lt;/code&gt; with no index on it at all.&lt;/p&gt;

&lt;p&gt;A query filtering on a meta value therefore cannot use an index. Every clause in a meta query adds another join, and on a site with 50,000 posts carrying 20 meta rows each the table holds a million rows. Three clauses mean three joins against a million rows on every page load. It is one of the commonest reasons a site that was quick in year one is unusable in year three, and it turns up constantly in &lt;a href="https://mecanik.dev/en/posts/woocommerce-performance-why-store-is-slow/" rel="noopener noreferrer"&gt;WooCommerce performance work&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom post types and taxonomies
&lt;/h3&gt;

&lt;p&gt;A custom post type is right when the thing is content. It needs its own list screen, permalinks, revisions and an editorial workflow, and it makes sense as a page somebody could visit. A custom taxonomy is right when you need a shared vocabulary that groups those things and deserves archive pages of its own.&lt;/p&gt;

&lt;p&gt;Both bring free machinery: admin screens, capabilities, search, the block editor and the REST API. Set &lt;code&gt;show_in_rest&lt;/code&gt; to true or the block editor will not handle the type, and register both on the &lt;code&gt;init&lt;/code&gt; hook, never before it.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you actually need your own table
&lt;/h3&gt;

&lt;p&gt;Your own table is right when the data is not content: high volume append only records such as event logs, import queues, price history or audit trails, or anything you will filter and sort by a field that is not a post column. Past a few hundred thousand rows queried by their own fields, a table with the right indexes outperforms post meta by orders of magnitude and stays predictable as it grows.&lt;/p&gt;

&lt;p&gt;The cost is that you own everything: table creation and versioned migrations, cleanup in &lt;code&gt;uninstall.php&lt;/code&gt;, your own admin screens, REST endpoints and caching. That is why the honest answer for most plugins is still a custom post type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Is Four Habits, and Three of Them Get Skipped
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://developer.wordpress.org/apis/security/" rel="noopener noreferrer"&gt;WordPress security handbook&lt;/a&gt; states the principle plainly: do not trust user input, third party APIs, or data already sitting in your database. Four habits carry almost all of the risk, and in the plugins we audit they are skipped in a consistent order. Capability checks first, nonces second, output escaping third. Prepared statements come last, because a missing one gets caught in review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capability checks
&lt;/h3&gt;

&lt;p&gt;Every handler that changes something must ask whether this user is allowed to, which means &lt;code&gt;current_user_can()&lt;/code&gt; with the specific capability, checked inside the handler rather than only around the button that calls it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;is_admin()&lt;/code&gt; is not a permission check. It reports which side of the site the request is on, and returns true for any logged in subscriber hitting an admin-ajax endpoint. An &lt;code&gt;admin_post_&lt;/code&gt; or &lt;code&gt;wp_ajax_&lt;/code&gt; action with no capability check is reachable by every registered user, which on a shop means every customer who ever ordered. Our &lt;a href="https://mecanik.dev/en/posts/wordpress-security-hardening-checklist-2026/" rel="noopener noreferrer"&gt;WordPress security hardening checklist&lt;/a&gt; covers the site level controls around this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nonces
&lt;/h3&gt;

&lt;p&gt;A nonce protects a form or a URL against a request the user did not intend to make. Use &lt;code&gt;wp_nonce_field()&lt;/code&gt; in the form and &lt;code&gt;check_admin_referer()&lt;/code&gt; in the handler, or &lt;code&gt;check_ajax_referer()&lt;/code&gt; for AJAX. Despite the name they are not single use: they are hashes valid for a window, one day by default under a two tick scheme that puts the real lifetime between twelve and twenty four hours.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.wordpress.org/apis/security/nonces/" rel="noopener noreferrer"&gt;nonces documentation&lt;/a&gt; is explicit that they must never be relied on for authentication, authorisation or access control. A nonce establishes that the request came from your form. It says nothing about whether that person should be allowed to do the thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sanitise on the way in, escape on the way out
&lt;/h3&gt;

&lt;p&gt;Validate where you can, because validation is specific: a postcode either matches the pattern or it does not. Sanitise where you cannot, with &lt;code&gt;sanitize_text_field()&lt;/code&gt;, &lt;code&gt;sanitize_email()&lt;/code&gt;, &lt;code&gt;sanitize_key()&lt;/code&gt;, &lt;code&gt;absint()&lt;/code&gt; or &lt;code&gt;wp_kses_post()&lt;/code&gt; depending on the field.&lt;/p&gt;

&lt;p&gt;Then escape at the point of output, every time, with &lt;code&gt;esc_html()&lt;/code&gt;, &lt;code&gt;esc_attr()&lt;/code&gt;, &lt;code&gt;esc_url()&lt;/code&gt; or &lt;code&gt;wp_kses_post()&lt;/code&gt;. The &lt;a href="https://developer.wordpress.org/apis/security/escaping/" rel="noopener noreferrer"&gt;escaping documentation&lt;/a&gt; asks for this as late as possible, so a reviewer sees escaping and output on the same line. Escaping gets skipped more than anything else because nothing looks wrong when you skip it. The page renders perfectly until somebody puts a script tag in a field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prepared statements
&lt;/h3&gt;

&lt;p&gt;Any query you write yourself goes through &lt;a href="https://developer.wordpress.org/reference/classes/wpdb/prepare/" rel="noopener noreferrer"&gt;&lt;code&gt;$wpdb-&amp;gt;prepare()&lt;/code&gt;&lt;/a&gt;, which takes &lt;code&gt;%d&lt;/code&gt; for integers, &lt;code&gt;%f&lt;/code&gt; for floats, &lt;code&gt;%s&lt;/code&gt; for strings and &lt;code&gt;%i&lt;/code&gt; for identifiers such as table and column names. Placeholders are left unquoted, a literal percent sign is written twice, and a &lt;code&gt;LIKE&lt;/code&gt; wildcard is passed inside the substitution argument rather than typed into the query. Concatenating a variable into SQL is not a style disagreement, it is the vulnerability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The REST API and the Block Editor
&lt;/h2&gt;

&lt;p&gt;A plugin written this year should expose its data through the REST API and its settings through the editor, not through a hand rolled options page.&lt;/p&gt;

&lt;p&gt;Routes are registered with &lt;code&gt;register_rest_route()&lt;/code&gt; on the &lt;code&gt;rest_api_init&lt;/code&gt; hook. Since WordPress 5.5 a &lt;code&gt;permission_callback&lt;/code&gt; argument is required, and omitting it triggers a &lt;code&gt;_doing_it_wrong()&lt;/code&gt; notice naming the route. A genuinely public endpoint uses &lt;code&gt;__return_true&lt;/code&gt;, which is the point of the design: making a route public becomes a deliberate line of code instead of an omission. The &lt;a href="https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/" rel="noopener noreferrer"&gt;custom endpoints documentation&lt;/a&gt; also covers the argument schema, where sanitisation and validation callbacks belong so bad input never reaches your handler.&lt;/p&gt;

&lt;p&gt;Settings are registered with &lt;code&gt;register_setting()&lt;/code&gt; and &lt;code&gt;show_in_rest&lt;/code&gt; set to true. That puts them on the core settings endpoint, so the block editor or an external script can read and write them through an interface that already handles authentication, permissions and validation. It removes an options page, its nonce, its form handler and the bugs living in them.&lt;/p&gt;

&lt;p&gt;Blocks are registered from a &lt;code&gt;block.json&lt;/code&gt; file, the recommended canonical method since WordPress 5.8. &lt;a href="https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/" rel="noopener noreferrer"&gt;The block metadata documentation&lt;/a&gt; sets out the benefit: assets declared there load only on pages where the block appears, instead of site wide because a plugin is active.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Discipline Inside a Plugin
&lt;/h2&gt;

&lt;p&gt;Four things account for most of the plugin caused slowness we find in audits, and all four are cheap to avoid and expensive to retrofit. The first is autoloaded options, because they cost something on every request forever.&lt;/p&gt;

&lt;p&gt;The second is uncached remote requests during a page load. A &lt;code&gt;wp_remote_get()&lt;/code&gt; to a supplier's API with no caching means every visitor waits for that supplier. When the supplier is slow your site is slow, and when it is down your site hangs until the timeout expires. Cache the response in a transient, set an explicit timeout, and decide in advance what the page renders when the call fails.&lt;/p&gt;

&lt;p&gt;The third is queries inside a loop. Calling &lt;code&gt;get_post_meta()&lt;/code&gt; for each of 200 rows is 200 round trips unless the meta cache has been primed, and &lt;code&gt;WP_Query&lt;/code&gt; primes it for you if you let it. The fix is usually to stop disabling something, which is true of most findings in a &lt;a href="https://mecanik.dev/en/posts/wordpress-performance-audit-core-web-vitals/" rel="noopener noreferrer"&gt;Core Web Vitals audit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The fourth is work done inside somebody's page request. WP-Cron is not system cron: it is triggered on page load, so a scheduled job runs inside a visitor's request, and on a quiet site the two o'clock job does not run until somebody visits at five. Define &lt;code&gt;DISABLE_WP_CRON&lt;/code&gt;, drive &lt;code&gt;wp-cron.php&lt;/code&gt; from a real system scheduler, and keep jobs short and idempotent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Surviving Core Updates: The Part Nobody Budgets For
&lt;/h2&gt;

&lt;p&gt;Core rarely deletes anything outright. Functions get deprecated, keep working and emit a notice, which is why running staging with &lt;code&gt;WP_DEBUG&lt;/code&gt; enabled is the cheapest early warning system available. A deprecation notice is a dated invitation to fix something while it is still cheap.&lt;/p&gt;

&lt;p&gt;The process that prevents surprises costs about an hour a quarter. Follow the core development blog so you know when a beta and then a release candidate exist. Read the Field Guide, published during the release candidate phase, which lists that release's developer facing features and breaking changes. Then put the release candidate on a staging copy and run a written smoke test of the plugin's real functions.&lt;/p&gt;

&lt;p&gt;Version support matters as much as the code. &lt;a href="https://wordpress.org/about/requirements/" rel="noopener noreferrer"&gt;WordPress requires PHP&lt;/a&gt; 7.4 as an absolute floor and recommends 8.3 or newer, alongside MariaDB 10.11 or MySQL 8.0. Declare &lt;code&gt;Requires PHP&lt;/code&gt; and &lt;code&gt;Requires at least&lt;/code&gt; in the plugin header honestly, then test on the lowest version you declared rather than on whatever the developer's laptop runs.&lt;/p&gt;

&lt;p&gt;Version your own plugin semantically and mean it. A patch fixes something, a minor adds behaviour without breaking anything, and a major may break provided it says what it broke. Clients on automatic updates rely on that promise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distribution, Licensing and How Updates Reach the Site
&lt;/h2&gt;

&lt;p&gt;WordPress is released under the GPL version 2 or later, and &lt;a href="https://wordpress.org/about/license/" rel="noopener noreferrer"&gt;the wordpress.org licence page&lt;/a&gt; sets out the project's position that plugins and themes are derivative works which inherit the licence, while acknowledging a legal grey area over what counts as derivative.&lt;/p&gt;

&lt;p&gt;You get the source, always, and you can hire anybody else to modify it. What the GPL does not do is force you to publish it, so a plugin built for one business can stay private. It also does not stop the developer selling the same work to somebody else. If exclusivity matters, that is a contract term rather than a licence term.&lt;/p&gt;

&lt;p&gt;If the plugin goes into the public directory it has to satisfy &lt;a href="https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/" rel="noopener noreferrer"&gt;the plugin directory guidelines&lt;/a&gt;, eighteen of them. The first requires GPL compatible licensing for everything in the package, images included. Others rule out trialware, meaning functionality locked behind a payment or upgrade, ban obfuscated code, prohibit tracking users without consent, and forbid links or credits added to the public site without permission.&lt;/p&gt;

&lt;p&gt;If it stays private, updates become your problem. Set the &lt;code&gt;Update URI&lt;/code&gt; header, which exists to stop a private plugin being overwritten by a similarly named one from the directory, and serve updates from your own endpoint. Leaving this to the end is how a client ends up updating by FTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Custom WordPress Plugin Costs
&lt;/h2&gt;

&lt;p&gt;The bands below are UK agency prices in GBP, for work delivered to the standard described here: with tests, documentation and a named person responsible after launch. A capable WordPress and PHP developer bills roughly £400 to £600 a day, so these are statements about scope rather than rate.&lt;/p&gt;

&lt;p&gt;A small utility plugin runs £1,500 to £3,000. One job, a few hooks, perhaps a settings toggle: redirect handling, an extra field on an order, a nightly export to a supplier.&lt;/p&gt;

&lt;p&gt;A mid sized integration runs £3,000 to £15,000. A third party API with authentication, retries and error handling, a custom post type, admin screens and background processing. This is the most commonly commissioned size and the most frequently underestimated, because the integration is a week and the failure handling is a fortnight.&lt;/p&gt;

&lt;p&gt;A substantial product plugin runs £20,000 to £75,000 and upwards. Its own tables, block editor interfaces, licensing and update infrastructure, multisite support, and a support burden that begins the day it ships.&lt;/p&gt;

&lt;p&gt;Paying at the low end is not automatically wrong. It is wrong when the price came from a scope that quietly excluded the deliverables below. Our guide to &lt;a href="https://mecanik.dev/en/posts/hire-wordpress-developer-rates-what-to-ask/" rel="noopener noreferrer"&gt;WordPress developer rates and what to ask&lt;/a&gt; covers how to read a quote, and our &lt;a href="https://mecanik.dev/en/wordpress-developer-for-hire/" rel="noopener noreferrer"&gt;WordPress development&lt;/a&gt; page sets out how we scope this work.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should be in the deliverable
&lt;/h3&gt;

&lt;p&gt;Ask for all of these in writing before work starts, because every one is cheap to include and expensive to add afterwards. The source, in a repository you own, with its history intact, rather than a zip file emailed on the last day. Unit tests for the business rules and an integration test for anything that writes to the database or calls an external service, which is what makes it safe to change in two years by somebody who was not there.&lt;/p&gt;

&lt;p&gt;A readme saying what the plugin does, what it hooks, what it stores and where, what external services it calls and what happens when each of those fails. Two pages is plenty, and its absence is why plugins get replaced rather than maintained. An &lt;code&gt;uninstall.php&lt;/code&gt; that removes options, tables, cron events and meta. And a named support arrangement covering testing against each core release and fixing what that testing finds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting It Built
&lt;/h2&gt;

&lt;p&gt;Mecanik builds plugins at all three sizes above, and takes over plugins written by somebody else, often the more useful engagement. Our &lt;a href="https://mecanik.dev/en/wordpress-developer-for-hire/" rel="noopener noreferrer"&gt;WordPress developer&lt;/a&gt; and &lt;a href="https://mecanik.dev/en/services/software-development/" rel="noopener noreferrer"&gt;software development&lt;/a&gt; pages explain how we scope and deliver. If you already have a plugin nobody wants to touch, an audit against the practices above takes about a day and tells you whether it is repairable or replaceable.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/wordpress-7-ai-client/" rel="noopener noreferrer"&gt;WordPress 7.0 and the AI Client in Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/custom-software-development-uk-the-complete-buyers-guide/" rel="noopener noreferrer"&gt;Custom Software Development UK - The Complete Buyer's Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/salesforce-integration/" rel="noopener noreferrer"&gt;Salesforce Integration: Limits, Rails and Real Cost&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/technical-due-diligence/" rel="noopener noreferrer"&gt;Technical Due Diligence: What Buyers Actually Look For&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Should custom functionality go in a plugin or in the theme?&lt;/strong&gt;&lt;br&gt;
In a plugin, unless it is purely presentational. A theme gets swapped at the next redesign and everything it was doing stops: custom post types lose their admin screens, shortcodes render as raw text, and integrations silently stop running. Anything that must still be true after a redesign belongs in a plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does a custom WordPress plugin cost in the UK?&lt;/strong&gt;&lt;br&gt;
A small utility plugin typically costs £1,500 to £3,000, a mid sized integration with a third party API and admin screens costs £3,000 to £15,000, and a substantial product plugin with its own tables and update infrastructure costs £20,000 to £75,000 or more. Developers capable of this work bill roughly £400 to £600 a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it ever acceptable to edit WordPress core or another plugin's files?&lt;/strong&gt;&lt;br&gt;
No. Those edits are erased by the next update, with no error and usually no discovery until something stops working. Use actions and filters instead. If the hook you need does not exist, wrap the behaviour, fork the plugin under version control, or ask upstream for the hook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I own a plugin I paid somebody to build?&lt;/strong&gt;&lt;br&gt;
You own your copy and whatever the contract says. The GPL gives you the source, the right to modify it and the right to hire anyone else to maintain it, and it does not require you to publish it, so a plugin built for one business can stay private. It does not stop the developer reselling the same work, so put exclusivity in the contract if it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you stop a plugin breaking when WordPress updates?&lt;/strong&gt;&lt;br&gt;
Test it against every release candidate on a staging copy before that version ships, run staging with WP_DEBUG enabled so deprecation notices surface early, and declare the PHP and WordPress versions the plugin supports in its header. WordPress requires PHP 7.4 as a floor and recommends 8.3 or newer.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Software Supply Chain Security for Small Teams</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Sat, 31 Oct 2026 19:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/software-supply-chain-security-for-small-teams-3e65</link>
      <guid>https://dev.to/mecanik-dev/software-supply-chain-security-for-small-teams-3e65</guid>
      <description>&lt;p&gt;Supply chain security sounds like a problem for organisations with a security function, and the framing is misleading. A small team running a handful of services typically depends on hundreds of packages it has never read, pulled at build time from registries it does not control, executing installation scripts on machines that hold its deployment credentials.&lt;/p&gt;

&lt;p&gt;The exposure is not proportional to company size. It is proportional to dependency count and build automation, and small teams often have more of the first and less scrutiny of the second than the organisations that worry about this publicly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The uncomfortable arithmetic:&lt;/strong&gt; your application has perhaps a dozen direct dependencies and several hundred transitive ones. You chose the dozen. You did not choose the rest, you have not read any of them, and any one of them running an install script has the same access as your build process. That is the actual attack surface, and it is not visible from the dependency list you wrote.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Supply Chain Security Starts at Install Time
&lt;/h2&gt;

&lt;p&gt;The dangerous moment is usually not running the code. It is installing it.&lt;/p&gt;

&lt;p&gt;Package managers permit scripts to execute during installation, which means a compromised or malicious package runs with the privileges of whoever ran the install. On a developer machine that is their credentials. In a build pipeline it is the deployment keys, which is worse.&lt;/p&gt;

&lt;p&gt;Three attack shapes account for most real incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typosquatting.&lt;/strong&gt; A package with a name one character from a popular one, published and waiting for a mistyped install command. Cheap to execute and effective at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account compromise of a legitimate package.&lt;/strong&gt; A widely used package whose maintainer's credentials were stolen, updated with something harmful. This is the hardest to defend against because nothing about the name or the download count looks wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency confusion.&lt;/strong&gt; A public package published under the name of one of your internal ones, where a misconfigured resolver prefers the public registry. Entirely a configuration problem and entirely preventable.&lt;/p&gt;

&lt;p&gt;None of these require anyone to target you specifically, which is the point. They are opportunistic and they scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controls That Repay the Effort at Small Scale
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Commit the lockfile and install from it.&lt;/strong&gt; A lockfile pins exact versions and their hashes. Installing from the manifest instead resolves fresh versions at build time, which means your build is not reproducible and an upstream change reaches production without review. Use the install command that fails when the lockfile disagrees rather than the one that updates it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disable install scripts where you can.&lt;/strong&gt; Many ecosystems support this, and most packages do not need them. It is the single largest reduction in exposure available, and the packages that break are worth knowing about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automate vulnerability alerts, then triage.&lt;/strong&gt; Scanners produce more findings than a small team can act on, and the failure mode is not missing alerts, it is ignoring all of them because most are irrelevant. Filter to what is reachable from your code and act on that. A queue nobody reads is worse than no queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pin your build tooling too.&lt;/strong&gt; The container image, the runtime version and the action or plugin your pipeline calls are all dependencies, and referencing a mutable tag means your build changes without you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate build credentials from everything else.&lt;/strong&gt; A pipeline that can deploy should not hold credentials that can do anything more, and a compromised build should not be able to reach production data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an SBOM Does and Does Not Tell You
&lt;/h2&gt;

&lt;p&gt;A software bill of materials lists what is in your application, and its value is answering one question fast: are we affected by this?&lt;/p&gt;

&lt;p&gt;That question used to take days. When a widely used component turns out to be vulnerable, an organisation with a current inventory answers in minutes and one without it spends a week searching. That difference is the whole argument, and it is why &lt;a href="https://www.cisa.gov/sbom" rel="noopener noreferrer"&gt;SBOM adoption is being pushed&lt;/a&gt; as baseline practice rather than as an advanced control.&lt;/p&gt;

&lt;p&gt;What it does not do is make you secure. An inventory is a list, not a defence. It does not tell you whether a listed vulnerability is reachable in your code, whether it matters in your configuration, or whether anything has been compromised. Teams that generate one and file it have added a document rather than a control.&lt;/p&gt;

&lt;p&gt;Generate it from the build, so it describes what was actually shipped rather than what the manifest claimed. Keep the one matching each release. And accept that its value is entirely in the speed of answering a question you will be asked under pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Small Teams Actually Get Hurt
&lt;/h2&gt;

&lt;p&gt;Not usually by an exotic package compromise. By ordinary things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets in the repository&lt;/strong&gt;, which remain in history after deletion and are found by automated scanners within minutes of a push. &lt;strong&gt;A dependency three years out of date&lt;/strong&gt; with a published exploit and no upgrade path, because upgrading was deferred until it became a project. &lt;strong&gt;Build pipelines with excessive permissions&lt;/strong&gt;, where a pull request from a fork can run with access it should not have. &lt;strong&gt;Nobody watching&lt;/strong&gt;, so a compromise persists because no alert reaches a person.&lt;/p&gt;

&lt;p&gt;The unglamorous conclusion is that keeping dependencies current is most of supply chain security in practice. The &lt;a href="https://mecanik.dev/en/posts/software-maintenance-cost/" rel="noopener noreferrer"&gt;software maintenance cost&lt;/a&gt; that teams treat as optional is the same work, and deferring it converts a routine upgrade into an emergency with an exploit attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Proportionate Starting Point
&lt;/h2&gt;

&lt;p&gt;For a small team, in order: get secrets out of the repository and rotate anything exposed. Commit lockfiles and install from them strictly. Turn on dependency alerts and triage weekly rather than continuously. Pin build tooling to immutable references. Reduce pipeline permissions to the minimum. Generate an SBOM per release once the rest is in place.&lt;/p&gt;

&lt;p&gt;That sequence covers the realistic threats without a security function, and each step is hours rather than weeks. The controls beyond it, provenance attestation, reproducible builds, signed artefacts, are genuinely valuable and belong to a stage where the basics are already stable.&lt;/p&gt;

&lt;p&gt;Mecanik reviews and hardens build pipelines as part of our &lt;a href="https://mecanik.dev/en/services/application-security/" rel="noopener noreferrer"&gt;application security&lt;/a&gt; work. The finding is almost never a sophisticated compromise. It is a token in a repository and a dependency nobody has updated since it was chosen.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/password-storage/" rel="noopener noreferrer"&gt;Password Storage: What to Use in 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/wordpress-security-hardening-checklist-2026/" rel="noopener noreferrer"&gt;WordPress Security Hardening Checklist for 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/uptime-sla/" rel="noopener noreferrer"&gt;Uptime SLAs That Mean Something&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/api-security-protect-public-api/" rel="noopener noreferrer"&gt;API Security: How to Protect a Public API in 2026&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Why is installing packages the risky moment?&lt;/strong&gt;&lt;br&gt;
Because package managers allow scripts to run during installation, with the privileges of whoever ran the install. On a developer machine that means their credentials; in a build pipeline it means deployment keys. The code does not have to be executed by your application to do damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is dependency confusion?&lt;/strong&gt;&lt;br&gt;
An attack where a public package is published under the name of one of your internal packages, and a misconfigured resolver prefers the public registry over your private one. It requires nobody to target you specifically and is entirely preventable through resolver configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does an SBOM actually give you?&lt;/strong&gt;&lt;br&gt;
A fast answer to one question: are we affected by this? With a current inventory that takes minutes; without one it takes a week of searching. It does not tell you whether a listed vulnerability is reachable in your code or whether anything has been compromised, so an SBOM that is generated and filed is a document rather than a control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I commit my lockfile?&lt;/strong&gt;&lt;br&gt;
Yes, and install from it strictly using the command that fails when the lockfile disagrees rather than the one that updates it. Installing from the manifest resolves fresh versions at build time, which makes builds unreproducible and lets an upstream change reach production without any review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where should a small team start?&lt;/strong&gt;&lt;br&gt;
Remove secrets from the repository and rotate anything exposed, commit lockfiles and install strictly, enable dependency alerts and triage them weekly, pin build tooling to immutable references, reduce pipeline permissions, then generate an SBOM per release. Each step is hours rather than weeks.&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
      <category>devops</category>
      <category>programming</category>
    </item>
    <item>
      <title>Drupal AI: What the AI Modules Actually Do</title>
      <dc:creator>Norbert E. Boros</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/mecanik-dev/drupal-ai-what-the-ai-modules-actually-do-28mj</link>
      <guid>https://dev.to/mecanik-dev/drupal-ai-what-the-ai-modules-actually-do-28mj</guid>
      <description>&lt;p&gt;Drupal AI has stopped being a scatter of provider-specific contributed modules that whoever needed them bolted on separately. It is now one coordinated project with an abstraction layer underneath it, a security-team-covered release cycle, and a packaged product in Drupal CMS that offers to switch parts of it on during installation. The question worth asking has moved on from whether Drupal can do AI to which parts of it are worth turning on, and what each one costs once four editors use it every working day.&lt;/p&gt;

&lt;p&gt;Most write-ups stop at the demo. Somebody types a prompt, a page appears, a chatbot creates a content type while you watch. The demo is real. What it does not tell you is what leaves your infrastructure when an editor clicks that button, which of the modules on the list have never shipped a stable release, and which budget line actually grows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What does Drupal AI give a business?&lt;/strong&gt; A provider abstraction in the AI module that routes chat, embeddings, translation and image calls to any configured vendor or to a model on your own hardware, plus submodules for alt text, translation, editorial assistance, semantic search and site-building agents. The narrow features are production ready and cost very little to run. Semantic search and agents are still alpha or immature and need real engineering built around them.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Drupal AI Actually Is in 2026
&lt;/h2&gt;

&lt;p&gt;The spine of it is a single contributed project. The &lt;a href="https://www.drupal.org/project/ai" rel="noopener noreferrer"&gt;AI (Artificial Intelligence) module&lt;/a&gt; reports 17,818 sites in use, works with Drupal 10.5 and above or 11.2 and above, and carries stable releases at 1.4.7 and 1.3.12 with a 1.5.0 release candidate published on 27 August 2026. Stable branches are covered by the Drupal Security Team, which matters more than the feature list when you are deciding whether to run something on a client site. A 2.0.x branch is in active development.&lt;/p&gt;

&lt;p&gt;For context on the platform underneath it, the current recommended &lt;a href="https://www.drupal.org/project/drupal" rel="noopener noreferrer"&gt;Drupal core&lt;/a&gt; release is 11.4.5, published on 6 August 2026, with 10.6.15 kept available for sites that have not upgraded yet. If you are still deciding whether Drupal is the right platform at all, our &lt;a href="https://mecanik.dev/en/posts/drupal-web-development-a-2026-guide/" rel="noopener noreferrer"&gt;2026 guide to Drupal web development&lt;/a&gt; covers that question separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  The provider abstraction is the part that matters
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://project.pages.drupalcode.org/ai/2.0.x/developers/developer_information/" rel="noopener noreferrer"&gt;developer documentation&lt;/a&gt; states the design plainly: AI providers are services Drupal can call, and those calls have been abstracted into operation types. The named types include chat in basic and streaming forms, text to image, text to speech, speech to text, embeddings, moderation, audio to audio, speech to speech, image classification, translate text and image to image.&lt;/p&gt;

&lt;p&gt;The consequence is commercial rather than technical. A module written against the chat operation works with whichever provider is configured, so changing vendor is a configuration change rather than a rewrite. Drupal's own &lt;a href="https://new.drupal.org/ai/about-drupal-ai/providers" rel="noopener noreferrer"&gt;providers page&lt;/a&gt; lists 48 integrations, including OpenAI, Anthropic, Google Gemini and Vertex, AWS Bedrock, Azure, Mistral, Groq, Hugging Face, DeepL, Deepgram, ElevenLabs, LiteLLM, LM Studio, Ollama and amazee.ai.&lt;/p&gt;

&lt;p&gt;That portability has a limit worth stating early. The interface is portable; the prompts are not. A prompt tuned against one model frequently produces worse output against another, so a provider switch is cheap in code and costs a round of prompt testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What ships in the box
&lt;/h3&gt;

&lt;p&gt;The AI project bundles a core layer plus submodules for automators, an API explorer used for debugging, observability and logging, a chatbot, CKEditor integration, and an assistants and agents API. Several capabilities that started inside it have since moved out to their own contributed projects, which is why the ecosystem looks scattered when you first search for it. AI Search, AI Content Suggestions, AI Translate, AI Validations, AI ECA and Field Widget Actions now have separate release cycles and very different maturity levels.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Drupal CMS Packages It
&lt;/h2&gt;

&lt;p&gt;Drupal CMS is a separate product rather than a version of core, aimed at marketers and content teams rather than developers. &lt;a href="https://www.drupal.org/blog/drupal-cms-20-is-here-visual-building-ai-and-site-templates-transform-drupal" rel="noopener noreferrer"&gt;Drupal CMS 2.0 shipped on 28 January 2026&lt;/a&gt;, built on Drupal core 11.3, and the release line has kept moving since: 2.1.4 was published on 1 September 2026.&lt;/p&gt;

&lt;p&gt;The AI capability it packages is deliberately narrow. It generates complete pages from text prompts using the available Canvas components, runs an admin chatbot for site-building tasks such as creating content types, defining taxonomy terms and adding fields, generates alt text with human review in the loop, and provides a dashboard showing which features are on and which providers are configured. The setup flow offers amazee.ai, OpenAI and Anthropic.&lt;/p&gt;

&lt;p&gt;The word in the release announcement that should shape your risk assessment is "optional". Nothing in a Drupal CMS install calls a model until somebody configures a provider and supplies credentials, so a fresh install is not by itself a data protection question. It becomes one the moment an administrator pastes an API key into the provider form, which is usually done by whoever is fastest rather than whoever is accountable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Applications That Earn Their Keep
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Alt text and media metadata
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.drupal.org/project/ai_image_alt_text" rel="noopener noreferrer"&gt;AI Image Alt Text&lt;/a&gt; module reports 10,467 sites and released 1.0.2 on 5 December 2025 for Drupal 10.2 and 11. It puts a Generate button on every image field widget, supports multilingual content and custom prompts, and ships a bulk submodule for regenerating alt text across images already on the site.&lt;/p&gt;

&lt;p&gt;This is the clearest win in the whole set, because it targets work that never gets done otherwise. A library of six thousand images represents weeks of manual writing that no organisation funds, so the alt attributes stay empty and the accessibility audit keeps failing on the same line every year. Generation plus review turns that into a few days, provided the review actually happens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translation across a multilingual site
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.drupal.org/project/ai_translate" rel="noopener noreferrer"&gt;AI Translate&lt;/a&gt; reached stable 1.4.0 on 27 August 2026, reports 886 sites and works with Drupal 10.4 and above or 11. It adds one-click translation to Drupal's own content translation system, working from the Translate tab, handling multiple field types and referenced entities.&lt;/p&gt;

&lt;p&gt;The important design point is that it fills Drupal's existing translation workflow rather than replacing it. Revisions, moderation states and permissions stay exactly where they were, and a human can edit the result through the normal interface. That is the difference between a translation feature you can put in front of a regulated client and a bolt-on that bypasses your editorial controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Editorial assistance and taxonomy
&lt;/h3&gt;

&lt;p&gt;The AI project ships CKEditor integration, and the AI Content Suggestions module moved out to contrib to cover summarisation, readability scoring, moderation, tone adjustment, title generation and taxonomy tag suggestion inside the authoring experience. This is the busiest and least settled corner of the ecosystem, and the modules here change shape between releases more than the rest.&lt;/p&gt;

&lt;p&gt;Before planning around any of them, read the reported install count on the project page. Alt text sits above ten thousand sites; several of the editorial modules sit in the hundreds. That is not a judgement on the code, it is a measure of how much production shaking-out it has had, and it should decide how much testing time you budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semantic Search Is the Feature Most Likely to Disappoint
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.drupal.org/project/ai_search" rel="noopener noreferrer"&gt;AI Search&lt;/a&gt; is the module people ask for by name and the one least ready to be switched on. As of 2 September 2026 it has no supported stable release: the 1.x branch sits at 1.3.0-alpha4 from 12 July 2026 and the 2.x branch at 2.0.0-alpha2 from 24 June 2026, with 703 reported installs.&lt;/p&gt;

&lt;p&gt;It also brings infrastructure with it. It runs as a backend for the Search API module and requires a vector database, with support for Milvus, Pinecone, Qdrant, PostgreSQL, MariaDB, OpenSearch, Azure and SQLite. The honest description is alpha software plus a datastore your team now operates, monitors and backs up. That is a project, not a checkbox.&lt;/p&gt;

&lt;p&gt;The deeper problem is upstream of the module. Semantic retrieval is only as good as the chunks you feed it, and a content set written as long pages covering six loosely related topics embeds into vectors that mean nothing in particular. Sites that get good results from vector search almost always restructured their content first, and that restructuring is the actual work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent-Style Site Building
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.drupal.org/project/ai_agents" rel="noopener noreferrer"&gt;AI Agents&lt;/a&gt; module is a framework for text-to-action agents, at stable 1.3.4 since 13 August 2026, with 9,677 reported installs and support for Drupal 10.3 and 11 or higher. It ships three agents out of the box, covering fields, content types and taxonomy, and integrates with the chatbot and the AI Assistants API.&lt;/p&gt;

&lt;p&gt;Used well, this is genuinely useful for the tedious middle of a build. Creating thirty fields across six content types by hand is an afternoon of clicking that produces no design value, and an agent does it in minutes from a description. What it does not do is decide what those content types should be, which is the part that determines whether the site still works in two years.&lt;/p&gt;

&lt;p&gt;One operational rule separates a time saver from an incident. Run configuration agents against a development environment with configuration export enabled, never against production, so every change arrives as a reviewable diff in version control. Our guide to &lt;a href="https://mecanik.dev/en/posts/ai-agents-for-business-cost-failure-modes/" rel="noopener noreferrer"&gt;AI agents in business, their costs and failure modes&lt;/a&gt; covers why unreviewed agent actions are the most expensive category of AI failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosted API or a Model You Run Yourself
&lt;/h2&gt;

&lt;p&gt;The abstraction gives you three shapes, and the choice moves cost, latency and data handling in different directions.&lt;/p&gt;

&lt;p&gt;A hosted vendor API is the default. It is cheapest to start, needs no infrastructure and gives access to the strongest models. It also means content leaves your network on every call, and your running cost is a variable bill you do not control.&lt;/p&gt;

&lt;p&gt;A private or managed endpoint from a provider that contracts on data residency sits in the middle. You still pay per token, but you can point at a region and get contractual terms about processing. It is what most UK and EU clients end up choosing.&lt;/p&gt;

&lt;p&gt;A model on your own hardware, served through Ollama or LM Studio and configured as a provider, is the only version where content never leaves your infrastructure. The cost shape inverts: instead of a per-token bill with no floor, you pay a fixed monthly cost for the machine whether you make ten calls or ten million.&lt;/p&gt;

&lt;p&gt;Latency matters less than people expect for editorial work, because an editor waiting a few seconds for generated alt text does not care where the model runs. It matters when a model sits in the request path of a public search or chatbot, which is exactly the case where self-hosting is hardest to size.&lt;/p&gt;

&lt;p&gt;Quality is the honest trade. Open-weight models have closed most of the gap on constrained tasks such as alt text and translation, where the instruction is simple and the output is short. They remain behind on long-context summarisation and on the multi-step instruction following that agentic configuration changes require.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Drupal AI Costs to Run
&lt;/h2&gt;

&lt;p&gt;Token pricing is published per million tokens and quoted in US dollars. OpenAI's &lt;a href="https://platform.openai.com/docs/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; currently lists gpt-5.6-luna at $0.20 input and $1.20 output, gpt-5.6-terra at $2.00 and $12.00, and gpt-5.6-sol at $4.00 and $20.00 on short context. Cached input is roughly ten times cheaper than fresh input on each tier, which matters a great deal for retrieval workloads that resend the same context.&lt;/p&gt;

&lt;h3&gt;
  
  
  A worked estimate for editorial features
&lt;/h3&gt;

&lt;p&gt;These are estimates, and they are built from assumptions you should replace with your own measurements. Take a 900-word page. Source text plus a system prompt lands near 1,500 input tokens, and a translation of it near 1,600 output tokens, because most target languages expand.&lt;/p&gt;

&lt;p&gt;On the mid tier, that is about $0.003 of input and $0.019 of output, so roughly $0.022 per page per language. Translating a back catalogue of 500 pages into eight languages is 4,000 calls and about $88, once. Keeping up with 40 new pages a month across the same eight languages is about $7 a month.&lt;/p&gt;

&lt;p&gt;That number usually surprises people in the wrong direction. They have budgeted for a large AI bill and the editorial features cost less than the coffee. Alt text and taxonomy suggestion are smaller still, because the outputs are a few dozen tokens each.&lt;/p&gt;

&lt;h3&gt;
  
  
  The number that does bite
&lt;/h3&gt;

&lt;p&gt;Retrieval is different, because every query resends the retrieved passages to the model as input. Ten passages of 500 tokens is 5,000 input tokens plus a question, with maybe 300 tokens of answer.&lt;/p&gt;

&lt;p&gt;On the cheapest tier that is about $0.0014 per query, so 50,000 queries a month costs about $70. On the mid tier the same traffic costs about $680. Model choice moves that bill by a factor of ten, and prompt caching moves it again.&lt;/p&gt;

&lt;p&gt;This is also the one feature whose cost tracks visitor traffic rather than editorial volume. Everything else scales with how much your team publishes, which is predictable. A public AI search or chatbot scales with how many people visit, which is not, and it is the feature that produces the alarming invoice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the estimates leave out
&lt;/h3&gt;

&lt;p&gt;Vector database hosting is a separate line, whether that is a managed service or a server your team runs. Embedding the corpus once is cheap; re-embedding on every content save is not, and nobody models it. Failed and retried calls are billed. And the review step is labour rather than tokens, which is why it is almost always the largest number on the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Protection Is Where UK and EU Projects Stall
&lt;/h2&gt;

&lt;p&gt;When an editor clicks Generate, the field content and your prompt go to the configured provider. Under a hosted API that is a transfer of data to a third party, and it happens on every call, silently, from an interface that looks like the rest of the CMS.&lt;/p&gt;

&lt;p&gt;The specific risk in a content management system is that people underestimate what it holds. Articles are the safe part. Comments, user profiles, webform submissions, editorial notes and unpublished drafts about named individuals are all personal data sitting in the same database, and an automator configured a little too broadly will happily send them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller and processor
&lt;/h3&gt;

&lt;p&gt;You do not transfer responsibility by using a vendor. The ICO's guidance on &lt;a href="https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/artificial-intelligence/guidance-on-ai-and-data-protection/what-are-the-accountability-and-governance-implications-of-ai/" rel="noopener noreferrer"&gt;accountability and governance in AI&lt;/a&gt; is explicit that overall accountability for data protection compliance lies with the controller, meaning your organisation, and that identifying controller and processor relationships is part of the exercise rather than a formality.&lt;/p&gt;

&lt;p&gt;The same guidance sets a clear expectation on impact assessments. It states that in the vast majority of cases, the use of AI will involve processing likely to result in a high risk to individuals' rights and freedoms and will therefore trigger the legal requirement to undertake a DPIA. Where you assess that a particular use is not high risk, you still have to document how you reached that conclusion. Note that the guidance is currently under review following the Data (Use and Access) Act, so recheck it before relying on the detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  The EU AI Act transparency obligations
&lt;/h3&gt;

&lt;p&gt;If you serve EU users, Article 50 of &lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202401689" rel="noopener noreferrer"&gt;Regulation (EU) 2024/1689&lt;/a&gt; applies. Article 113 sets the general application date as 2 August 2026, which has passed, so these are live obligations rather than future ones.&lt;/p&gt;

&lt;p&gt;Three parts touch a CMS directly. Article 50(1) requires that systems intended to interact directly with people are built so those people are informed they are dealing with an AI system, unless it is obvious. That is your site chatbot. Article 50(2) puts a marking obligation on providers of systems generating synthetic content, with an exemption where the system performs an assistive function for standard editing or does not substantially alter the input.&lt;/p&gt;

&lt;p&gt;Article 50(4) is the one that changes how you staff a project. Deployers who generate or manipulate text published to inform the public on matters of public interest must disclose that it was artificially generated, and the obligation does not apply where the content has undergone human review or editorial control and a natural or legal person holds editorial responsibility for it. The review step you were tempted to cut is the same step that discharges the disclosure duty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Fails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Metadata nobody reads
&lt;/h3&gt;

&lt;p&gt;Alt text generated in bulk and published unread is the most common failure, because the feature makes it so easy. A vision model describes what is in the frame, not what the image is doing on the page, so a product photograph becomes "a person holding a bottle" rather than the product name. For a screen reader user that is worse than an empty attribute, because the page now looks remediated and nobody will revisit it.&lt;/p&gt;

&lt;p&gt;The same applies to generated summaries and meta descriptions. They read fluently, which is precisely why reviewers skim them, and a fluent wrong summary survives review far longer than an awkward right one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search that is worse than the search it replaced
&lt;/h3&gt;

&lt;p&gt;Two failure modes stack. The first is structural: badly chunked content produces retrieval that returns approximately relevant passages instead of the right one. The second is a regression nobody anticipates, which is the loss of exact matching. A user typing a part number, an invoice reference or a surname wants that exact string, and pure vector search returns documents that are semantically about part numbers.&lt;/p&gt;

&lt;p&gt;The fix is hybrid retrieval, running keyword and vector search together and merging the results. That is standard practice, and with AI Search sitting at alpha it is usually something you build rather than configure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Translation that drifts from the source
&lt;/h3&gt;

&lt;p&gt;Machine translation of a page is fine. Machine translation of a page that later changes in English, with the translations left untouched, is a site that quietly contradicts itself in seven languages. Nothing in the module tracks source staleness for you, so somebody has to own re-translation as a process.&lt;/p&gt;

&lt;p&gt;Numbers deserve their own check. A translated page that renders a price in the wrong currency symbol, or converts a figure the English never converted, creates a commercial dispute rather than a typo.&lt;/p&gt;

&lt;h3&gt;
  
  
  The review step nobody budgeted
&lt;/h3&gt;

&lt;p&gt;Every feature here moves work rather than removing it. Generation takes seconds and review takes minutes, so at 500 pages the review is the project. Teams that budget only for implementation discover this in week three and quietly stop using the feature, which is how AI pilots die.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Still Have to Build
&lt;/h2&gt;

&lt;p&gt;Governance comes first, because the modules give you capability without policy. You need to decide which roles can use which feature, on which content types and fields, against which provider, and then enforce it in configuration rather than in a wiki page.&lt;/p&gt;

&lt;p&gt;Prompts belong in version-controlled configuration, reviewed and deployed like any other change. Prompts typed into a production form by whoever was on shift are untested code with direct cost implications.&lt;/p&gt;

&lt;p&gt;Spend control is not included. The observability submodule logs calls, which tells you what happened after it happened. A misconfigured automator looping over a large content set needs a hard cap at the provider, plus an alert.&lt;/p&gt;

&lt;p&gt;You also need fallbacks. A provider outage should degrade the authoring experience gracefully, not prevent an editor saving a node. This is ordinary integration engineering, and it is the part that separates a demo from a deployment. If you are also weighing up front-end architecture at the same time, our comparison of &lt;a href="https://mecanik.dev/en/posts/headless-cms-vs-traditional-cms/" rel="noopener noreferrer"&gt;headless and traditional CMS approaches&lt;/a&gt; is the companion decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Implementation Costs
&lt;/h2&gt;

&lt;p&gt;Rates first, since everything else derives from them. UK Drupal contract rates run roughly £400 to £550 a day for mid-level and £550 to £750 for senior work, as set out in our guide to &lt;a href="https://mecanik.dev/en/posts/hire-drupal-developer-rates-skills-vetting/" rel="noopener noreferrer"&gt;hiring a Drupal developer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A narrow deployment, meaning the AI module, one configured provider, alt text and translation, role-level governance and support for your DPIA, typically lands at £4,000 to £9,000. A full editorial suite adding taxonomy suggestion, CKEditor assistance, prompts held in code and spend monitoring runs £10,000 to £25,000.&lt;/p&gt;

&lt;p&gt;Semantic search or a public chatbot over a substantial content set is a different order of work. Hybrid retrieval, a chunking strategy, vector infrastructure and an evaluation harness that tells you whether the new search beats the old one puts that at £25,000 to £60,000. Token spend sits on top of all three and is usually the smallest line in the first year.&lt;/p&gt;

&lt;p&gt;Two costs sit outside the development budget. The DPIA and the provider contract review are legal and data protection work, and editorial review capacity is a staffing decision rather than a project line. Scoping both is part of our &lt;a href="https://mecanik.dev/en/ai-integration-services/" rel="noopener noreferrer"&gt;AI integration services&lt;/a&gt; engagement, because a project that ships without them ships a liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting It Deployed
&lt;/h2&gt;

&lt;p&gt;Mecanik builds these integrations on Drupal as part of our &lt;a href="https://mecanik.dev/en/services/software-development/" rel="noopener noreferrer"&gt;software development&lt;/a&gt; work, and the pattern is consistent across clients. The modules install in an afternoon. What takes the time is deciding which features justify their review burden, keeping content out of a third party's logs where it should not go, and building the governance and monitoring the contributed modules deliberately leave to you. If you want that scoped against your own content set and your own regulatory position, our &lt;a href="https://mecanik.dev/en/ai-integration-services/" rel="noopener noreferrer"&gt;AI integration services&lt;/a&gt; page is the place to start.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Related reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/wordpress-7-ai-client/" rel="noopener noreferrer"&gt;WordPress 7.0 and the AI Client in Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/ai-agent-payments/" rel="noopener noreferrer"&gt;AI Agent Payments: What Agentic Commerce Means for Merchants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/drupal-hosting/" rel="noopener noreferrer"&gt;Drupal Hosting: What Actually Runs Drupal Well&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mecanik.dev/en/posts/agentforce/" rel="noopener noreferrer"&gt;Agentforce: What Salesforce AI Agents Actually Cost&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What does the Drupal AI module actually do?&lt;/strong&gt;&lt;br&gt;
It provides an abstraction layer that turns AI calls into operation types such as chat, embeddings, text to image and translate text, then routes them to whichever provider you configure. Submodules build on that layer to deliver specific features including alt text generation, content translation, taxonomy suggestion, semantic search and site-building agents. The base module does nothing visible on its own until you install a provider, supply credentials and enable the submodules you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Drupal AI free to use?&lt;/strong&gt;&lt;br&gt;
The modules are free and open source under the same licence as Drupal core, but the model calls are not. Every generation, embedding and retrieval sends tokens to whichever provider you configured, and that provider bills you per million tokens in its own currency. Running a model on your own hardware removes the per-token bill and replaces it with a fixed monthly cost for the machine that serves it, which is cheaper only above a certain volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Drupal AI send my content to a third party?&lt;/strong&gt;&lt;br&gt;
Under a hosted provider, yes. The field content and your prompt leave your infrastructure on every call, and a content management system holds personal data in comments, user profiles and webform submissions as well as in articles. You remain the controller under UK GDPR, the ICO expects a DPIA in the vast majority of AI deployments, and a self-hosted model is the only configuration where nothing leaves your own servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Drupal AI Search production ready?&lt;/strong&gt;&lt;br&gt;
Not on its own. The AI Search module had no supported stable release as of 2 September 2026, only alpha builds on both branches, and it requires the Search API module plus a vector database that your team then has to operate. Sites getting good results from it have built hybrid retrieval and a chunking strategy around it, so treat it as one component of a search project rather than a module you switch on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much does a Drupal AI implementation cost?&lt;/strong&gt;&lt;br&gt;
A narrow deployment covering the AI module, one provider, alt text and translation, role-level governance and DPIA support usually runs £4,000 to £9,000. A full editorial suite with prompts held in code and spend monitoring runs £10,000 to £25,000. Semantic search or a public chatbot over a large content set, with hybrid retrieval, chunking and evaluation, runs £25,000 to £60,000, and token spend sits on top as the smallest line.&lt;/p&gt;

</description>
      <category>php</category>
      <category>drupal</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
