<?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: Anatoliy Dovgun</title>
    <description>The latest articles on DEV Community by Anatoliy Dovgun (@adovgun).</description>
    <link>https://dev.to/adovgun</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%2Fuser%2Fprofile_image%2F183424%2F2ad467b6-c4be-4f40-816c-ab26b47dacff.png</url>
      <title>DEV Community: Anatoliy Dovgun</title>
      <link>https://dev.to/adovgun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adovgun"/>
    <language>en</language>
    <item>
      <title>4WP-Booking: How One Dental Clinic’s Request Became a Domain-Driven WordPress Booking Platform</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Wed, 09 Sep 2026 13:41:13 +0000</pubDate>
      <link>https://dev.to/adovgun/4wp-booking-how-one-dental-clinics-request-became-a-domain-driven-wordpress-booking-platform-2fm6</link>
      <guid>https://dev.to/adovgun/4wp-booking-how-one-dental-clinics-request-became-a-domain-driven-wordpress-booking-platform-2fm6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Let's use a real case — a dental clinic and its appointment booking — to walk through what client-centricity, domain thinking, and scalability actually look like in WordPress development.&lt;/p&gt;

&lt;p&gt;Let's dive into WordPress development — domain first, integration second.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most WordPress booking plugins start the same way: a client needs to connect their site to a specific tool, so a developer wires up that one API and ships it. It works — until the second client shows up with a different calendar system, and the "quick integration" has to be rebuilt from scratch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/4wpdev/4wp-booking" rel="noopener noreferrer"&gt;4WP-Booking&lt;/a&gt; started from exactly that kind of request — a dental practice that needed native, on-site appointment scheduling connected to &lt;a href="https://clinic-cards.com/" rel="noopener noreferrer"&gt;Clinic Cards&lt;/a&gt;, the CRM it already ran on. But instead of shipping a "Clinic Cards plugin," we treated the request as a design problem first and an integration second. This article walks through why, using &lt;a href="https://4wp.dev/methodologies/domain-driven-design/" rel="noopener noreferrer"&gt;Domain-Driven Design&lt;/a&gt; and a lightweight &lt;a href="https://4wp.dev/methodologies/sdd/" rel="noopener noreferrer"&gt;Software Design Document&lt;/a&gt; process — the same methodologies we apply across every plugin built on &lt;a href="https://github.com/4wpdev/4wp-bundle" rel="noopener noreferrer"&gt;4wp-bundle&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The request behind the plugin
&lt;/h2&gt;

&lt;p&gt;The trigger for 4WP-Booking was a real, live clinic: &lt;a href="https://hrechkovskyi.com/booking/" rel="noopener noreferrer"&gt;Dr. Hrechkovskyi's dental practice in Lutsk&lt;/a&gt;. Patients needed to pick a service, a doctor, or a date, see real availability, and book directly from the clinic's WordPress site — without a phone call, and without the clinic's staff re-entering anything into Clinic Cards by hand.&lt;/p&gt;

&lt;p&gt;That's a completely reasonable, narrow request. The trap is answering it narrowly. If "booking" is modeled as "talk to the Clinic Cards API," every future requirement — a second CRM, a different industry, a client who uses Google Calendar instead of a clinic system — turns into a rewrite. If "booking" is modeled as its own domain, with Clinic Cards as one way of fulfilling it, the plugin can grow without breaking.&lt;/p&gt;

&lt;p&gt;That distinction is the whole argument for Domain-Driven Design, and it's why we reached for it here instead of just shipping an API wrapper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling booking as a domain, not an API call
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://4wp.dev/methodologies/domain-driven-design/" rel="noopener noreferrer"&gt;DDD&lt;/a&gt; starts from a simple premise: the business model should drive the architecture, not the other way around. Before writing anything, we asked what "booking" actually &lt;em&gt;means&lt;/em&gt;, independent of Clinic Cards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;appointment&lt;/strong&gt; is an entity — it has an identity, a status, and a lifecycle (requested, confirmed, completed, cancelled) that persists regardless of which calendar system stores it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;strong&gt;time slot&lt;/strong&gt; and a &lt;strong&gt;service&lt;/strong&gt; are value objects — defined entirely by their attributes, not by identity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;provider&lt;/strong&gt; — Clinic Cards today, something else tomorrow — is a detail the domain depends on, not the thing the domain is built around.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Framed this way, Clinic Cards stops being the foundation of the plugin and becomes an implementation of a contract. WordPress itself plays the same supporting role: it renders the calendar, exposes the shortcode and Gutenberg block, and stores configuration — but the booking logic doesn't know it's running inside WordPress, and it doesn't know it's talking to Clinic Cards. That separation is what "domain logic stays independent and portable" means in practice, and it's the reason the same approach scales to every plugin in the 4WP ecosystem, not just this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sketching the SDD before writing a line of integration code
&lt;/h2&gt;

&lt;p&gt;Where DDD answers &lt;em&gt;how the domain is modeled&lt;/em&gt;, a &lt;a href="https://4wp.dev/methodologies/sdd/" rel="noopener noreferrer"&gt;Software Design Document&lt;/a&gt; answers &lt;em&gt;how the system gets built&lt;/em&gt; — architecture, data flow, and build order, captured before implementation rather than discovered during it. For 4WP-Booking, that meant working through five questions up front:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt; — What's the MVP? Book by service, by doctor, or by date; capture patient name, phone, email; nothing more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt; — How do the pieces talk to each other? A WordPress-facing surface (block, shortcode, optional Elementor widget), a booking domain layer, and a provider layer behind an interface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data model&lt;/strong&gt; — Where do appointments, providers, and credentials live, and what depends on what?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data flow&lt;/strong&gt; — How does a click on "9:00 AM, Dr. Kozak" become a confirmed slot in Clinic Cards and a confirmation on screen?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implementation order&lt;/strong&gt; — Build the provider contract first, the Clinic Cards implementation second, the WordPress surfaces last — so the parts most likely to change (the UI) depend on the parts least likely to change (the domain), and not the reverse.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Roughly, the shape that came out of that process looks like this:&lt;/p&gt;

&lt;p&gt;WordPress surface → Shortcode · Gutenberg block · Elementor widget&lt;/p&gt;

&lt;p&gt;(interchangeable, no domain knowledge)&lt;/p&gt;

&lt;p&gt;Booking domain → Appointment · Service · Availability&lt;/p&gt;

&lt;p&gt;(framework-agnostic, provider-agnostic)&lt;/p&gt;

&lt;p&gt;Provider contract → a single interface every calendar&lt;/p&gt;

&lt;p&gt;integration must satisfy&lt;/p&gt;

&lt;p&gt;Providers → Clinic Cards (live)&lt;/p&gt;

&lt;p&gt;Google Calendar (planned)&lt;/p&gt;

&lt;p&gt;Calendly (planned)&lt;/p&gt;

&lt;p&gt;Nothing above this diagram's provider layer needs to change when a new provider is added. That's the point of designing the document before the integration: it turns "add Calendly support" from an architectural risk into a scheduling decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the provider stayed pluggable
&lt;/h2&gt;

&lt;p&gt;Clinic Cards is a strong first provider for a reason — it's purpose-built for clinics, which was exactly what the originating request needed. But the WordPress booking market is much bigger than clinics, and two names dominate general-purpose scheduling: &lt;strong&gt;Google Calendar&lt;/strong&gt;, which almost every business already uses, and &lt;strong&gt;Calendly&lt;/strong&gt;, which has become the default "just send me a link" tool for services and consultations.&lt;/p&gt;

&lt;p&gt;Because the provider sits behind a contract rather than being hard-coded into the domain, both are roadmap items rather than rewrites. The domain doesn't need to know whether "check availability" means calling Clinic Cards' schedule endpoint or reading free/busy blocks from a Google Calendar — it only needs an answer that fits the same shape. That's the practical payoff of doing the domain modeling and the design document first: the second and third integrations get cheaper instead of more expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  API keys, and why the server holds them
&lt;/h2&gt;

&lt;p&gt;One WordPress-specific decision is worth calling out on its own: where credentials live. 4WP-Booking keeps every provider API key — the Clinic Cards token today, a Google or Calendly credential tomorrow — on the server side, inside WordPress. The browser never sees it. Every call to the provider's API is made from WordPress itself, not from the visitor's session, which matters as soon as more than one provider (and more than one client's credentials) is in play. It's a small architectural rule, but it's the kind of rule that's easy to skip when a plugin is built as a one-off integration and much harder to skip when it's designed as a domain with a provider contract from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a "WordPress Methodologies" story, not just a plugin note
&lt;/h2&gt;

&lt;p&gt;DDD explains how to model the domain. SDD explains how to plan the build. Neither replaces the other, and neither is specific to booking — they're the same methodologies we use across the 4WP plugin family, from &lt;a href="https://wordpress.org/plugins/4wp-smart-link/" rel="noopener noreferrer"&gt;4WP Smart Link&lt;/a&gt; to &lt;a href="https://wordpress.org/plugins/4wp-faq/" rel="noopener noreferrer"&gt;4WP FAQ&lt;/a&gt; to &lt;a href="https://github.com/4wpdev/4wp-weather" rel="noopener noreferrer"&gt;4WP Weather&lt;/a&gt;, all built on the shared &lt;a href="https://github.com/4wpdev/4wp-bundle" rel="noopener noreferrer"&gt;4wp-bundle&lt;/a&gt; foundation. The pattern holds regardless of the vertical: identify the domain independent of the first integration, document the structure before building it, and keep the provider — CRM, calendar, weather API, whatever it is — behind a contract instead of woven through the plugin.&lt;/p&gt;

&lt;p&gt;That discipline is also why a client request that starts this specific — "connect my WordPress site to Clinic Cards" — is worth treating as a platform decision rather than a one-off script. It costs more up front than an API wrapper would. It costs a lot less the second time a client asks for something adjacent.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it's worth bringing in an architect
&lt;/h2&gt;

&lt;p&gt;Not every booking integration needs this much ceremony. A single site, a single provider, no plans to reuse the plugin elsewhere — that's a legitimate case for a lighter-weight, direct integration, and DDD's own guidance is to skip the heavier patterns for exactly that kind of simple, low-complexity build.&lt;/p&gt;

&lt;p&gt;But the moment a project needs to support more than one provider, more than one client with different requirements, or a booking flow with real business rules attached (cancellation policies, deposits, multi-staff scheduling), it's worth having a developer who thinks in domains and design documents, not just endpoints — someone who can own the provider contract and the data model as the integration list grows, rather than patching a new if branch in for every new calendar service. That's the difference between a plugin that supports Calendly next quarter and one that has to be rebuilt to support it.&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%2F4wp.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Fimage-4wp-booking-ddd-sdd-article-md-1024x581.png%2520align%3D" 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%2F4wp.dev%2Fwp-content%2Fuploads%2F2026%2F09%2Fimage-4wp-booking-ddd-sdd-article-md-1024x581.png%2520align%3D" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where 4WP-Booking stands today
&lt;/h2&gt;

&lt;p&gt;4WP-Booking is live and open source on &lt;a href="https://github.com/4wpdev/4wp-booking" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and its Clinic Cards integration is running in production on &lt;a href="https://hrechkovskyi.com/booking/" rel="noopener noreferrer"&gt;Dr. Hrechkovskyi's clinic site&lt;/a&gt; today. It's currently working through the WordPress.org plugin review process, with submission planned once the current plugin ahead of it in the queue clears verification. Google Calendar and Calendly support are next on the roadmap — and thanks to the provider contract the domain was built around, adding them is a matter of implementing the interface, not redesigning the plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://4wp.dev/plugin/4wp-booking/" rel="noopener noreferrer"&gt;4WP-Booking plugin page&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/4wpdev/4wp-booking" rel="noopener noreferrer"&gt;4WP-Booking on GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://hrechkovskyi.com/booking/" rel="noopener noreferrer"&gt;Dr. Hrechkovskyi Dental Clinic — live booking example&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://4wp.dev/methodologies/domain-driven-design/" rel="noopener noreferrer"&gt;Domain-Driven Design — 4wp.dev methodologies&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://4wp.dev/methodologies/sdd/" rel="noopener noreferrer"&gt;Software Design Document (SDD) — 4wp.dev methodologies&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/4wpdev/4wp-bundle" rel="noopener noreferrer"&gt;4wp-bundle on GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>opensource</category>
      <category>software</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>SOLID in the WordPress Ecosystem: What 4wp.dev's Plugins Actually Prove</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sun, 06 Sep 2026 10:03:17 +0000</pubDate>
      <link>https://dev.to/adovgun/solid-in-the-wordpress-ecosystem-what-4wpdevs-plugins-actually-prove-3h5</link>
      <guid>https://dev.to/adovgun/solid-in-the-wordpress-ecosystem-what-4wpdevs-plugins-actually-prove-3h5</guid>
      <description>&lt;p&gt;WordPress makes it easy to write code that works and easy to write code that lasts, and those are not the same skill. Hooks, filters, custom post types, widgets — the platform hands you enough rope to build something maintainable or something that collapses the moment a second developer touches it. SOLID is the discipline that decides which one you get. Not as academic dogma imported from enterprise Java, but as five habits that map cleanly onto things WordPress developers already do every day.&lt;/p&gt;

&lt;p&gt;We’ve written up all five principles in depth on &lt;strong&gt;4wp.dev/architectures/solid&lt;/strong&gt;. This isn’t a repeat of that material — it’s the harder question: does our own plugin catalog actually hold up against it? Where does it, and where are we still watching ourselves?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single Responsibility&lt;/strong&gt; — the one we had to learn from our own mistake&lt;br&gt;
SRP says a class, a module, a plugin should have one reason to change. The honest WordPress version of this failure mode is the “kitchen sink” plugin — the one that does SEO, and a slider, and contact forms, and social icons, because it was easier to bolt features onto something that already existed than to ship something new.&lt;/p&gt;

&lt;p&gt;We named this risk directly on our own SRP page, using 4wp-bundle as the cautionary example: if it ever grew to carry responsive controls, FAQ management, Gutenberg extensions, and settings all in one codebase, that’s exactly the violation. The actual answer wasn’t a paragraph of theory — it’s the catalog itself. 4WP-Booking, 4WP-FAQ, 4WP Smart Link, 4WP Weather, 4WP Drive, 4WP Notifications — each is a separate plugin with a separate reason to change, sharing only infrastructure through 4wp-bundle, not features. That split isn’t a marketing decision. It’s SRP applied at the product level, not just the class level, and it’s the reason a bug in the booking flow can never be a reason to redeploy the FAQ registry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open/Closed&lt;/strong&gt; — the principle WordPress ships for free&lt;br&gt;
OCP — open for extension, closed for modification — is arguably the one principle WordPress gets right by default. do_action() and apply_filters() are OCP in procedural form: WooCommerce alone ships 700+ action hooks and 400+ filter hooks, and its whole extension ecosystem exists without a single third party editing WooCommerce’s own source.&lt;/p&gt;

&lt;p&gt;Our own clearest example is 4WP-Booking. The plugin was built around a provider contract from day one — Clinic Cards today, Google Calendar and Calendly on the roadmap — specifically so that adding a new booking provider is an extension, not a rewrite. Nothing in the booking domain has to change for a new provider to plug in behind the same contract. That’s the same shape as a hook, just at the architecture layer instead of the callback layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Liskov Substitution&lt;/strong&gt; — the one that’s easiest to fake and hardest to actually keep&lt;br&gt;
LSP requires that anything substitutable for a base type actually behaves like it — same contract, no surprise exceptions, no broken assumptions. WordPress breaks this constantly: a WP_Widget subclass that throws where the parent returns cached output, a custom post type wrapper that crashes on get_permalink(). The interface looks satisfied. The behavior isn’t.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/4wp-smart-link/" rel="noopener noreferrer"&gt;4WP Smart Link&lt;/a&gt; is our sharpest internal test of this, even though it’s a runtime pattern rather than a class hierarchy — worth being precise about, since LSP is usually framed as inheritance. The plugin decides between anchor mode (wrap the block in tag:&amp;lt; a &amp;gt; ) and host mode (attach the click via data-forwp-smart-link-url) depending on whether inner interactive elements need to keep priority. Whichever mode fires, the outward contract — “this block is now clickable, and its inner links still work” — holds identically. Nothing downstream needs to know which mode ran. That’s the substitutability guarantee LSP is actually protecting, applied to a strategy choice instead of a class swap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interface Segregation&lt;/strong&gt; — don’t make every plugin implement methods it’ll never use&lt;br&gt;
ISP says no client should depend on methods it doesn’t use. Our own page frames this well for Gutenberg: a simple Quote block only needs to render, a Cover block needs render, transform, and style variations — forcing both through one bloated block contract is the violation, and forcing a lightweight SEO plugin to implement Has_Cron because some other plugin needs scheduled tasks is the same mistake at the plugin level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/4wp-faq/" rel="noopener noreferrer"&gt;4WP-FAQ&lt;/a&gt; is the clean version of this: the registry (the FAQ entry itself, as a CPT) doesn’t know or care how it’s displayed, and the two display blocks — 4WP FAQ List and 4WP FAQ Card — each consume only what they need from the registry, not a shared monolithic “FAQ manager” interface that both would have to partially ignore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency Inversion&lt;/strong&gt; — the one that makes the roadmap possible&lt;br&gt;
DIP says high-level modules should depend on abstractions, not concrete implementations — a Mailer_Interface instead of a hard-coded wp_mail() call, so you can swap providers without touching business logic.&lt;/p&gt;

&lt;p&gt;This is, again, &lt;strong&gt;4WP-Booking&lt;/strong&gt;‘s core architectural bet. The booking domain doesn’t depend on Clinic Cards — it depends on a provider contract that Clinic Cards happens to implement first. That’s the entire reason Google Calendar and Calendly can be added later as roadmap items instead of rewrites. DIP isn’t an abstraction for its own sake here; it’s the specific mechanism that turns “add a new integration” from a risk into a scheduling decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where this actually lands&lt;/strong&gt;&lt;br&gt;
SOLID overlaps with itself constantly — Booking’s provider contract is simultaneously an OCP and a DIP story, and that’s not sloppy analysis, it’s what these principles look like when they’re actually load-bearing instead of decorative. The honest version of this article isn’t “we nailed all five” — it’s that four of the five show up as deliberate architecture in shipped plugins, and the fifth (SRP) shows up as a mistake we named on our own site before we fixed it in the catalog. That’s the difference between citing SOLID and being accountable to it.&lt;/p&gt;

&lt;p&gt;Full breakdowns of each principle, with more WordPress-specific examples and common violations: &lt;strong&gt;4wp.dev/architectures/solid&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sources&lt;/p&gt;

&lt;p&gt;&lt;a href="https://4wp.dev/architectures/solid/" rel="noopener noreferrer"&gt;SOLID overview — 4wp.dev&lt;/a&gt;&lt;br&gt;
&lt;a href="https://4wp.dev/architectures/solid/single-responsibility/" rel="noopener noreferrer"&gt;Single Responsibility Principle — 4wp.dev&lt;/a&gt;&lt;br&gt;
&lt;a href="https://4wp.dev/architectures/solid/open-closed-principle/" rel="noopener noreferrer"&gt;Open/Closed Principle — 4wp.dev&lt;/a&gt;&lt;br&gt;
&lt;a href="https://4wp.dev/architectures/solid/liskov-substitution/" rel="noopener noreferrer"&gt;Liskov Substitution Principle — 4wp.dev&lt;/a&gt;&lt;br&gt;
&lt;a href="https://4wp.dev/architectures/solid/interface-segregation/" rel="noopener noreferrer"&gt;Interface Segregation Principle — 4wp.dev&lt;/a&gt;&lt;br&gt;
&lt;a href="https://4wp.dev/architectures/solid/dependency-inversion/" rel="noopener noreferrer"&gt;Dependency Inversion Principle — 4wp.dev&lt;/a&gt;&lt;br&gt;
4WP-Booking · 4WP Smart Link · 4WP-FAQ · 4wp-bundle&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>php</category>
      <category>softwareengineering</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>The Whale Metaphor: How OOP's Four Pillars Actually Work in WordPress</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sat, 29 Aug 2026 20:51:45 +0000</pubDate>
      <link>https://dev.to/adovgun/the-whale-metaphor-how-oops-four-pillars-actually-work-in-wordpress-21cd</link>
      <guid>https://dev.to/adovgun/the-whale-metaphor-how-oops-four-pillars-actually-work-in-wordpress-21cd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In the age of AI engineering and vibe coding, almost nobody mentions OOP anymore. But what if children were never taught prefixes, roots, and suffixes — the architecture of words — or how a sentence is properly built? Will AI agents really be enough for the specialists of tomorrow, if those specialists never learned the grammar underneath?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://4wp.dev/oop/" rel="noopener noreferrer"&gt;Dive into OOP in WordPress development practice →&lt;/a&gt; (original source, featuring the four whales example)&lt;/p&gt;

&lt;p&gt;Most WordPress developers learn Object-Oriented Programming the hard way: by staring at WP_Widget, WP_Query, or WP_Post and reverse-engineering why core is built the way it is. Textbooks explain encapsulation, abstraction, inheritance, and polymorphism with abstract diagrams that rarely survive contact with real code.&lt;/p&gt;

&lt;p&gt;Here's a different way to think about it — using a whale.&lt;/p&gt;

&lt;p&gt;A whale keeps its vital organs protected inside its body, dives into depths where the mechanics of survival are invisible from the surface, passes traits down to its calf, and adapts its behavior differently depending on the environment it's in. Swap "whale" for "class," and you've basically described the four pillars of OOP. Let's walk through each one with WordPress-specific code, then look at how the same principles scale from a five-page brochure site to an enterprise platform.&lt;/p&gt;

&lt;p&gt;Why OOP Matters in WordPress at All&lt;/p&gt;

&lt;p&gt;WordPress was procedural for most of its early life, and plenty of plugins still are. But once your project outgrows a handful of files, procedural code starts fighting you: global state leaks everywhere, the same logic gets copy-pasted into three different hooks, and a single typo in a variable name three files away breaks something unrelated.&lt;/p&gt;

&lt;p&gt;OOP fixes this by grouping data and behavior together into objects instead of scattering functions and passing arrays between them. WordPress core made this bet a long time ago — WP_Widget, WP_Query, and WP_Post are all classes — and the four principles below are the foundation that makes classes trustworthy enough to build on. They're also the on-ramp to SOLID, the next level of discipline for larger codebases.&lt;/p&gt;

&lt;p&gt;Encapsulation: The Protected Whale&lt;/p&gt;

&lt;p&gt;A whale's vital organs sit safely inside its body, reachable only through a small number of controlled openings. Encapsulation applies the same idea to a class: internal data is hidden from the outside world, and only a deliberate, controlled interface is exposed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_display_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'display_name'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_password_hash&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's public and what isn't. get_display_name() is a safe, read-only window into the object. get_password_hash() stays private — nothing outside the class can reach it, even by accident. That's the entire point: encapsulation isn't about hiding things for secrecy, it's about making it impossible to misuse an object's internals from the outside.&lt;/p&gt;

&lt;p&gt;In a WordPress plugin, this is the difference between a settings class that validates and sanitizes every value it stores, versus a plugin that lets any file in the codebase poke directly into a raw options array. One of those breaks when someone forgets to call sanitize_text_field(). The other can't.&lt;/p&gt;

&lt;p&gt;Abstraction: The Deep Whale&lt;/p&gt;

&lt;p&gt;A whale dives into depths where its physiology does things — pressure regulation, oxygen management — that are irrelevant to anyone watching from a boat. All you need to know is: it dives, it surfaces, it breathes. Abstraction works the same way in code: it hides complexity behind a simple, stable interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payment_Gateway&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stripe&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Payment_Gateway&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;stripe_api&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything calling process($amount) doesn't need to know that Stripe's implementation involves API authentication, currency conversion, and webhook handling. The abstract class defines the contract — "every payment gateway must be able to process an amount" — and each concrete class fills in the messy details.&lt;/p&gt;

&lt;p&gt;This matters enormously in WordPress e-commerce or membership plugins, where you might support Stripe today and PayPal or a local payment processor tomorrow. If the rest of your plugin talks to Payment_Gateway instead of talking to Stripe directly, swapping or adding a gateway means writing one new class — not hunting through the codebase for every place that assumed Stripe.&lt;/p&gt;

&lt;p&gt;Inheritance: Mother and Calf&lt;/p&gt;

&lt;p&gt;A calf inherits traits from its mother without needing to relearn how to swim from scratch. Inheritance lets a child class receive properties and methods from a parent, then extend or specialize them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Base_Widget&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;WP_Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;set_transient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Popular_Posts&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Base_Widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_posts&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'orderby'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'comment_count'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$posts&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base_Widget extends WordPress's own WP_Widget and adds one useful shared behavior: caching. Popular_Posts then extends Base_Widget and gets caching for free, without reimplementing it. If you build five widgets this way, you write the caching logic exactly once.&lt;/p&gt;

&lt;p&gt;This is also where a lot of WordPress developers get their first real taste of OOP, because WP_Widget practically forces the pattern on you. But the same idea applies to custom post type controllers, REST API endpoint classes, or any group of components that share a common backbone but differ in specifics.&lt;/p&gt;

&lt;p&gt;A word of caution: inheritance is powerful but easy to overuse. Deep inheritance chains (a class extending a class extending a class extending a class) get brittle fast — a change to a distant parent can silently break every descendant. Use it when the "is-a" relationship is genuinely true (a Popular_Posts widget is a widget), and reach for composition when it isn't.&lt;/p&gt;

&lt;p&gt;Polymorphism: The Many Forms&lt;/p&gt;

&lt;p&gt;Whales adapt their behavior to different environments while remaining recognizably whales. Polymorphism is the OOP version: different objects share the same interface, but each implements it in its own way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Notifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email_Notifier&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Notifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;wp_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Alert'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Notifiable&lt;/span&gt; &lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The notify() function doesn't know or care whether it's talking to an Email_Notifier, a Slack_Notifier, or an SMS_Notifier — as long as each one implements send(), they're interchangeable. This is what makes plugin architectures extensible: you can add a brand-new notification channel by writing a new class that implements Notifiable, and every place in your codebase that already calls notify() picks it up automatically, with zero changes to existing code.&lt;/p&gt;

&lt;p&gt;The Same Principles, Different Scale&lt;/p&gt;

&lt;p&gt;What makes these four pillars genuinely useful — rather than just academic — is that they scale up and down with the size of the project.&lt;/p&gt;

&lt;p&gt;On a small brochure site or blog, you don't need an elaborate class hierarchy. A single, well-encapsulated class is often enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Theme_Options&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nv"&gt;$options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sanitize_text_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;update_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_theme_options'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one class handles a contact form's settings or a set of theme options. Encapsulation keeps the stored data safe from careless direct writes, and if you later need a specialized version — say, a widget with extra behavior — inheriting from WP_Widget gets you there without rebuilding anything from scratch.&lt;/p&gt;

&lt;p&gt;At the enterprise end, the same four principles are doing exactly the same job, just at higher stakes: encapsulated data models that can't be corrupted by a stray script, abstracted payment or search providers that can be swapped without a rewrite, inherited base classes that keep dozens of custom post types consistent, and polymorphic interfaces that let a plugin ecosystem grow without every new feature requiring changes to old code.&lt;/p&gt;

&lt;p&gt;Where This Leads: From Four Pillars to SOLID&lt;/p&gt;

&lt;p&gt;Encapsulation, abstraction, inheritance, and polymorphism aren't four unrelated rules to memorize — they're the raw material that a more advanced set of rules is built from: SOLID.&lt;/p&gt;

&lt;p&gt;The connection is direct, not theoretical. A well-encapsulated UserProfile class, one that exposes only what callers actually need, is already halfway to Single Responsibility — a class that guards its own data tends to guard its own job, too. The Payment_Gateway abstraction is a working example of Dependency Inversion: the rest of the plugin depends on the abstract contract, not on Stripe specifically, which is also what makes it Open for extension, closed for modification. Base_Widget extending WP_Widget only holds up as long as every subclass can stand in for its parent without surprising callers — that's Liskov Substitution, and it's exactly where careless inheritance chains start to break. And the Notifiable interface is a small, focused contract rather than a bloated one — the seed of Interface Segregation.&lt;/p&gt;

&lt;p&gt;In other words, once encapsulation, abstraction, inheritance, and polymorphism feel natural, you're not learning SOLID from zero — you're learning the names for things you're already halfway doing. That's the next stop: &lt;a href="https://4wp.dev/architectures/solid/" rel="noopener noreferrer"&gt;SOLID Principles in WordPress →&lt;/a&gt;, five rules that take these four pillars and turn them into architecture that survives years of feature requests without a rewrite.&lt;/p&gt;

&lt;p&gt;But it starts here — with a whale, its calf, and four ideas that WordPress core has been quietly demonstrating in WP_Widget, WP_Query, and WP_Post all along.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>php</category>
      <category>programming</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Open/Closed Principle in WordPress Architecture</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sun, 01 Mar 2026 15:26:58 +0000</pubDate>
      <link>https://dev.to/adovgun/openclosed-principle-in-wordpress-architecture-4f3l</link>
      <guid>https://dev.to/adovgun/openclosed-principle-in-wordpress-architecture-4f3l</guid>
      <description>&lt;p&gt;WordPress plugins rarely collapse because of complexity.&lt;/p&gt;

&lt;p&gt;They collapse because of change.&lt;/p&gt;

&lt;p&gt;New payment gateway.&lt;br&gt;&lt;br&gt;
New notification channel.&lt;br&gt;&lt;br&gt;
New integration.&lt;br&gt;&lt;br&gt;
New business rule.&lt;/p&gt;

&lt;p&gt;And suddenly — you are modifying the same class again.&lt;/p&gt;

&lt;p&gt;That’s exactly what the &lt;strong&gt;Open/Closed Principle (OCP)&lt;/strong&gt; is designed to prevent.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Open/Closed Principle Really Means
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Software entities should be open for extension, but closed for modification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; mean your code never changes.&lt;/p&gt;

&lt;p&gt;It means:&lt;/p&gt;

&lt;p&gt;Core behavior remains stable.&lt;br&gt;&lt;br&gt;
New functionality is added by extension — not by editing existing logic.&lt;/p&gt;

&lt;p&gt;If every new feature requires touching old code, your architecture is fragile.&lt;/p&gt;


&lt;h2&gt;
  
  
  A Typical WordPress Anti-Pattern
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php
class PaymentProcessor {
    public function process(string $method, float $amount): void {
        if ($method === 'paypal') {
            // PayPal logic
        }

        if ($method === 'stripe') {
            // Stripe logic
        }

        if ($method === 'bank') {
            // Bank transfer logic
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Works fine.&lt;/p&gt;

&lt;p&gt;Until business says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add Apple Pay&lt;/li&gt;
&lt;li&gt;Add crypto&lt;/li&gt;
&lt;li&gt;Add installment payments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each time → modify the same class.&lt;/p&gt;

&lt;p&gt;That’s not closed for modification.&lt;/p&gt;

&lt;p&gt;That’s conditional chaos.&lt;/p&gt;
&lt;h2&gt;
  
  
  A Better Architectural Direction
&lt;/h2&gt;

&lt;p&gt;Introduce abstraction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;interface PaymentMethod {&lt;br&gt;
    public function process(float $amount): void;&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Concrete implementations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PayPalPayment implements PaymentMethod {}
class StripePayment implements PaymentMethod {}
class BankTransferPayment implements PaymentMethod {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Processor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class PaymentProcessor {
    public function __construct(private PaymentMethod $method) {}

    public function process(float $amount): void {
        $this-&amp;gt;method-&amp;gt;process($amount);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;/p&gt;

&lt;p&gt;Adding Apple Pay = new class.&lt;br&gt;
No modification of existing logic.&lt;/p&gt;

&lt;p&gt;That’s Open/Closed in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why WordPress Developers Struggle with OCP
&lt;/h2&gt;

&lt;p&gt;WordPress encourages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature flags inside giant classes&lt;/li&gt;
&lt;li&gt;Conditionals inside hooks&lt;/li&gt;
&lt;li&gt;Direct API calls&lt;/li&gt;
&lt;li&gt;Procedural branching logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But scalable systems require extension-based thinking.&lt;/p&gt;

&lt;p&gt;If your architecture grows by adding if statements — it won’t scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Apply OCP in WordPress
&lt;/h2&gt;

&lt;p&gt;Use it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment gateways&lt;/li&gt;
&lt;li&gt;Notification systems (email, SMS, Slack)&lt;/li&gt;
&lt;li&gt;Export formats (CSV, PDF, JSON)&lt;/li&gt;
&lt;li&gt;Caching strategies&lt;/li&gt;
&lt;li&gt;Storage drivers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any place where variation is expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;p&gt;Open/Closed Principle enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safer refactoring&lt;/li&gt;
&lt;li&gt;Replaceable infrastructure&lt;/li&gt;
&lt;li&gt;Testable components&lt;/li&gt;
&lt;li&gt;Plugin-style extensibility&lt;/li&gt;
&lt;li&gt;Long-term maintainability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your plugin is constantly rewritten, OCP is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Most WordPress plugins are built to work.&lt;/p&gt;

&lt;p&gt;Few are built to evolve.&lt;/p&gt;

&lt;p&gt;Open/Closed Principle is what makes the difference.&lt;/p&gt;

&lt;p&gt;Full breakdown:&lt;br&gt;
&lt;a href="https://4wp.dev/architectures/solid/open-closed-principle/" rel="noopener noreferrer"&gt;https://4wp.dev/architectures/solid/open-closed-principle/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>4wpdev</category>
      <category>wordpress</category>
      <category>solidprinciples</category>
    </item>
    <item>
      <title>Single Responsibility Principle for OOP WordPress Developers</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sat, 28 Feb 2026 17:55:21 +0000</pubDate>
      <link>https://dev.to/adovgun/single-responsibility-principle-for-oop-wordpress-developers-1700</link>
      <guid>https://dev.to/adovgun/single-responsibility-principle-for-oop-wordpress-developers-1700</guid>
      <description>&lt;p&gt;If you want to grow as an OOP WordPress developer, understanding the Single Responsibility Principle (SRP) is not optional — it’s foundational.&lt;/p&gt;

&lt;p&gt;SRP is the first principle from the well-known SOLID design principles. It states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A class should have only one reason to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  That sounds simple. In practice — especially in WordPress — it’s often ignored.
&lt;/h2&gt;

&lt;p&gt;The Real Problem in WordPress Projects&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many WordPress developers still build:&lt;/li&gt;
&lt;li&gt;Massive utility classes&lt;/li&gt;
&lt;li&gt;God-like service objects&lt;/li&gt;
&lt;li&gt;Theme functions.php monsters&lt;/li&gt;
&lt;li&gt;Plugins that do “everything”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tight coupling&lt;/li&gt;
&lt;li&gt;Hard-to-test code&lt;/li&gt;
&lt;li&gt;Fear of refactoring&lt;/li&gt;
&lt;li&gt;Fragile architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an OOP WordPress developer, this becomes a career bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Single Responsibility Really Means
&lt;/h2&gt;

&lt;p&gt;SRP does NOT mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One method per class&lt;/li&gt;
&lt;li&gt;Ultra-micro classes&lt;/li&gt;
&lt;li&gt;Artificial splitting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SRP means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A class should have one clear responsibility in the business domain.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Bad:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;class OrderManager {&lt;br&gt;
    public function createOrder() {}&lt;br&gt;
    public function sendEmail() {}&lt;br&gt;
    public function generateInvoicePDF() {}&lt;br&gt;
    public function logActivity() {}&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Good:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;class OrderCreator {}&lt;br&gt;
class OrderMailer {}&lt;br&gt;
class InvoiceGenerator {}&lt;br&gt;
class ActivityLogger {}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each class now has one reason to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying SRP in WordPress Architecture
&lt;/h2&gt;

&lt;p&gt;For a professional OOP WordPress developer, SRP applies to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Plugin Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controllers&lt;/li&gt;
&lt;li&gt;Services&lt;/li&gt;
&lt;li&gt;Repositories&lt;/li&gt;
&lt;li&gt;Integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MyPlugin.php (3000 lines)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/&lt;br&gt;
 ├── Application/&lt;br&gt;
 ├── Domain/&lt;br&gt;
 ├── Infrastructure/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Hooks &amp;amp; WordPress Actions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;add_action('init', function () {&lt;br&gt;
    register_post_type(...);&lt;br&gt;
    sendEmails();&lt;br&gt;
    syncWithAPI();&lt;br&gt;
});&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Instead, delegate:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;add_action('init', [PostTypeRegistrar::class, 'register']);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Theme Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In modern block themes and FSE:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Templates define structure&lt;/li&gt;
&lt;li&gt;Patterns define reusable UI&lt;/li&gt;
&lt;li&gt;PHP handles domain logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mixing all of this breaks SRP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for an OOP WordPress Developer
&lt;/h2&gt;

&lt;p&gt;If you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Work on enterprise WordPress projects&lt;/li&gt;
&lt;li&gt;Pass technical interviews&lt;/li&gt;
&lt;li&gt;Build scalable products&lt;/li&gt;
&lt;li&gt;Refactor safely&lt;/li&gt;
&lt;li&gt;Collaborate in teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SRP is your baseline.&lt;/p&gt;

&lt;p&gt;Without it, you are just writing procedural code wrapped in classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common SRP Mistakes in WordPress
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fat service classes&lt;/li&gt;
&lt;li&gt;Overloaded helpers&lt;/li&gt;
&lt;li&gt;Static utility dumping grounds&lt;/li&gt;
&lt;li&gt;Mixed domain + infrastructure logic&lt;/li&gt;
&lt;li&gt;Business logic inside template files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this sounds familiar — your architecture needs refactoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the Full Architecture Breakdown
&lt;/h2&gt;

&lt;p&gt;This article is a short overview.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;For a deeper architectural breakdown, examples, and structured explanation, read the original post:&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
👉 &lt;a href="https://4wp.dev/architectures/solid/single-responsibility/" rel="noopener noreferrer"&gt;https://4wp.dev/architectures/solid/single-responsibility/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thought&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Being an OOP WordPress developer is not about using classes.&lt;br&gt;
It’s about understanding responsibility boundaries.&lt;br&gt;
Master SRP — and the rest of SOLID becomes easier.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>oop</category>
      <category>solidprinciples</category>
      <category>4wpev</category>
    </item>
    <item>
      <title>4WP.dev — Modular WordPress Architecture, Gutenberg Custom Blocks, SEO &amp; Automation</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sat, 14 Feb 2026 23:05:31 +0000</pubDate>
      <link>https://dev.to/adovgun/4wpdev-modular-wordpress-architecture-gutenberg-custom-blocks-seo-automation-3if2</link>
      <guid>https://dev.to/adovgun/4wpdev-modular-wordpress-architecture-gutenberg-custom-blocks-seo-automation-3if2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“When you have one plugin — it’s great. When you have several — even better. When they are actively used — perfect. But then the real pain begins…”&lt;br&gt;
— Full-stack WordPress developer with open two eyes&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have 12+ plugins and over 30+ projects where use it and know how management it from one place:&lt;/p&gt;

&lt;p&gt;Site: &lt;a href="https://4wp.dev/plugin/" rel="noopener noreferrer"&gt;https://4wp.dev/plugin/&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/4wpdev/4wpdev" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wpdev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple example: one WordPress project uses multiple plugins. Then more projects reuse them. Over time the ecosystem grows — modules increase, dependencies expand, logic overlaps, responsibilities blur, performance fluctuates, updates introduce risks, and maintenance becomes unpredictable.&lt;/p&gt;

&lt;p&gt;4WP.dev is a modular WordPress development framework designed to solve this problem and scale complex Gutenberg-based projects efficiently.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>add_filter(‘practice_case_b2b_migration’): Why Using __return_false Changes the Core Email Flow in WordPress</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sat, 07 Feb 2026 21:03:49 +0000</pubDate>
      <link>https://dev.to/adovgun/addfilterpracticecaseb2bmigration-why-using-returnfalse-changes-the-core-email-flow-in-2p2a</link>
      <guid>https://dev.to/adovgun/addfilterpracticecaseb2bmigration-why-using-returnfalse-changes-the-core-email-flow-in-2p2a</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add_filter( 'send_email_change_email', '__return_false' )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Returning False Changes the Core Email Flow in WordPress&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Filters in WordPress are often described as tools for modifying data.&lt;/p&gt;

&lt;p&gt;In real-world systems — they control behavior.&lt;/p&gt;

&lt;p&gt;And in complex architectures, they prevent infrastructure-level failures.&lt;/p&gt;

&lt;p&gt;This article walks through a real B2B marketplace migration case and explains why returning false in specific filters is not a hack — but an architectural control decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Scenario: B2B Marketplace + CRM Migration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Imagine:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are building a B2B marketplace.&lt;/li&gt;
&lt;li&gt;The source of truth for users is an external CRM.&lt;/li&gt;
&lt;li&gt;Thousands of users must be migrated or synchronized.&lt;/li&gt;
&lt;li&gt;During synchronization, user emails may change.&lt;/li&gt;
&lt;li&gt;Users do not initiate these changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first glance, this looks like a standard import task.&lt;/p&gt;

&lt;p&gt;It isn’t.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What WordPress Does by Default&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When wp_update_user() runs and detects an email change, WordPress:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detects the email difference.&lt;/li&gt;
&lt;li&gt;Prepares a notification. &lt;/li&gt;
&lt;li&gt;Applies the filter send_email_change_email.&lt;/li&gt;
&lt;li&gt;If the filter returns true → the email is sent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Additionally, depending on context, WordPress may also trigger:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New user notification&lt;/li&gt;
&lt;li&gt;Password change notification&lt;/li&gt;
&lt;li&gt;Email verification flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For interactive user behavior — this is correct.&lt;/p&gt;

&lt;p&gt;For CRM-driven migration — this is dangerous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Infrastructure Risk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Uncontrolled email triggers during migration can cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Thousands of unintended emails&lt;/li&gt;
&lt;li&gt;SMTP throttling&lt;/li&gt;
&lt;li&gt;Domain reputation damage&lt;/li&gt;
&lt;li&gt;Confused users&lt;/li&gt;
&lt;li&gt;Support overload&lt;/li&gt;
&lt;li&gt;Trust erosion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a minor technical detail.&lt;/p&gt;

&lt;p&gt;This is product-level and infrastructure-level risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The First Layer of Control (And Why It Wasn’t Enough)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During initial system design, we anticipated user creation issues.&lt;/p&gt;

&lt;p&gt;So we disabled new user notifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add_filter( 'wp_send_new_user_notification_to_user', '__return_false' );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That solved welcome emails during migration.&lt;/p&gt;

&lt;p&gt;But real-world testing exposed a gap.&lt;/p&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Some users were created.&lt;/li&gt;
&lt;li&gt;Some were updated.&lt;/li&gt;
&lt;li&gt;Some had email changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even with new user notifications disabled, WordPress still triggered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;send_email_change_email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because from core perspective — email change is a separate lifecycle event.&lt;/p&gt;

&lt;p&gt;Lesson:&lt;/p&gt;

&lt;p&gt;Disabling one filter does not mean you control the lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Critical Decision Point&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To prevent email change notifications:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;add_filter( 'send_email_change_email', '__return_false' );&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default → true.&lt;/p&gt;

&lt;p&gt;We explicitly return false.&lt;/p&gt;

&lt;p&gt;Important:&lt;/p&gt;

&lt;p&gt;This does not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify user data&lt;/li&gt;
&lt;li&gt;Override internal functions&lt;/li&gt;
&lt;li&gt;Disable WordPress email globally&lt;/li&gt;
&lt;li&gt;Break core logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It changes a boolean decision flag.&lt;/p&gt;

&lt;p&gt;That’s a behavioral override.&lt;/p&gt;

&lt;p&gt;And that’s an architectural choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Is Architecturally Significant&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is a difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modifying data&lt;/li&gt;
&lt;li&gt;Modifying control flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Boolean filters like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send_email_change_email&lt;/li&gt;
&lt;li&gt;send_password_change_email&lt;/li&gt;
&lt;li&gt;user_has_cap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are not data transformers.&lt;/p&gt;

&lt;p&gt;They are behavioral switches.&lt;/p&gt;

&lt;p&gt;They sit at decision points inside core.&lt;/p&gt;

&lt;p&gt;Changing them alters system flow — not data integrity.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scoped Implementation (Best Practice)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This should never be applied blindly in production.&lt;/p&gt;

&lt;p&gt;Recommended approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ( defined( 'CRM_MIGRATION_PROCESS' ) &amp;amp;&amp;amp; CRM_MIGRATION_PROCESS ) {
    add_filter( 'send_email_change_email', '__return_false' );
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apply only in CLI context&lt;/li&gt;
&lt;li&gt;Scope to migration service layer&lt;/li&gt;
&lt;li&gt;Enable only during sync&lt;/li&gt;
&lt;li&gt;Remove after migration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Control the scope.&lt;/p&gt;

&lt;p&gt;Never globally suppress lifecycle behavior without context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Practical Classification of WordPress Filters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Understanding filter types helps avoid architectural mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Data Filters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Modify values.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the_content&lt;/li&gt;
&lt;li&gt;pre_user_email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Boolean Control Filters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Control system decisions.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send_email_change_email&lt;/li&gt;
&lt;li&gt;send_password_change_email&lt;/li&gt;
&lt;li&gt;user_has_cap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are control switches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Pre-Persistence Filters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run before saving data.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;pre_insert_user_data&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Capability &amp;amp; Security Filters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Control permissions.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;map_meta_cap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our migration case, we were dealing with Boolean Control Filters.&lt;/p&gt;

&lt;p&gt;These require architectural awareness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Was Required in B2B Context&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this migration:&lt;/li&gt;
&lt;li&gt;Email changes were system-driven.&lt;/li&gt;
&lt;li&gt;CRM was authoritative.&lt;/li&gt;
&lt;li&gt;Users did not initiate updates.&lt;/li&gt;
&lt;li&gt;No verification flow was required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From UX perspective — notifications would create noise.&lt;/p&gt;

&lt;p&gt;From infrastructure perspective — risk.&lt;/p&gt;

&lt;p&gt;From architecture perspective — mandatory control.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Engineering Lessons&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Study full lifecycle, not just creation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inspect internal trigger points in wp_update_user().&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Map all related filters before mass operations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use scoped boolean filters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Document every override.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WordPress core works correctly.&lt;/p&gt;

&lt;p&gt;But it works correctly for interactive user flows.&lt;/p&gt;

&lt;p&gt;CRM-driven synchronization is not interactive.&lt;/p&gt;

&lt;p&gt;If you do not control trigger points —&lt;br&gt;
core will execute its default behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thought&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;add_filter( 'send_email_change_email', '__return_false' );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not about “turning something off.”&lt;/p&gt;

&lt;p&gt;It is about taking control over system behavior.&lt;/p&gt;

&lt;p&gt;In complex B2B systems, filters are not customization tools.&lt;/p&gt;

&lt;p&gt;They are infrastructure control mechanisms.&lt;/p&gt;

&lt;p&gt;And every time you override default WordPress behavior,&lt;br&gt;
you are making an architectural decision — not just writing code.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://4wp.dev" rel="noopener noreferrer"&gt;https://4wp.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>4wpdev</category>
      <category>wordpress</category>
      <category>webdev</category>
    </item>
    <item>
      <title>4wp Responsive Plugin</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Tue, 03 Feb 2026 16:46:58 +0000</pubDate>
      <link>https://dev.to/adovgun/4wp-responsive-plugin-2pnp</link>
      <guid>https://dev.to/adovgun/4wp-responsive-plugin-2pnp</guid>
      <description>&lt;h1&gt;
  
  
  4WP Responsive
&lt;/h1&gt;

&lt;p&gt;Responsive controls for core Gutenberg blocks with per-breakpoint spacing, visibility, font-size, and alignment support. Designed to be theme.json-first and editor-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;– Plugin page: &lt;a href="https://4wp.dev/plugin/4wp-responsive/" rel="noopener noreferrer"&gt;https://4wp.dev/plugin/4wp-responsive/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;– GitRepository: &lt;a href="https://github.com/4wpdev/4wp-responsive" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wp-responsive&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does today
&lt;/h2&gt;

&lt;p&gt;– Responsive spacing per breakpoint (Padding/Margin: top/right/bottom/left).&lt;/p&gt;

&lt;p&gt;– Responsive visibility per breakpoint (Hide / Show only).&lt;/p&gt;

&lt;p&gt;– Responsive font-size per breakpoint for blocks that support typography.&lt;/p&gt;

&lt;p&gt;– Responsive text alignment per breakpoint (left/center/right/justify).&lt;/p&gt;

&lt;p&gt;– Works with core blocks (e.g. Heading, Paragraph, Group, List) and any block that supports the same features.&lt;/p&gt;

&lt;p&gt;– Uses utility CSS classes + CSS variables (no frontend JS).&lt;/p&gt;

&lt;p&gt;– Editor preview stays in sync with the selected device mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages
&lt;/h2&gt;

&lt;p&gt;– Theme.json-first: reads spacing and font-size presets from the active theme.&lt;/p&gt;

&lt;p&gt;– Works with standard blocks (no custom block requirement).&lt;/p&gt;

&lt;p&gt;– Minimal markup changes: classes + CSS variables only.&lt;/p&gt;

&lt;p&gt;– Safe fallback: content stays intact if the plugin is deactivated.&lt;/p&gt;

&lt;p&gt;– Single generated CSS file, cached with a hash.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;– &lt;strong&gt;Presets&lt;/strong&gt; (from &lt;code&gt;theme.json&lt;/code&gt;) are applied via utility classes like:&lt;/p&gt;

&lt;p&gt;– &lt;code&gt;has-forwp-padding-top-mobile-40&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;– &lt;code&gt;has-forwp-font-size-desktop-large&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;– &lt;code&gt;has-forwp-text-align-mobile-center&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;– &lt;strong&gt;Custom values&lt;/strong&gt; (e.g. 150 / 150px / 2rem) are applied via:&lt;/p&gt;

&lt;p&gt;– class &lt;code&gt;has-forwp-*-custom&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;– CSS variable on the block, e.g. &lt;code&gt;–forwp-padding-top-mobile: 150px;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;– this keeps output clean while allowing arbitrary values&lt;/p&gt;

&lt;h2&gt;
  
  
  Editor UI
&lt;/h2&gt;

&lt;p&gt;You will find &lt;strong&gt;Responsive Spacing&lt;/strong&gt; in the block inspector sidebar:&lt;/p&gt;

&lt;p&gt;– Tabs: Desktop / Tablet / Mobile&lt;/p&gt;

&lt;p&gt;– Sections: Padding, Margin, Font Size, Alignment, Visibility&lt;/p&gt;

&lt;p&gt;– Current screen size is shown per tab&lt;/p&gt;

&lt;p&gt;– Hidden blocks are dimmed in the editor instead of fully removed&lt;/p&gt;

&lt;h2&gt;
  
  
  Settings
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Settings → 4WP Responsive&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Breakpoints
&lt;/h3&gt;

&lt;p&gt;Displayed in a table with the active source:&lt;/p&gt;

&lt;p&gt;– &lt;code&gt;theme.json&lt;/code&gt; (&lt;code&gt;settings.custom.breakpoints&lt;/code&gt;), if present&lt;/p&gt;

&lt;p&gt;– Plugin settings, only when &lt;code&gt;theme.json&lt;/code&gt; has no breakpoints&lt;/p&gt;

&lt;p&gt;– Default values if neither is available&lt;/p&gt;

&lt;p&gt;If the theme defines breakpoints, custom fields are disabled to avoid conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS storage path
&lt;/h3&gt;

&lt;p&gt;Generated CSS is stored in &lt;code&gt;wp-content/uploads/4wp-responsive/&lt;/code&gt; by default.&lt;/p&gt;

&lt;p&gt;You can override the storage path with an absolute path if your architecture requires it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;– WordPress 6.0+&lt;/p&gt;

&lt;p&gt;– PHP 7.4+&lt;/p&gt;

&lt;p&gt;– A theme with &lt;code&gt;theme.json&lt;/code&gt; is recommended (for presets).&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;– The plugin respects theme.json presets for spacing and font sizes.&lt;/p&gt;

&lt;p&gt;– Custom values are supported, but only through CSS variables (no inline padding/margin).&lt;/p&gt;

</description>
      <category>4wp</category>
      <category>wordpressplugin</category>
      <category>wordpressdevelopment</category>
      <category>4wpdev</category>
    </item>
    <item>
      <title>4WP-FAQ is not just another FAQ block — it is a smart wrapper around core Accordion.</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Mon, 02 Feb 2026 21:16:44 +0000</pubDate>
      <link>https://dev.to/adovgun/4wp-faq-is-not-just-another-faq-block-it-is-a-smart-wrapper-around-core-accordion-157p</link>
      <guid>https://dev.to/adovgun/4wp-faq-is-not-just-another-faq-block-it-is-a-smart-wrapper-around-core-accordion-157p</guid>
      <description>&lt;p&gt;&lt;strong&gt;4WP-FAQ&lt;/strong&gt; is not another FAQ plugin and not a new block.&lt;br&gt;
It’s a smart wrapper over the WordPress core Accordion, adding structure and meaning to existing content.&lt;/p&gt;

&lt;p&gt;The plugin does not change design, does not break layouts, and does not modify the base block structure. It works entirely on top of native Accordion blocks, adding aggregation logic and Schema.org support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What 4WP-FAQ does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Automatically converts Accordion-based questions and answers into valid FAQPage Schema (JSON-LD)&lt;/p&gt;

&lt;p&gt;Aggregates all FAQ blocks across the site and understands where and in what context each question is used&lt;/p&gt;

&lt;p&gt;Works only with real content — no duplication, no invented questions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;4WP-FAQ creates a single source of truth for real questions and answers:&lt;/p&gt;

&lt;p&gt;Clear visibility into which questions are actually used&lt;/p&gt;

&lt;p&gt;Transparent mapping of questions to pages and contexts&lt;/p&gt;

&lt;p&gt;Easy validation of content reuse and relevance&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Philosophy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Content is the source of truth.&lt;br&gt;
The plugin doesn’t generate knowledge — it records facts, aggregates them, and exposes data for analysis, reuse, and integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built for scale&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reuse common questions via autocomplete&lt;/p&gt;

&lt;p&gt;Real usage and interaction statistics&lt;/p&gt;

&lt;p&gt;Click and expansion tracking with entry-point context&lt;/p&gt;

&lt;p&gt;A knowledge base built on actual user questions, not copies or assumptions&lt;/p&gt;

&lt;p&gt;🔗 Plugin page &amp;amp; documentation:&lt;br&gt;
&lt;a href="https://4wp.dev/plugin/4wp-faq/" rel="noopener noreferrer"&gt;https://4wp.dev/plugin/4wp-faq/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💻 GitHub repository:&lt;br&gt;
&lt;a href="https://github.com/4wpdev/4wp-faq" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wp-faq&lt;/a&gt;&lt;/p&gt;

</description>
      <category>4wpdev</category>
      <category>wordpressplugins</category>
      <category>woordpressdevelopment</category>
    </item>
    <item>
      <title>How to Use MCP with WordPress: Step-by-Step</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Sat, 24 Jan 2026 22:28:00 +0000</pubDate>
      <link>https://dev.to/adovgun/how-to-use-mcp-with-wordpress-step-by-step-1p3i</link>
      <guid>https://dev.to/adovgun/how-to-use-mcp-with-wordpress-step-by-step-1p3i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This guide shows how to connect an MCP client to WordPress via the MCP Adapter and create content using MCP tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress 6.9+&lt;/li&gt;
&lt;li&gt;MCP Adapter installed and activated&lt;/li&gt;
&lt;li&gt;MCP client (Cursor, Claude Desktop, VS Code, etc.)&lt;/li&gt;
&lt;li&gt;Application Password for the WordPress admin user (HTTP transport)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Abilities Provider (Required)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP Adapter exposes only abilities that are explicitly registered and marked meta.mcp.public=true. That means you need a small abilities provider plugin that registers admin‑only tools such as create-post and update-post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/4wpdev/4wp-mcp-abilities" rel="noopener noreferrer"&gt;GitHub → 4wp-mcp-abilities&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: This is not a production plugin. Treat it as reference code only. You can adapt it inside a theme or a temporary internal plugin, but you must review security, permissions, and data exposure before using it in any real environment.&lt;/p&gt;

&lt;p&gt;The repository is intentionally minimal and acts as a reference implementation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Code is poetry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What exactly must be enabled&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Abilities plugin active (admin‑only).&lt;/li&gt;
&lt;li&gt;Abilities registered on wp_abilities_api_init.&lt;/li&gt;
&lt;li&gt;Each ability includes meta.mcp.public = true.&lt;/li&gt;
&lt;li&gt;Admin capability check (recommended: manage_options).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;- Abilities code example (minimal) *&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Register on wp_abilities_api_init
wp_register_ability('your-plugin/create-post', [
  'label' =&amp;gt; 'Create Post',
  'description' =&amp;gt; 'Create a WordPress post',
  'input_schema' =&amp;gt; [
    'type' =&amp;gt; 'object',
    'properties' =&amp;gt; [
      'title' =&amp;gt; ['type' =&amp;gt; 'string'],
      'content' =&amp;gt; ['type' =&amp;gt; 'string'],
      'status' =&amp;gt; ['type' =&amp;gt; 'string', 'default' =&amp;gt; 'draft']
    ],
    'required' =&amp;gt; ['title','content']
  ],
  'execute_callback' =&amp;gt; function($input){
    return wp_insert_post([
      'post_title' =&amp;gt; $input['title'],
      'post_content' =&amp;gt; $input['content'],
      'post_status' =&amp;gt; $input['status'] ?? 'draft'
    ]);
  },
  'permission_callback' =&amp;gt; function(){
    return current_user_can('manage_options');
  },
  'meta' =&amp;gt; [
    'mcp' =&amp;gt; [
      'public' =&amp;gt; true,
      'type' =&amp;gt; 'tool'
    ]
  ]
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Step 1: Install and activate MCP Adapter&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Command (WP-CLI):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd "/path/to/wordpress"&lt;br&gt;
wp plugin install mcp-adapter --activate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can also activate via WP Admin → Plugins → Installed Plugins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Install and activate the Abilities Provider plugin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install the plugin, then activate it in WP Admin. This is required for content actions to appear as MCP tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Configure MCP client (HTTP)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the MCP WordPress remote proxy to connect over HTTP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mcpServers": {
    "wordpress-local": {
      "command": "npx",
      "args": ["-y", "@automattic/mcp-wordpress-remote@latest"],
      "env": {
        "WP_API_URL": "http://your-site.local/wp-json/mcp/mcp-adapter-default-server",
        "WP_API_USERNAME": "admin",
        "WP_API_PASSWORD": "YOUR_APP_PASSWORD"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Verify MCP server in your editor&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open View → Command Palette → MCP: Open MCP Settings&lt;/li&gt;
&lt;li&gt;Confirm the server is listed and has no errors&lt;/li&gt;
&lt;li&gt;Reload the editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step 5: List tools and execute an ability&lt;br&gt;
&lt;strong&gt;Command (MCP tools/list)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}
**Command (Execute ability)**:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"mcp-adapter-execute-ability","arguments":{"ability_name":"your-plugin/create-post","parameters":{"title":"Example","content":"Hello from MCP","status":"draft"}}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Commands&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tools/list — list MCP tools&lt;/li&gt;
&lt;li&gt;tools/call — execute a tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;API&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Default endpoint: /wp-json/mcp/mcp-adapter-default-server&lt;/li&gt;
&lt;li&gt;Transport: HTTP or STDIO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Logic&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;MCP connects to WordPress&lt;/li&gt;
&lt;li&gt;MCP discovers tools (abilities)&lt;/li&gt;
&lt;li&gt;MCP executes tools to read/write content&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Expected Result&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP client can list tools&lt;/li&gt;
&lt;li&gt;MCP can create or update posts through abilities
&lt;strong&gt;FAQ&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;**Q: What should I watch out for?&lt;br&gt;
**Sometimes the agent output lands as one big Classic block. Convert it to blocks once, and the content renders correctly.&lt;/p&gt;

&lt;p&gt;**Q: Can an agent write in blocks?&lt;br&gt;
**Yes. Send Gutenberg block markup (&amp;lt;!-- wp:... --&amp;gt;) in the content so WordPress opens it as blocks instead of Classic.&lt;/p&gt;

&lt;p&gt;**Q: Do I need to write custom code?&lt;br&gt;
**Yes. The MCP Adapter only exposes abilities that are explicitly marked meta.mcp.public=true.&lt;/p&gt;

&lt;p&gt;**Q: Why does WP-CLI fail on Local?&lt;br&gt;
**Local uses a site-specific MySQL socket. If WP-CLI cannot connect, pass the socket via WP_CLI_DB_HOST.&lt;/p&gt;

&lt;p&gt;**Q: Is STDIO safe for production?&lt;br&gt;
**STDIO is intended for local development. Use HTTP with Application Passwords for production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install MCP Adapter, activate an abilities provider plugin, configure your MCP client, and use MCP tools to create or update content. Keep abilities admin‑only and expose only what you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wordpress.com/blog/2025/10/07/mcp/" rel="noopener noreferrer"&gt;WordPress.com: See Your Site Through AI (MCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/WordPress/mcp-adapter" rel="noopener noreferrer"&gt;WordPress MCP Adapter (official repository)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/4wpdev/4wp-mcp-abilities" rel="noopener noreferrer"&gt;4wp-mcp-abilities (public reference repo)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Automattic" rel="noopener noreferrer"&gt;Automattic on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Thanks to the Automattic team for making MCP Adapter possible.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Step 2: Essential Theme Files – Core WordPress Theme Structure</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Wed, 27 Aug 2025 09:01:19 +0000</pubDate>
      <link>https://dev.to/adovgun/step-2-essential-theme-files-core-wordpress-theme-structure-36ig</link>
      <guid>https://dev.to/adovgun/step-2-essential-theme-files-core-wordpress-theme-structure-36ig</guid>
      <description>&lt;h2&gt;
  
  
  Step 2: Essential Theme Files
&lt;/h2&gt;

&lt;p&gt;Create the core files required for a WordPress Full Site Editor theme using the 4WP Starter Theme* as our foundation for Personal Branding and Corporate sites.&lt;/p&gt;

&lt;p&gt;Project Foundation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Base Theme: 4WP Starter Theme* - FSE starter theme built for developers&lt;/li&gt;
&lt;li&gt;Theme Name: 4WP Starter Theme* (customize for your project)&lt;/li&gt;
&lt;li&gt;Use Cases: Personal Branding Site and Corporate websites&lt;/li&gt;
&lt;li&gt;License: MIT License (developer-friendly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essential Files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;style.css - Theme header and metadata (REQUIRED)&lt;/li&gt;
&lt;li&gt;theme.json - FSE configuration and design tokens (REQUIRED)&lt;/li&gt;
&lt;li&gt;templates/index.html - Main FSE template (REQUIRED for theme functionality)&lt;/li&gt;
&lt;li&gt;functions.php - Theme functionality and hooks (recommended for professional themes)&lt;/li&gt;
&lt;li&gt;screenshot.png - Theme preview image (1200x900px, WordPress standard)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Integration:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;a href="https://github.com/4wpdev/4wp-starter-theme" rel="noopener noreferrer"&gt;4WP Starter Theme*&lt;/a&gt; repository (🚧 under development - use your own theme name for production) provides:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Version control: Git-based development workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Collaboration: Issues, pull requests, and community contributions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Documentation: README.md with setup instructions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Licensing: Clear MIT license for commercial and personal use&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updates: Regular improvements and WordPress compatibility updates&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Step:
&lt;/h2&gt;

&lt;p&gt;Configure Composer for dependency management and modern PHP development practices with the 4WP Starter Theme* foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completed Step:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/adovgun/step-1-basic-theme-structure-fse-theme-development-3bpb"&gt;Basic FSE theme structure and WordPress development fundamentals established with proper directory organization and security practices.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;===&lt;br&gt;
Resources &amp;amp; References&lt;br&gt;
Full Course on GitHub: &lt;a href="https://github.com/4wpdev/4wp-course-fse-theme-development" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wp-course-fse-theme-development&lt;/a&gt;&lt;br&gt;
Theme Code on GitHub: &lt;a href="https://github.com/4wpdev/4wp-starter-theme" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wp-starter-theme&lt;/a&gt;&lt;br&gt;
Course for WordPress developers: &lt;a href="https://4wp.dev/course/fse-theme-development/" rel="noopener noreferrer"&gt;https://4wp.dev/course/fse-theme-development/&lt;/a&gt;&lt;br&gt;
Author Fullstack WordPress developer: &lt;a href="https://anatoliy.dovgun.com/" rel="noopener noreferrer"&gt;https://anatoliy.dovgun.com/&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Step 1: Basic Theme Structure – FSE Theme Development</title>
      <dc:creator>Anatoliy Dovgun</dc:creator>
      <pubDate>Tue, 26 Aug 2025 10:40:15 +0000</pubDate>
      <link>https://dev.to/adovgun/step-1-basic-theme-structure-fse-theme-development-3bpb</link>
      <guid>https://dev.to/adovgun/step-1-basic-theme-structure-fse-theme-development-3bpb</guid>
      <description>&lt;h2&gt;
  
  
  Step 1: Basic Theme Structure
&lt;/h2&gt;

&lt;p&gt;Create the fundamental folder structure for a Full Site Editor (FSE) theme following WordPress best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Snippet:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;cd wp-content/themes/starry&lt;br&gt;
mkdir -p {assets/{css,js,images},inc,parts,patterns,styles,templates}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What This Creates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assets/ – Static files (CSS, JS, images)&lt;/li&gt;
&lt;li&gt;inc/ – PHP includes and functions&lt;/li&gt;
&lt;li&gt;parts/ – Template parts (header, footer, etc.)&lt;/li&gt;
&lt;li&gt;patterns/ – Block patterns&lt;/li&gt;
&lt;li&gt;styles/ – Theme style variations&lt;/li&gt;
&lt;li&gt;templates/ – HTML template files&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Step:
&lt;/h2&gt;

&lt;p&gt;After creating the structure, we’ll add the essential theme files (style.css, theme.json, functions.php).&lt;/p&gt;

&lt;p&gt;===&lt;br&gt;
Resources &amp;amp; References&lt;br&gt;
Full Course on GitHub: &lt;a href="https://github.com/4wpdev/4wp-course-fse-theme-development" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wp-course-fse-theme-development&lt;/a&gt;&lt;br&gt;
Theme Code on GitHub: &lt;a href="https://github.com/4wpdev/4wp-starter-theme" rel="noopener noreferrer"&gt;https://github.com/4wpdev/4wp-starter-theme&lt;/a&gt;&lt;br&gt;
Course for WordPress developers: &lt;a href="https://4wp.dev/course/fse-theme-development/" rel="noopener noreferrer"&gt;https://4wp.dev/course/fse-theme-development/&lt;/a&gt;&lt;br&gt;
Author Fullstack WordPress developer: &lt;a href="https://anatoliy.dovgun.com/" rel="noopener noreferrer"&gt;https://anatoliy.dovgun.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>fse</category>
      <category>wordpress</category>
      <category>4wpdev</category>
      <category>gutenberg</category>
    </item>
  </channel>
</rss>
