Skip to main content

Command Palette

Search for a command to run...

MVC in WordPress: The Pattern Everyone Learns, Few Truly Apply

Updated
β€’3 min readβ€’View as Markdown
MVC in WordPress: The Pattern Everyone Learns, Few Truly Apply

πŸ“Œ Originally published on 4wp.dev β†’ MVC Pattern in WordPress. Long read on LinkedIn: MVC in WordPress: the pattern everyone learns, few truly apply.

Every WordPress developer has heard of MVC. Very few plugins actually follow it.

https://www.youtube.com/watch?v=C2DnJEmbk4A

WordPress isn't an MVC framework β€” but its building blocks map cleanly onto Model Β· View Β· Controller once you know where to look.

πŸ‘‰ Interactive diagram with all examples: 4wp.dev/architectures/mvc


1. Controller β€” the traffic director

Receives the request, validates it, calls the Model, picks the View. No SQL. No HTML.

In WordPress, Controllers are:

  • hook callbacks (init, save_post)

  • REST API route handlers

  • wp_ajax_* handlers

// Controller β€” REST endpoint
register_rest_route( 'myplugin/v1', '/products', [
    'methods'             => 'GET',
    'permission_callback' => fn() => current_user_can( 'read' ),
    'callback'            => function ( WP_REST_Request $req ) {
        $products = get_all_products( $req->get_param( 'category' ) ); // β†’ Model
        return rest_ensure_response( $products );
    },
] );

➑️ More Controller examples (hooks, AJAX): 4wp.dev/architectures/mvc/#mvc-detail-controller


2. Model β€” the data layer

Owns data and business rules. Returns clean data. Never renders HTML.

In WordPress, Models are:

  • Custom Post Types, taxonomies, registered meta (schema)

  • $wpdb data-access functions/classes

  • Options API settings classes

// Model β€” the only layer that touches options
class PluginSettings {
    private const KEY = 'myplugin_settings';
 
    public static function get( string $key, $default = null ) {
        return ( get_option( self::KEY, [] ) )[ $key ] ?? $default;
    }
}

➑️ CPT schema and $wpdb examples: 4wp.dev/architectures/mvc/#mvc-detail-model


3. View β€” the presentation layer

Renders data it receives. No queries. No business logic.

In WordPress, Views are:

  • theme templates (single.php, archive.php)

  • shortcode output

  • Gutenberg block save() / render templates

<!-- View β€” escape, display, nothing else -->
<article class="product">
    <h1><?php echo esc_html( $product['title'] ); ?></h1>
    <p class="price"><?php echo esc_html( $product['price'] ); ?></p>
</article>

➑️ Shortcode and block examples: 4wp.dev/architectures/mvc/#mvc-detail-view


4. Real plugin: 4WP Weather

4WP Weather (GitHub) is built along the same lines:

Layer In the plugin
Controller REST route forwp-weather/v1/weather β€” the block calls it, PHP does the remote request
Model Provider (OpenWeatherMap), API key in Options API, transient cache
View Gutenberg block with Small / Compact / Advanced templates

Result: the API key never reaches the browser, cache lives in one place, and the block only renders.

Another example of the 4WP approach: 4WP SEO Helper (GitHub).


5. Quick checklist

  • [ ] Controllers are thin: validate β†’ call Model β†’ return/render

  • [ ] Only the Model touches $wpdb, options, meta

  • [ ] Views contain only escaping and markup

  • [ ] Nonces and capability checks live in the Controller

  • [ ] Templates never call WP_Query directly


Go deeper


Written by Anatoliy Dovgun Β· 4wp.dev β€” FSE WordPress development. Follow: GitHub Β· X Β· DEV Β· Medium Β· Hashnode Β· YouTube