If you’ve spent any serious time building WordPress themes, you know the struggle: templates riddled with WP_Query calls, global $post spaghetti, and get_field() sprinkled everywhere. It quickly turns into an unmaintainable mess, making it hard to debug, test, or even understand what data a given template is actually supposed to display. This is precisely why I gravitated towards Sage, and specifically why its View Composer pattern, powered by Acorn, has become an indispensable tool in my workflow. Forget the database bloat and unmanageable interfaces of Divi or Elementor; Sage gives us a clean, programmatic way to build robust sites.
Sage aims for an MVC-like architecture, encouraging a clear separation of concerns. Your Blade templates should be purely presentation. They display data, they don’t fetch it. That’s where View Composers shine. A View Composer is essentially a class that binds data to a specific Blade view or set of views before they are rendered. This means your data fetching and manipulation logic lives in a dedicated, testable class, completely separate from your markup.
Let’s walk through a concrete example. Imagine you have a custom post type called ‘Project’ and you want to display a list of these projects on a dedicated “Our Projects” page. Without a View Composer, you might be tempted to drop a WP_Query directly into your resources/views/pages/projects.blade.php file. While it works, it immediately violates the principle of separation of concerns. The template becomes responsible for both querying the database and presenting the results.
Here’s how we do it the Sage way, using a View Composer:
First, let’s create our composer class. You can place this in app/View/Composers/ProjectComposer.php:
namespace App\View\Composers;
use Roots\Acorn\View\Composer;
use WP_Query;
class ProjectComposer extends Composer
{
/**
* List of views served by this composer.
*
* @var array
*/
protected static $views = [
'pages.projects', // This refers to resources/views/pages/projects.blade.php
];
/**
* Data to be passed to view before rendering.
*
* @return array
*/
public function with()
{
return [
'projects' => $this->getProjects(),
];
}
/**
* Get a list of published projects.
*
* @return array
*/
protected function getProjects()
{
$args = [
'post_type' => 'project',
'posts_per_page' => -1, // Get all projects
'post_status' => 'publish',
'orderby' => 'title',
'order' => 'ASC',
];
$query = new WP_Query($args);
return $query->posts; // Returns an array of WP_Post objects
}
}
In this ProjectComposer class, we define which views it serves (pages.projects). The with() method is where we define the data we want to pass to our view. Here, it calls a protected getProjects() method, which encapsulates all the WP_Query logic. This makes the data retrieval logic reusable and isolated.
Next, we need to tell Sage (or rather, Acorn) to use this composer for our pages.projects view. The most common way is to register it in your config/view.php file, within the composers array:
// config/view.php
<?php
return [
// ... other config ...
'composers' => [
'pages.projects' => \App\View\Composers\ProjectComposer::class,
],
// ... other config ...
];
Now, your resources/views/pages/projects.blade.php becomes incredibly clean and focused purely on presentation:
@extends('layouts.app')
@section('content')
<div class="container mx-auto my-8">
<h1 class="text-3xl font-bold mb-6">Our Projects</h1>
@if (!empty($projects))
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach ($projects as $project)
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-2">{{ $project->post_title }}</h2>
<p class="text-gray-600 mb-4">{{ \wp_trim_words($project->post_content, 30) }}</p>
<a href="{{ \get_permalink($project->ID) }}" class="text-blue-500 hover:underline">View Project</a>
@endforeach
@else
<p class="text-gray-700">No projects found.</p>
@endif
@endsection
Notice how $projects is directly available in the template? No global $post, no WP_Query! The template simply iterates over the $projects variable, assuming it has been provided. This is the power of View Composers. Your template designers can focus purely on HTML and CSS, while your developers handle the data logic.
This approach not only leads to vastly more organized and readable code but also significantly improves maintainability and testability. You can easily unit test your ProjectComposer to ensure it fetches the correct data without needing to render the view or even touch the WordPress database directly (with proper mocking). This kind of organized, programmatic codebase is a world away from the often-unmanageable database bloat and convoluted markup generated by page builders, proving Sage truly is the developer’s choice for modern WordPress.