
A Hyva view model is a plain PHP class implementing Magento’s ArgumentInterface that a template pulls in with $viewModels->require(), with no layout XML wiring. It replaces logic that Luma kept in block classes and helpers, keeps templates thin, and can add cache tags to the page through IdentityInterface, something core Magento view models cannot do.
If your team is moving from Luma to Hyva, view models are the first pattern to get right. They decide where data access lives, how templates stay readable, and whether pages are invalidated correctly in the full page cache. This guide covers what Magento view models are, what Hyva adds, the built-in view models worth knowing, how cache tags work, and the mistakes that cause stale pages or hard-to-test code.
What a view model is in Magento
Adobe’s view model documentation describes a view model as “an abstraction of the view exposing public properties and commands.” The goal is to move business logic out of block classes so that it is easier to maintain, test, and reuse. View models have been available since Magento 2.2, and Adobe recommends them over helpers in templates.
In core Magento, a view model must implement \Magento\Framework\View\Element\Block\ArgumentInterface. You attach it to a block in layout XML and read it in the template:
<block name="examplecorp.new.viewmodel" template="ExampleCorp_Catalog::example.phtml">
<arguments>
<argument name="view_model" xsi:type="object">ExampleCorp\Catalog\ViewModel\MyNewViewModel</argument>
</arguments>
</block>
<?php
/** @var \ExampleCorp\Catalog\ViewModel\MyNewViewModel $viewModel */
$viewModel = $block->getViewModel();
?>
<h1><?= $escaper->escapeHtml($viewModel->getTitle()) ?></h1>
The view model class itself declares only the dependencies it needs in its constructor. That is the main design win over custom block classes, which inherit a large Context object and every dependency that comes with it.
What Hyva adds: the ViewModelRegistry
Hyva keeps Magento’s view models and removes the layout XML step. The Hyva view model documentation explains that a $viewModels variable is available in every template, next to $block and $escaper. It comes from the Hyva theme module, and you no longer need to declare view models in XML.
<?php
use Hyva\Theme\Model\ViewModelRegistry;
use Hyva\Theme\ViewModel\CurrentProduct;
use Hyva\Theme\ViewModel\SvgIcons;
/** @var ViewModelRegistry $viewModels */
$currentProduct = $viewModels->require(CurrentProduct::class);
$icons = $viewModels->require(SvgIcons::class);
?>
require() returns any class that implements ArgumentInterface. Looking at the registry’s source in the Hyva theme module, it resolves the class through Magento’s object manager with get(), so each view model is a shared instance for the request. It throws an InvalidViewModelClass exception if the class does not exist or does not implement ArgumentInterface, which catches typos early.
This is a big part of why Hyva templates feel simpler to work in. A frontend developer can open a template, see exactly which view models it uses at the top of the file, and add one without touching layout XML.
Luma blocks and helpers vs Hyva view models
| Concern | Luma pattern | Hyva pattern |
|---|---|---|
| Where data logic lives | Custom block class or helper | View model class |
| How the template gets it | $block->getSomething() or $this->helper() |
$viewModels->require(Class::class) |
| Layout XML needed | Yes, for block class or view model argument | No |
| Constructor dependencies | Inherits block Context plus extras |
Only what the class declares |
| Reuse across templates | Tied to a block type | Any template can require it |
| Unit testing | Harder, block context to mock | Plain class, simple to test |
| Adding FPC cache tags | Block getIdentities() |
View model IdentityInterface, collected by Hyva |
Blocks still have a place. A block is the right tool when you need to control rendering itself: block HTML caching with its own cache key, child block rendering, or a template that changes based on layout arguments. For data and formatting, a view model is the cleaner choice.
Built-in Hyva view models worth knowing
The Hyva theme module ships a large set of view models. The ViewModel directory on GitHub lists them, including BlockCache, CurrentCategory, CurrentProduct, Customer, CustomerSectionData, HeroiconsOutline, HeroiconsSolid, Image, Modal, Navigation, ProductAttributes, ProductList, ProductPage, ProductPrice, ReCaptcha, StoreConfig, SvgIcons, Slider, and Wishlist.
A few you will use constantly:
- StoreConfig.
getStoreConfig($path)wraps Magento’s scope config lookup at store scope. It replaces the common Luma habit of injectingScopeConfigInterfaceinto a block just to read one setting. - ProductPage. Exposes the current product and helpers such as
getShortDescription(),getAddToCartUrl(),getImage(), price formatting, and currency data. It implements IdentityInterface, so the product’s cache tags reach the page. - CurrentCategory. Holds the category in context, with
get(),fetch()(which can return null), andexists(). It also implements IdentityInterface. - SvgIcons and the Heroicons view models. Render inline SVG icons from a template. Hyva’s SvgIcons documentation describes calling
renderHtml('icon-name', $width, $height)or a camel-cased method such aschevronDownHtml(), with optional class names and attributes. - CustomerSectionData. Supplies default values for private content sections on the client, which matters for cart and customer data on cached pages.
Before writing a custom view model, check whether one of these already does the job. Duplicating ProductPage logic in a custom class is a common source of inconsistent prices and image URLs across templates.
View models and full page cache tags
This is the part most tutorials skip, and it decides whether your pages update when data changes.
Magento’s full page cache tags each cached page with identifiers such as product and category IDs. When a product is saved, Magento purges pages carrying that product’s tag. Blocks contribute tags through getIdentities(). According to Hyva’s view model cache tags documentation, “Standard Magento provides no mechanism for view models to add cache tags.”
Hyva adds that mechanism. A view model that implements Magento\Framework\DataObject\IdentityInterface has its identities collected and added to the X-Magento-Tags response header and to block HTML cache records. Three details matter:
- Collection is lazy. Hyva calls
getIdentities()when the response is finalized, not when the view model is required. The documentation explains that some view models, such as Navigation, build up their tags while the page renders. - Developer mode shows the tags. Hyva injects a
ViewModelCacheTagsBlockbefore the end of the body. In developer mode it prints an HTML comment listing the collected tags, which is the fastest way to check that a custom view model is tagging correctly. - ESI blocks need the block passed in. The optional second argument to
require()exists for view models with cache tags used inside ESI blocks, which in the default theme are the desktop and mobile menu templates. In that case, call$viewModels->require(Navigation::class, $block). Elsewhere, leave it out.
If you use Varnish, these tags are what Varnish purges on. Our guide to full page cache and Varnish on Hyva explains how tags, ESI, and hole-punching fit together.
Writing a custom view model: a short recipe
Moving logic out of a Luma block usually follows the same steps.
1. Identify the logic. Find the methods the template calls on $block that load data or format values. Leave rendering concerns, such as child HTML, in the block.
2. Create the class. Put it in your module’s ViewModel namespace, implement ArgumentInterface, and inject only what it needs.
<?php
namespace ExampleCorp\Catalog\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Framework\DataObject\IdentityInterface;
class ShippingPromise implements ArgumentInterface, IdentityInterface
{
public function __construct(
private \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
) {}
public function getCutoffHour(): int
{
return (int) $this->scopeConfig->getValue('examplecorp/shipping/cutoff_hour', 'store');
}
public function getIdentities(): array
{
return ['examplecorp_shipping_promise'];
}
}
3. Require it in the template. Add the use statement and a $viewModels->require() call at the top of the template, then replace $block calls.
4. Add identities if the output depends on data that can change. If a value comes from an entity that is saved in the admin, return that entity’s cache tags so pages are purged when it changes. Config values are usually handled by a config cache flush, but custom entities need tags.
5. Write a unit test. Because a view model is a plain class, you can test it with a mocked config or repository and no block context.
The same recipe applies when adapting third-party extensions. Many Luma extensions put logic in blocks that also render Knockout components. Our guide to adapting Magento extensions to Hyva covers how compatibility modules usually split that logic into view models and Alpine.js templates.
Common mistakes
Using the object manager in a template. Calling ObjectManager::getInstance() in a phtml file hides dependencies and bypasses DI configuration. $viewModels->require() gives you the same convenience in a supported way.
Keeping state in a view model. Because the registry returns a shared instance per request, a view model that stores per-item state in a property will carry it into the next template that requires it. Pass the item into the method instead, for example getBadge($product), rather than setting the product on the view model first.
Forgetting IdentityInterface on data-bearing view models. If a view model renders data from an entity, such as a product badge, a store locator entry, or a custom banner, and does not return tags, the cached page will not be purged when that entity changes. That shows up as “the admin change did not appear” reports.
Assuming identities are always present. In the Hyva source, ProductPage::getIdentities() returns an empty array if the product has not been loaded during that request. If a template relies on the view model for tags, make sure it actually reads the product.
Passing $block everywhere. The second argument to require() is meant for tagged view models inside ESI blocks. Passing it by habit adds noise and confuses the next developer about why it is there.
Heavy logic in the template. A view model does not help if the template still loops over collections and computes values. Keep templates to output and simple conditions.
How this changes team habits
Developers coming from Luma are used to reaching for a custom block or a helper. On Hyva, the default answer becomes a view model, and the template becomes the single place to read what a component needs. Teams that adopt this early find code review simpler, because the require() calls at the top of each template list every dependency. Our article on retraining a Magento frontend team from Knockout to Alpine covers the JavaScript side of the same transition, and what Hyva theme development involves covers the full build process.
Bemeir is a Brooklyn ecommerce agency. Our Hyva development services and Magento development services cover theme builds, extension compatibility, and performance work on Magento Open Source and Adobe Commerce. We also offer Shopify development, Shopware development, and BigCommerce development. Learn more about Bemeir, see our technology partners, or visit the Bemeir home page.
FAQ
What is the difference between a block and a view model in Magento?
A block controls rendering: its template, child blocks, and block HTML caching. A view model is a plain class implementing ArgumentInterface that supplies data and formatting to a template. Adobe recommends view models over helpers and over adding logic to custom block classes.
How do I use a view model in a Hyva template?
Call $viewModels->require(YourViewModel::class) at the top of the template. The $viewModels registry is available in every Hyva template, so no layout XML argument is needed. The class must implement Magento\Framework\View\Element\Block\ArgumentInterface.
Can Hyva view models add full page cache tags?
Yes. Core Magento has no mechanism for view models to add cache tags, but Hyva collects identities from any view model that implements IdentityInterface and adds them to the X-Magento-Tags header and block HTML cache records.
When should I pass $block as the second argument to require()?
Only when a view model with cache tags is used inside an ESI block. In the default Hyva theme, that applies to the desktop and mobile menu templates. In other templates, call require() with the class name alone.
Are Hyva view models shared instances?
Yes. The registry resolves view models through Magento’s object manager get() method, which returns a shared instance for the request. Avoid storing per-item state in view model properties; pass the item into the method instead.





