I just got off a call with a new client who was at their wit’s end over a “simple” membership plugin another dev had built. Every time they created a new membership level, the whole site ground to a halt. Turns out the original developer came from a Laravel background and had tried to force a full-blown WordPress MVC pattern into the plugin. The result was a tangle of controllers and models fighting WordPress’s own event-driven system.
And I get it. When you move into serious WordPress development, especially coming from another framework, your first instinct is to impose structure. MVC (Model-View-Controller) feels like the “right” way to build things. It’s organized, it separates concerns, and it’s what the big frameworks use. The problem is that WordPress isn’t built that way.
The time I tried it and massively failed
I’ve made this exact mistake myself. Years ago I was building a custom scheduling system for a client and thought I’d be smart about it. I wrote a clean set of controllers to handle booking requests and models to manage appointment data. The code was tidy, self-contained, and completely wrong for WordPress. The flaw showed the moment the client asked for a simple sidebar widget listing “Upcoming Appointments.” My structure had bypassed the WordPress Loop and the standard template hierarchy, so there was no easy way to hook into my own data. My neat little black box was useless to the rest of the site. I ripped it out and started over the right way.
Embracing the WordPress way: hooks and filters
WordPress doesn’t use a router-controller system. It uses an event-driven architecture. The whole lifecycle of a request is a series of events, or “hooks.” You have actions (like init or wp_head) and filters (like the_content). Your job isn’t to build a rigid MVC structure. It’s to hook into that lifecycle at the right moment and get your work done.
Instead of a “controller” that handles a specific URL, you write a class that hooks into init to register a post type and into save_post to process meta fields. It feels different, but it’s how you work with the platform instead of against it.
<?php
// The WordPress Way: A Class that hooks into the system.
class My_Custom_Post_Type {
public function __construct() {
// This is our entry point. Instead of a router, we use an action hook.
add_action( 'init', [ $this, 'register_cpt' ] );
add_action( 'save_post', [ $this, 'save_meta_data' ], 10, 2 );
}
public function register_cpt() {
// ... code to register custom post type ...
}
public function save_meta_data( $post_id, $post ) {
// ... code to handle saving custom fields ...
// This only runs when a post is saved. Simple. Effective.
}
}
// And to kick it all off:
new My_Custom_Post_Type();
So what’s the right approach?
There’s a real debate about architectural patterns in WordPress. People have explored alternatives like the Action-Domain-Responder (ADR) pattern, which I first read about in an article over at carlalexander.ca. But for almost every project, the answer is simpler: don’t force it. The only place a true WordPress MVC pattern earns its keep is a headless setup, where WordPress is just a data source behind the REST API. There your front-end JavaScript app can be MVC, MVVM, or whatever you like. The PHP side should stick to the script.
This stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
The takeaway isn’t that MVC is bad. It’s that context matters. A good developer picks the right tool for the job, and in WordPress that means actions and filters.