Creating Frontend Templates: Professional MVC Views in WordPress
Master frontend templates in WordPress to build clean, MVC-compliant views for your plugin. Learn to pass data from controllers to the display layer.
Previously in this course, we explored Mastering the Plugin Template Hierarchy for WordPress, which taught us how to locate files within themes. Now, we're moving from theory to implementation: we will build the actual view layer for our Knowledge Base plugin, ensuring our display logic remains strictly separated from our business logic.
In a true MVC architecture, the controller handles the request, fetches the data via the model, and then "hands off" that data to a view (a template file) to be rendered. This keeps your code maintainable and allows theme developers to override your output easily.
The MVC View Philosophy
In WordPress, "views" are simply PHP files that contain HTML mixed with minimal PHP logic. We never perform database queries inside our templates. Instead, we prepare all necessary data in our ArticleController and pass it to the view.
To maintain consistency, we will create a views/ directory in our plugin structure. This directory will house two types of templates:
- Single Templates: For displaying one specific Knowledge Base article.
- Archive Templates: For displaying a list of all articles.
Worked Example: Rendering a Single Article
First, let's create a template file at views/single-kb-article.php. This file will receive an $article object from our controller.
PHP<?php #6A9955">// views/single-kb-article.php #6A9955">// We expect $article to be passed as an object ?> <article class="kb-article"> <h1><?php echo esc_html($article->post_title); ?></h1> <div class="kb-content"> <?php echo apply_filters('the_content', $article->post_content); ?> </div> </article>
Now, we need our controller to render this. In your ArticleController, you'll create a method that loads this view while ensuring the data is available in the local scope.
PHP#6A9955">// Inside ArticleController.php public function render_single_view($post_id) { $article = get_post($post_id); #6A9955">// In a real app, use your Model layer #6A9955">// Pass $article to the view include plugin_dir_path(__FILE__) . '../views/single-kb-article.php'; }
Creating the Archive Template
For our archive, we need to iterate over a collection of articles. Create views/archive-kb-articles.php:
PHP<?php #6A9955">// views/archive-kb-articles.php #6A9955">// Expects $articles to be an array of WP_Post objects ?> <div class="kb-archive"> <?php if (!empty($articles)) : ?> <ul> <?php foreach ($articles as $article) : ?> <li> <a href="<?php echo get_permalink($article->ID); ?>"> <?php echo esc_html($article->post_title); ?> </a> </li> <?php endforeach; ?> </ul> <?php else : ?> <p>No articles found.</p> <?php endif; ?> </div>
Passing Data from Controller to View
The key to clean MVC is the "data injection" pattern. Your controller should be the only place where data is prepared.
When your controller calls the template, it should look like this:
PHPpublic function render_archive() { #6A9955">// 1. Fetch data from Model $articles = $this->model->get_all_articles(); #6A9955">// 2. Buffer the output to prevent premature rendering ob_start(); include plugin_dir_path(__FILE__) . '../views/archive-kb-articles.php'; return ob_get_clean(); }
By using ob_start() and ob_get_clean(), we capture the HTML generated by the template into a variable. This is crucial for shortcodes or filters where you need to return a string rather than echoing it immediately.
Hands-on Exercise
- Create a
views/folder in your plugin directory. - Create
single-kb-article.phpandarchive-kb-articles.phpusing the snippets above. - In your
ArticleController, create a method that fetches the current post and includes the single-article template. - Use the
template_includefilter to force WordPress to load your plugin's template when a user views a single Knowledge Base article. (Hint: Use the logic covered in our previous lesson on template hierarchy).
Common Pitfalls
- Direct Database Queries in Views: Avoid calling
$wpdborget_posts()inside your template files. If you find yourself doing this, move that logic to your Model Layer and pass the results to the view. - Security Neglect: Never echo variables directly without escaping. Always use
esc_html(),esc_attr(), orwp_kses_post()for content. - Variable Scope: When using
include, the template inherits the scope of the controller method. Be careful not to overwrite variables accidentally.
Recap
We've successfully decoupled our display layer from our logic. By creating specific view files and utilizing output buffering in our controllers, we've built a professional-grade template system. This structure ensures that your code remains clean, secure, and easy to extend as we continue building our Knowledge Base plugin.
Up next: We will take these views and make them accessible anywhere on your site by Building Shortcodes.
Work with me

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard โ by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.

Headless WordPress + Next.js Frontend Development
Keep WordPress for content, get a lightning-fast Next.js frontend. The best of both worlds โ familiar editing, modern speed.