📌 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.
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:
// 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)
-
$wpdbdata-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
- 🔌 4WP plugins
Written by Anatoliy Dovgun · 4wp.dev — FSE WordPress development.
Follow: GitHub · X · DEV · Medium · Hashnode · YouTube
Top comments (0)