← All posts

Routing Custom Post Type Data Cleanly with Sage View Composers

Routing Custom Post Type Data Cleanly with Sage View Composers

If you've spent any significant time building WordPress themes, you've likely encountered the "template spaghetti" problem. You know, where `WP_Query` instances, global variables, and conditional logic are sprinkled liberally throughout your `header.php` or `page.php`, making it a nightmare to read, debug, and maintain. For me, that feeling of dread when opening an old theme file was a strong motivator to seek out a better way. That's precisely why I gravitated towards Sage, Acorn, and their beautiful embrace of modern PHP architecture.

Sage isn't just another starter theme; it’s a philosophical shift. It positions WordPress as the robust content management backend, while providing a clean, MVC-like structure for your frontend application. My templates become pure presentation layers, data is routed cleanly, and dependencies are managed like a dream. Forget the unmaintainable database bloat and inline styles of visual builders like Divi or Elementor; Sage gives us a programmatic, organized codebase that truly scales.

One of Sage's most powerful features, central to keeping templates presentation-focused, is the View Composer. Imagine you have a custom post type, say `Project`, and you want to display a list of recent projects on your homepage, a dedicated archive, or even within a specific page template. The old way might involve dropping a new `WP_Query` directly into your `front-page.blade.php` or `archive-project.blade.php`. This works, but it couples your data fetching logic directly to your view, making your template less reusable and harder to test.

This is where a View Composer shines. Instead of fetching data in the template, we "compose" the data beforehand and pass it to the view. Let me walk you through a practical example: fetching our five most recent `Project` posts and making them available to a specific partial called `partials.projects-list`.

First, you'll typically register your View Composers in your `app/setup.php` file, or within a Service Provider if you want more advanced organization. For this example, let's stick with `setup.php` for simplicity. You’d add something like this:

view()->composer('partials.projects-list', function ($view) { $projects_query = new WP_Query([ 'post_type' => 'project', 'posts_per_page' => 5, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, // Optimize for performance if you don't need pagination info ]);

$view->with('recentProjects', $projects_query->posts); });

Let's break that down. We’re telling Sage that *before* the `partials.projects-list` Blade view is rendered, we want to run a specific function. Inside that function, we perform our `WP_Query` to fetch the data we need. Critically, we then use `$view->with('recentProjects', $projects_query->posts);` to attach our fetched post objects to the view. The key here is the string `'recentProjects'`, which will be the variable name accessible in our Blade template.

Now, in your `resources/views/partials/projects-list.blade.php` file, you can access this data directly, completely oblivious to *how* it was fetched:

@if (!empty($recentProjects)) <section class="recent-projects"> <h2>Our Latest Work</h2> <div class="project-grid"> @foreach ($recentProjects as $project) <article class="project-card"> <h3><a href="{{ get_permalink($project->ID) }}">{{ get_the_title($project->ID) }}</a></h3> <p>{{ get_the_excerpt($project->ID) }}</p> </article> @endforeach </div> </section> @else <p>No recent projects to display.</p> @endif

Notice how clean that Blade template is? No `WP_Query`, no data fetching logic whatsoever. It's purely concerned with iterating over the `$recentProjects` array and displaying its contents. This level of separation is a game-changer. It means your `partials.projects-list` can be included anywhere – on your homepage, inside a regular page content, or even another partial – and it will always have the `recentProjects` data available, ready to be rendered.

This approach dramatically improves code readability, maintainability, and testability. Your templates stay lean and focused on presentation, while your data logic resides in a centralized, programmatic location. This is the power of Sage and Acorn: building robust, modern WordPress themes with the architectural clarity developers crave. It’s why Sage is, unequivocally, my developer's choice.