Skip to content

Components

Build a component with a PHP class, a PHTML template, and a Magento layout block. Magewire resolves the class from the block's magewire argument and passes it to the template as $magewire.

Create your first component

1. Add a component to your Magento module

This example adds a counter to the CMS home page. Start in an existing, enabled Vendor_Module module. The three files are:

Vendor/Module/
├── Magewire/Counter.php
└── view/frontend/
    ├── layout/cms_index_index.xml
    └── templates/magewire/counter.phtml
Magewire/Counter.php
<?php

declare(strict_types=1);

namespace Vendor\Module\Magewire;

use Magewirephp\Magewire\Component;

class Counter extends Component
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
}

The public property is browser-visible state. The public method is an action the browser can call; validate and authorize actions that read or change business data.

2. Render it with PHTML

Magewire makes the component available to its template as $magewire:

view/frontend/templates/magewire/counter.phtml
<div data-testid="magewire-counter">
    <span><?= $escaper->escapeHtml(__('Count: %1', $magewire->count)) ?></span>

    <button type="button" wire:click="increment">
        <?= $escaper->escapeHtml(__('Increase')) ?>
    </button>
</div>

Keep one root element. Magewire uses it to attach the component snapshot and to update the rendered HTML. Escape text and attributes using Magento's $escaper.

3. Place it with layout XML

The cms_index_index handle places the counter on the CMS home page:

view/frontend/layout/cms_index_index.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">
    <body>
        <referenceContainer name="content">
            <block name="vendor.module.counter"
                   template="Vendor_Module::magewire/counter.phtml">
                <arguments>
                    <argument name="magewire" xsi:type="object" shared="false">
                        Vendor\Module\Magewire\Counter
                    </argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>

The magewire argument binds the PHP class to this block. shared="false" gives the block its own component instance, which also makes the pattern safe when you place the same class more than once on a page.

4. Try it

bin/magento cache:clean layout block_html full_page

Open the home page and click Increase. Magewire calls increment() on the server and updates the count without a full page reload. If the block does not appear, confirm the module is enabled and that this page uses cms_index_index.

Pass initial values from layout

Prefix an argument with magewire:mount: to pass a named value to mount(). This makes the same class reusable on different pages:

view/frontend/layout/catalog_category_view.xml
<block name="vendor.module.category-products"
       template="Vendor_Module::magewire/category-products.phtml">
    <arguments>
        <argument name="magewire" xsi:type="object" shared="false">
            Vendor\Module\Magewire\CategoryProducts
        </argument>
        <argument name="magewire:mount:category-id" xsi:type="number">10</argument>
        <argument name="magewire:mount:page-size" xsi:type="number">20</argument>
    </arguments>
</block>
Magewire/CategoryProducts.php
<?php

namespace Vendor\Module\Magewire;

use Magewirephp\Magewire\Component;

class CategoryProducts extends Component
{
    public int $categoryId = 0;
    public int $pageSize = 10;

    public function mount(int $categoryId, int $pageSize = 10): void
    {
        $this->categoryId = $categoryId;
        $this->pageSize = $pageSize;
    }
}

Magewire converts the category-id and page-size suffixes to $categoryId and $pageSize. mount() runs on the first render. On later requests, Magewire restores public state from the snapshot, so treat both values as untrusted when using them in a query or action. Resolve the current category and check its visibility through your Magento service.

Reuse the class safely

Each rendered block needs a unique name and an independent component instance. Set shared="false" on the Magento object argument when a class is bound more than once. Give each block its own magewire:mount:* values:

<block name="vendor.module.featured" template="Vendor_Module::magewire/category-products.phtml">
    <arguments>
        <argument name="magewire" xsi:type="object" shared="false">Vendor\Module\Magewire\CategoryProducts</argument>
        <argument name="magewire:mount:category-id" xsi:type="number">10</argument>
    </arguments>
</block>
<block name="vendor.module.sale" template="Vendor_Module::magewire/category-products.phtml">
    <arguments>
        <argument name="magewire" xsi:type="object" shared="false">Vendor\Module\Magewire\CategoryProducts</argument>
        <argument name="magewire:mount:category-id" xsi:type="number">20</argument>
    </arguments>
</block>

Advanced block arguments

Custom resolvers and features can read grouped arguments through the argument API. Application components usually only need magewire:mount:*, plus the per-placement magewire:listeners, magewire:loader, and magewire:modifiers arguments described in Layout overrides.

<argument name="magewire:config:cache-ttl" xsi:type="number">3600</argument>
$arguments->forMount()->all();          // ['categoryId' => 10, 'pageSize' => 20]
$arguments->forGroup('config')->all();  // ['cacheTtl' => 3600]

Use magewire:alias only when you need a stable component lookup alias, and magewire:resolver only for a custom resolver. The older array binding with a type item is retained for migration but marked deprecated in the V3 resolver. A boolean type item cannot construct a component. The magewire.* namespace is parsed but does not initialize public properties; use mount() instead.

See Resolvers for custom binding and Properties for public state.