Magento 2 view models: ArgumentInterface, layout XML, and when to use them
Magento 2 view models let you inject logic into templates without overriding blocks. This guide covers ArgumentInterface, layout XML (name="view_model"), getViewModel(), and when a view model beats a block.
Photo by olia danilevich from Pexels
A Magento 2 view model is a plain PHP class you attach to a block through layout XML. It holds the methods your .phtml needs, without stuffing business logic into the block class or rewriting core blocks.
You declare it with a layout argument named view_model and type object. The class must implement \Magento\Framework\View\Element\Block\ArgumentInterface. In the template you pull it with $block->getViewModel() or $block->getData('view_model').
Use a view model when you want reusable, testable template helpers and you do not need Magento 1-style block backwards compatibility. Prefer a custom block (or preference) only when you must change block lifecycle behavior, cache keys, or something the template layer cannot own.
Below: layout examples (new blocks and referenceBlock), a minimal ArgumentInterface class, DI compile, template usage, and a short view model vs block checklist.
What is a Magento 2 view model?
A view model is an abstraction of the view: public methods (and optional constructor dependencies) that templates call. It moves features and business logic out of block classes into classes that are easier to maintain, test, and reuse.
Adobe documents this pattern in the extension development guide on view models.
When to use view models (vs overriding a block)
Use a view model when:
- You need extra data or helpers in a template
- You want to avoid class preferences / block rewrites
- The logic is presentation-adjacent (labels, flags, small calculations), not core domain services you would put in an API layer anyway
- Backwards compatibility with old Magento block patterns is not required
Skip view models (or pair them carefully) when you must change how the block itself caches, names itself, or participates in layout generation. That still belongs on the block.
Layout XML: argument name="view_model" xsi:type="object"
Attach a view model to a block you own:
<block name="demoapp.content.viewmodel" template="DemoApp_Catalog::demo.phtml">
<arguments>
<argument name="view_model" xsi:type="object">DemoApp\Sales\ViewModel\ExampleViewModel</argument>
</arguments>
</block>
You can also attach the same pattern to existing Magento or third-party blocks with referenceBlock. Pass the fully qualified class name without a leading backslash.
Example: add a view model to invoice_totals from sales_email_order_invoice_items.xml:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"
label="Email Invoice Items List"
design_abstraction="custom">
<body>
<referenceBlock name="invoice_totals">
<arguments>
<argument name="view_model" xsi:type="object">DemoApp\Sales\ViewModel\ExampleViewModel</argument>
</arguments>
</referenceBlock>
</body>
</page>
The view model class and ArgumentInterface
Your view model can expose as many methods as you need. The hard requirement: implement \Magento\Framework\View\Element\Block\ArgumentInterface. Constructor injection works like any other Magento class.
Minimal example:
namespace DemoApp\Sales\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
class ExampleViewModel implements ArgumentInterface
{
public function getSomeDemoText()
{
return __('My demo text');
}
}
Compile DI before using it in templates
After you add the class and layout argument, compile DI so generated code and interceptors stay in sync:
bin/magento setup:di:compile
# short form
bin/magento s:d:c
Template usage: getViewModel() and getData('view_model')
Both of these resolve the same argument:
<?php
/** @var $viewModel \DemoApp\Sales\ViewModel\ExampleViewModel */
$viewModel = $block->getViewModel();
$viewModelText = $block->getData('view_model');
?>
<p><?= $block->escapeHtml($viewModel->getSomeDemoText()); ?></p>
<p><?= $block->escapeHtml($viewModelText->getSomeDemoText()); ?></p>
Practical example
View models shine when you customize a template: conditional copy, customer-group checks, payment-method notes on an invoice, small flags that used to live in an overridden block. Keep domain-heavy work in services; keep the view model thin and template-focused.
Magento 2 view model vs block: quick difference
| View model | Block | |
|---|---|---|
| Role | Methods/data for the template | Layout participant, template host, cache/name/lifecycle |
| Wiring | Layout view_model object argument | Class in layout, preferences, phtml via $block |
| Interface | ArgumentInterface | Extends Magento block classes |
| Prefer when | Add logic without rewrite | Change block behavior itself |
If the question is "do I need a preference on this block just to print one extra string?", start with a view model.
FAQ
Frequently asked questions
What is ArgumentInterface for in Magento 2 view models?
ArgumentInterface marks the class as a valid layout argument object Magento can inject into a block. Without it, the view_model argument should not be treated as a proper view model.
How do I set layout argument name="view_model"?
In the block's layout XML (or a referenceBlock), add <argument name="view_model" xsi:type="object">Vendor\Module\ViewModel\YourClass</argument>. No leading \.
How do I call $block->getViewModel() in a template?
After DI compile, in the .phtml: $viewModel = $block->getViewModel(); then call your methods. $block->getData('view_model') is equivalent if the argument name is view_model.
Magento 2 view model vs block: which should I choose?
Choose a view model to extend template behavior without overriding blocks. Choose (or extend) a block when you need block-level behavior: caching, identity, structural layout changes, or logic that is not "template helper" scope.
Where is the official Magento 2 view models documentation?
Adobe DevDocs: View models.
Wrap-up
View models keep templates flexible and blocks thinner: ArgumentInterface class, view_model layout argument, compile DI, call getViewModel() in the .phtml. Use them when you would otherwise rewrite a block for template-only needs.
If you are stuck on a Magento 2 / Adobe Commerce customization, I take on freelance Magento and Laravel work: let's work together on LinkedIn or start from denisveg.dev.
Related on this site: