Skip to main content
Beyond `the_field()`: Clean Data with Sage View Composers

Beyond `the_field()`: Clean Data with Sage View Composers

If you’ve spent any time digging into WordPress theme development, especially with custom post types and custom fields, you’ve probably run into the common…

If you’ve spent any time digging into WordPress theme development, especially with custom post types and custom fields, you’ve probably run into the common dilemma: how do you get your data from the database into your templates without making a mess? The traditional approach often involves sprinkling get_field('my_custom_field') or get_post_meta() directly into your template files. It works, sure, but it quickly leads to logic-heavy templates that are a nightmare to read, debug, and maintain. This is precisely the kind of “spaghetti code” that makes WordPress development feel unmanageable, especially when contrasted with the unmaintainable database bloat and vendor lock-in of page builders like Divi or Elementor.

This is where Sage, with its elegant MVC-like architecture, truly shines. Sage helps me build clean, programmatic WordPress themes that are a joy to work with. It leverages modern tools like Blade for templates, Acorn for dependency injection, and most importantly for today’s topic, View Composers for data management. My goal with Sage is always to keep my Blade templates purely presentation-focused. They should display data, not fetch it.

So, how do we achieve this pristine separation when dealing with custom post type data? Let me walk you through a practical example using a View Composer. Imagine I have a custom post type called “Service” and each service has a custom field (let’s say an Advanced Custom Field) for service_icon_class which stores a Font Awesome class name. I want to display this icon in my single-service.blade.php template.

The less-than-ideal way would be to simply add get_field('service_icon_class') directly into single-service.blade.php. While it functions, it’s mixing data retrieval logic with presentation. A better, Sage-approved way is to use a View Composer.

First, I’d create a new class in app/View/Composers/—let’s call it ServiceComposer.php. This class is responsible for fetching and preparing the data that my single-service view will need.

Here’s what my ServiceComposer.php might look like:

<?php

namespace App\View\Composers;

use Roots\Acorn\View\Composer;

class ServiceComposer extends Composer
{
    /**
     * List of views served by this composer.
     *
     * @var array
     */
    protected static $views = [
        'single-service',
    ];

/**
     * Data to be passed to view before rendering.
     *
     * @return array
     */
    public function with()
    {
        return [
            'serviceIconClass' => $this->serviceIconClass(),
        ];
    }

/**
     * Retrieve the service icon class.
     *
     * @return string|null
     */
    protected function serviceIconClass()
    {
        if (function_exists('get_field')) {
            return get_field('service_icon_class', get_the_ID());
        }
        return null;
    }
}

Next, I need to register this composer. I typically do this in app/setup.php within the add_filters('sage/display_view_data', function (array $data, array $view) { ... }); block, but for larger projects, I might opt for a custom service provider via Acorn. For simplicity, let’s assume setup.php. I would add \App\View\Composers\ServiceComposer::class to the $views array that Acorn processes, or even directly within the register_view_composers array if I’m using an older setup or a specific method.

With the composer set up, my single-service.blade.php template becomes incredibly clean:

@extends('layouts.app')

@section('content')
  <div class="service-detail">
    @if ($serviceIconClass)
      <i class="{{ $serviceIconClass }} fa-3x mb-4"></i>
    @endif
    <h1>{{ get_the_title() }}</h1>
    {!! get_the_content() !!}
@endsection

Notice how serviceIconClass is directly available in the template. The template doesn’t care how that data was fetched; it just trusts that the composer has provided it. This separation means my template is purely about presentation, and my data logic is encapsulated and testable within the ServiceComposer class. If I decide to change how the icon class is stored (maybe from ACF to a custom meta box or even a static mapping), I only need to modify ServiceComposer.php, not touch a single template file.

This approach is a game-changer for maintainability and scalability. It’s a stark contrast to digging through countless get_field() calls scattered across an unmanaged theme. By leveraging Sage’s View Composers and Acorn’s dependency injection, I can craft WordPress themes that are not just functional, but genuinely a pleasure to develop and evolve. It brings a level of organization and predictability that makes modern WordPress development feel less like a hack and more like a well-architected application.