Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Summary: Introducing Custom Product Types the Right Way

Summary: Introducing Custom Product Types the Right Way

~5 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Block 9 ends the way block 6 did with chapter 54, block 7 with chapter 61, and block 8 with chapter 70: a collected look back - this time across the entire path from the decision question in chapter 71 to the redeemable, discount-eligible order in chapter 77.

All files from this block at a glance

New and changed files from block 9

app/code/Mironsoft/Loyalty/
├── Model/
│   └── Product/
│       └── Type/
│           ├── PointsPackage.php                                (chapter 73)
│           └── PointsPackage/
│               └── Price.php                                    (chapter 73)
├── Observer/
│   └── CreditPurchasedPointsPackageOnOrderPlaced.php             (chapter 76)
├── Setup/
│   └── Patch/
│       └── Data/
│           ├── InstallPointsPackageAttributeSet.php              (chapter 74)
│           ├── InstallPointsPackageAmountAttribute.php           (chapter 74)
│           └── InstallSalesLoyaltyPointsPackageCreditedAttribute.php  (chapter 76)
├── etc/
│   ├── product_types.xml                                        (chapter 72)
│   └── events.xml                                               (chapter 76, extended)
└── view/
    ├── adminhtml/
    │   └── ui_component/
    │       └── product_form.xml                                 (chapter 74, optional)
    └── frontend/
        ├── layout/
        │   └── catalog_product_view_type_loyalty_points_package.xml  (chapter 75)
        └── templates/
            └── product/
                └── view/
                    └── type/
                        └── points-package.phtml                 (chapter 75)

Checklist: introducing custom product types

  1. Justify the decision (chapter 71): does an attribute on an existing type suffice, or does the concept need its own apply_to boundary?
  2. Register it (chapter 72): etc/product_types.xml with modelInstance, priceModel, indexPriceModel - flush the cache, and run setup:di:compile in production.
  3. Implement the type and price models (chapter 73): pick the right parent class (here Virtual), override only what genuinely differs.
  4. Prepare the admin form (chapter 74): a dedicated attribute set, required fields scoped with apply_to, communicate attribute set AND type together.
  5. Display and sell it in the frontend (chapter 75): use the automatic layout handle, generic additional_options instead of new cart/order templates.
  6. Wire up order processing (chapter 76): observer instead of plugin, a dedicated idempotency attribute, reuse existing block 1 repository interfaces instead of reinventing them.
  7. Test compatibility (chapter 77): a full reindex after introduction, click through price rules and third-party campaigns end-to-end instead of assuming they work.

Tipp: The thread running through all eight chapters of this block: a new product type is almost never "new" in the sense of "written entirely from scratch" - PointsPackage inherits from Virtual, the price model inherits from the core Price class, order processing keeps using PointsLedgerRepositoryInterface and CustomerRepositoryInterface from block 1 unchanged. What actually makes this type new is exclusively the distinct type code itself and the handful of spots where the business concept - a purchasable bundle of loyalty points - genuinely differs from everything already in place.

Block 10 turns to APIs next: REST and GraphQL endpoints that expose the same points balance and the same repositories - including the points-package logic this block just wired in - to external clients.