PHPackages                             luza/module-featured-product - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Admin Panels](/categories/admin)
4. /
5. luza/module-featured-product

ActiveMagento2-module[Admin Panels](/categories/admin)

luza/module-featured-product
============================

Magento 2 module for displaying a featured product with real-time inventory updates.

00

Since Jul 5Compare

[ Source](https://github.com/MateusM3/luza-featured-product-module-magento-2)[ Packagist](https://packagist.org/packages/luza/module-featured-product)[ RSS](/packages/luza-module-featured-product/feed)WikiDiscussions Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

Luza\_FeaturedProduct
=====================

[](#luza_featuredproduct)

A Magento 2 module that displays a single **featured product** as the first element of the homepage main content (Luma theme), with its **available stock updated in real time** — periodic AJAX, no full page reload.

The box shows the product's **title, price, base image and salable quantity**, and links to the product page when clicked. Everything is driven from the admin panel and **no theme files are edited** — the whole feature lives inside the module.

Table of contents
-----------------

[](#table-of-contents)

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [How it works](#how-it-works)
- [Architecture &amp; applied patterns](#architecture--applied-patterns)
- [Module structure](#module-structure)
- [Technical reference](#technical-reference)
- [Extending](#extending)
- [Troubleshooting](#troubleshooting)
- [Author &amp; license](#author--license)

Features
--------

[](#features)

- Featured-product box rendered as the **first element** of the homepage main content, occupying the **full `content main` width**.
- Shows **title, final price, base image and salable stock**.
- The **whole card links** to the product page.
- **Real-time stock**: the available quantity is refreshed periodically without reloading the page (Knockout + AJAX).
- Two selection strategies: **by SKU** or **by product**, chosen in the admin.
- **SKU validation** on save (rejects an unknown/empty SKU).
- **Self-healing config**: if the featured product's SKU is renamed, the configuration is updated automatically (observer).
- Fully **configurable from the admin panel**, scope-aware (default / website / store view).
- **No theme edits** — styles and templates ship with the module.
- **i18n** ready (pt\_BR translations included).

Requirements
------------

[](#requirements)

- Magento Open Source / Adobe Commerce **2.4.x**
- PHP **8.1 / 8.2 / 8.3**
- **Multi-Source Inventory** (`magento/module-inventory-sales-api`, enabled by default)

Installation
------------

[](#installation)

Install via Composer:

```
composer require luza/module-featured-product
```

Enable and upgrade:

```
bin/magento module:enable Luza_FeaturedProduct
bin/magento setup:upgrade
```

In **production mode**, also compile DI and deploy static content:

```
bin/magento setup:di:compile
bin/magento setup:static-content:deploy pt_BR en_US
```

Finally, clear the cache:

```
bin/magento cache:flush
```

Configuration
-------------

[](#configuration)

Open **Stores → Configuration → General → Featured Product**. There is also a shortcut under **Content → Featured Product** in the admin menu.

### General Settings

[](#general-settings)

FieldTypeDefaultDescription**Enabled**Yes/NoNoMaster switch. When off, the box is not rendered.**Selection Type**SKU / Product—How the featured product is chosen. Shown when *Enabled*.**Product SKU**Text—The SKU to feature. Validated on save (must exist). Shown when *Selection Type = SKU*.**Product**Dropdown—Pick the product from a list. Shown when *Selection Type = Product*.### Real-Time Stock

[](#real-time-stock)

FieldTypeDefaultDescription**Enable Real-Time Update**Yes/NoNoTurns the periodic stock refresh on/off. When off, the stock is shown once (server value).**Update Interval (seconds)**Text (digits &gt; 0)15How often the stock is refreshed. Shown when the update is enabled.All fields are **scope-aware** — you can configure a different featured product per website or store view.

How it works
------------

[](#how-it-works)

### Product selection &amp; validation

[](#product-selection--validation)

The product shown is driven entirely by configuration and resolved through a service layer (`FeaturedProductResolverInterface`):

- **By SKU** → `ProductRepository::get($sku)`. The *Product SKU* field is guarded by a backend model that runs on save:
    - empty value → *"The SKU field is empty."*
    - unknown SKU → *"No product found with SKU '…'. Please enter a valid SKU."*
- **By Product** → `ProductRepository::getById($id)` (chosen from a dropdown).

The resolver **fails soft**: if the feature is disabled, nothing is configured, or the product no longer exists, it returns `null` and the box is simply not rendered (a warning is logged) — the storefront never breaks.

### Real-time stock

[](#real-time-stock-1)

```
Homepage render
  → Block.getJsLayout() injects { qty, updateUrl, interval, enabled }
  → Knockout uiComponent (stock.js) starts an observable `qty` + polling
  → every N seconds: GET /featuredproduct/stock
        → thin controller → resolver + MSI stock service → { "qty": N }
  → the observable updates → Knockout re-renders ONLY the stock number

```

No full page reload; only the stock value changes. Stock is the **salable quantity**(MSI: quantity − reservations − out-of-stock threshold).

### Automatic SKU sync

[](#automatic-sku-sync)

An observer on `catalog_product_save_after` watches product saves. If the product currently configured as featured (by SKU) has its **SKU renamed**, the stored config value is updated to the new SKU automatically — so the featured box never points to a missing SKU. It runs on `save_after` (not `before`) so the config is only touched once the product save commits.

Architecture &amp; applied patterns
-----------------------------------

[](#architecture--applied-patterns)

- **Service contracts (interfaces + DI preferences).** Business logic sits behind `Api/FeaturedProductResolverInterface` and `Api/FeaturedProductStockInterface`, bound to their implementations in `etc/di.xml`. The **ViewModel** (storefront) and the **AJAX controller** reuse the exact same services — zero duplication.
- **ViewModel over block/template logic.** `ViewModel/FeaturedProductData` (implements `ArgumentInterface`) exposes everything the template needs; the `.phtml` stays logic-free.
- **Thin controller.** `Controller/Stock/Index` only resolves the product and returns `{ "qty": N }` — all logic is delegated to the services.
- **Multi-Source Inventory.** Salable quantity via `GetProductSalableQtyInterface`, **not**the deprecated `CatalogInventory\StockRegistry`.
- **Observer to prevent broken references.** `Observer/SyncFeaturedSku` keeps the configured SKU valid when a product is renamed.
- **Backend-model validation.** `Model/Config/Backend/ProductSku` validates the SKU on save.
- **Real-time UI with jsLayout + Knockout.** The stock line is a Knockout `uiComponent`initialised through `jsLayout` (block argument) + `Magento_Ui/js/core/app`, following the core minicart pattern.
- **No theme edits.** Styles live in `web/css/source/_module.less` (auto-collected by the theme); all markup is in the module.

Module structure
----------------

[](#module-structure)

```
Luza/FeaturedProduct/
├── Api/                                  # Service contracts (interfaces)
│   ├── FeaturedProductResolverInterface.php
│   └── FeaturedProductStockInterface.php
├── Block/
│   └── FeaturedProduct.php               # merges dynamic data into jsLayout
├── Controller/
│   └── Stock/Index.php                   # thin JSON endpoint for the AJAX poll
├── Model/
│   ├── Config.php                        # typed, scope-aware config reader
│   ├── Config/Backend/ProductSku.php     # validates the SKU on save
│   ├── Config/Source/SelectionType.php   # SKU | Product options
│   ├── Config/Source/ProductList.php     # product dropdown
│   ├── FeaturedProductResolver.php       # resolver implementation
│   └── FeaturedProductStock.php          # MSI salable-qty implementation
├── Observer/
│   └── SyncFeaturedSku.php               # keeps config in sync on SKU rename
├── ViewModel/
│   └── FeaturedProductData.php           # presentation data for the template
├── etc/
│   ├── acl.xml                           # admin ACL resource
│   ├── di.xml                            # interface → implementation bindings
│   ├── events.xml                        # catalog_product_save_after observer
│   ├── module.xml
│   ├── frontend/routes.xml               # frontName "featuredproduct"
│   └── adminhtml/{menu.xml, system.xml}  # admin menu + config fields
├── i18n/pt_BR.csv                        # translations
└── view/frontend/
    ├── layout/cms_index_index.xml        # reference block + jsLayout argument
    ├── templates/featured_product.phtml  # card markup + x-magento-init
    └── web/
        ├── css/source/_module.less       # styling (auto-collected by the theme)
        ├── js/view/stock.js              # Knockout uiComponent (polling)
        └── template/stock.html           # Knockout template for the stock line

```

Technical reference
-------------------

[](#technical-reference)

### Config paths

[](#config-paths)

PathGetter (`Model\Config`)`luza_featured_product/general/enabled``isEnabled()``luza_featured_product/general/selection_type``getSelectionType()``luza_featured_product/general/product_sku``getProductSku()``luza_featured_product/general/product_id``getProductId()``luza_featured_product/realtime_stock/enabled``isRealtimeStockEnabled()``luza_featured_product/realtime_stock/update_interval``getStockUpdateInterval()`### AJAX endpoint

[](#ajax-endpoint)

```
GET /featuredproduct/stock  →  {"qty": }

```

Returns the salable quantity of the currently configured featured product (`0` when none is resolvable). Route id `luza_featuredproduct`, frontName `featuredproduct`.

### Knockout component

[](#knockout-component)

- Component: `Luza_FeaturedProduct/js/view/stock` (extends `uiComponent`).
- Config (injected via `jsLayout`): `qty`, `updateUrl`, `interval` (ms), `enabled`.
- Template: `Luza_FeaturedProduct/stock` (`web/template/stock.html`) — binds `text: qty`.
- Bootstrapped by `Magento_Ui/js/core/app` from a `text/x-magento-init` block scoped to `[data-role=featured-product]`.

### ACL

[](#acl)

`Luza_FeaturedProduct::config` (child of `Magento_Config::config`) guards the admin section.

Extending
---------

[](#extending)

Because the resolution logic is behind service contracts, you can override behaviour without touching the module — declare a `preference` in your own module's `di.xml`:

```

```

The same applies to `FeaturedProductStockInterface` (e.g. to change how stock is calculated).

Troubleshooting
---------------

[](#troubleshooting)

- **Box not showing** — ensure *Enabled* is on and a valid product/SKU is configured, then `bin/magento cache:flush`.
- **Image shows a placeholder** — the base image resize may be missing; run `bin/magento catalog:images:resize`.
- **Style/JS changes not applied (default/production mode)** — regenerate static content and clear `pub/static`, `var/view_preprocessed`, then flush the cache.
- **Stock not updating** — check that *Enable Real-Time Update* is on and that `GET /featuredproduct/stock` returns JSON.

Author &amp; license
--------------------

[](#author--license)

Created by Mateus Junior Monteiro.

Licensed under the [MIT License](LICENSE) © Mateus Junior Monteiro.

###  Health Score

9

—

LowBetter than 0% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity0

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/20692202?v=4)[Mateus Monteiro](/maintainers/MateusM3)[@MateusM3](https://github.com/MateusM3)

### Embed Badge

![Health badge](/badges/luza-module-featured-product/health.svg)

```
[![Health](https://phpackages.com/badges/luza-module-featured-product/health.svg)](https://phpackages.com/packages/luza-module-featured-product)
```

###  Alternatives

[dog-ears/crud-d-scaffold

Extend Laravel 7's generators scaffold.

183.1k](/packages/dog-ears-crud-d-scaffold)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
