Skip to main content
Clean CPT Data Delivery: Using a Sage View Composer for Your Project Archive

Clean CPT Data Delivery: Using a Sage View Composer for Your Project Archive

I remember the early days of WordPress theme development, constantly battling global variables and embedding `WP_Query` calls directly into template files.

I remember the early days of WordPress theme development, constantly battling global variables and embedding WP_Query calls directly into template files. It felt like wrestling a bear while trying to make things look pretty. Discovering Sage transformed my approach, showing me how to build custom, maintainable sites with an architecture akin to a modern MVC framework. It’s a world away from the database bloat and convoluted HTML output of page builders like Divi or Elementor, which often lead to unmaintainable codebases. Sage provides a robust, programmatic foundation that prioritizes developer experience and clean separation of concerns.

One of Sage’s most potent features for achieving this clean architecture is the View Composer. If you’ve ever struggled to pass specific data to a WordPress template without cluttering it with query logic, View Composers are the elegant solution. They allow you to prepare all necessary data before your Blade template even sees it, ensuring your presentation layer remains purely focused on rendering content.

Imagine you have a custom post type (CPT) for “Projects” and you need to display an archive of these projects on a dedicated page. The traditional WordPress way might involve a new WP_Query() directly within your resources/views/page-projects.blade.php file. The Sage way is far more refined: we’ll create a View Composer specifically for that template, or a partial it includes.

First, let’s create the View Composer class itself. I organize these in app/View/Composers. We’ll create app/View/Composers/ProjectArchiveComposer.php:

// app/View/Composers/ProjectArchiveComposer.php
namespace App\View\Composers;

use Roots\Acorn\View\Composer;
use WP_Query;

class ProjectArchiveComposer extends Composer
{
    /**
     * List of views served by this composer.
     *
     * @var array
     */
    protected static $views = [
        'partials.content-page-projects', // Target a specific partial
        'page-projects', // Or target the main page template
    ];

/**
     * Data to be passed to view before rendering.
     *
     * @param  \Illuminate\View\View  $view
     * @return array
     */
    public function with(\Illuminate\View\View $view)
    {
        $projects_query = new WP_Query([
            'post_type' => 'project',
            'posts_per_page' => -1, // Get all projects
            'post_status' => 'publish',
            'orderby' => 'menu_order',
            'order' => 'ASC',
        ]);

$projectData = collect($projects_query->posts)->map(function ($post) {
            return (object) [
                'id' => $post->ID,
                'title' => get_the_title($post),
                'permalink' => get_permalink($post),
                'thumbnail' => get_the_post_thumbnail_url($post, 'medium'),
                // Add any other specific data you need from the post object
            ];
        })->all(); // Convert back to a plain array

return [
            'projects' => $projectData,
        ];
    }
}

In this composer, the static $views property automatically registers our ProjectArchiveComposer to apply to the specified Blade templates. The with method is where the data preparation happens. Here, I’m fetching all ‘project’ CPTs using WP_Query. Crucially, I then use Laravel’s collect() helper, made available by Acorn, to map each WP_Post object into a much cleaner, custom object containing only the id, title, permalink, and thumbnail that my template actually needs. This avoids passing the entire, often bloated, WP_Post object directly to the view.

Finally, in your resources/views/partials/content-page-projects.blade.php (or page-projects.blade.php, depending on your target), your code becomes refreshingly simple:

<div class="projects-grid">
    @foreach ($projects as $project)
        <article class="project-item">
            @if ($project->thumbnail)
                <a href="{{ $project->permalink }}">
                    <img src="{{ $project->thumbnail }}" alt="{{ $project->title }}">
                </a>
            @endif
            <h2><a href="{{ $project->permalink }}">{{ $project->title }}</a></h2>
        </article>
    @endforeach

That’s all it takes. Your Blade template is now completely devoid of WordPress-specific querying logic. It simply receives a $projects variable, pre-prepared and optimized, and renders it. This approach significantly boosts readability, enhances component reusability, and makes your codebase much easier to test and maintain. This is the essence of Sage: leveraging modern PHP standards and robust dependency injection through Acorn to bring order and sophistication to WordPress development, offering a powerful alternative to the unmaintainable complexities of page builders.