How to Implement a Custom Review System in Magento 2

Why You Might Need a Custom Review System in Magento 2

Magento 2 comes with a built-in product review system, but sometimes it just doesn’t cut it. Maybe you need extra fields, a different rating system, or tighter moderation controls. That’s where a custom review system comes in handy.

In this guide, we’ll walk through how to build a custom review module from scratch. No fluff, just clear steps and code examples to get you up and running.

Setting Up the Basic Module Structure

First, let’s create our module skeleton. Place this in app/code/Magefine/CustomReviews:

app/code/Magefine/CustomReviews/
├── etc/
│   ├── module.xml
│   ├── db_schema.xml
│   └── frontend/
│       └── routes.xml
├── registration.php
└── composer.json

Here’s what goes in registration.php:

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Magefine_CustomReviews',
    __DIR__
);

And your module.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magefine_CustomReviews" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Review"/>
        </sequence>
    </module>
</config>

Creating the Database Tables

We’ll need a table to store our custom review data. Add this to db_schema.xml:

<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="magefine_custom_reviews" resource="default" engine="innodb" comment="Custom Reviews Table">
        <column xsi:type="int" name="review_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Review ID"/>
        <column xsi:type="int" name="parent_review_id" padding="10" unsigned="true" nullable="false" comment="Original Review ID"/>
        <column xsi:type="varchar" name="custom_field" nullable="true" length="255" comment="Custom Field Example"/>
        <column xsi:type="smallint" name="custom_rating" padding="5" unsigned="true" nullable="true" comment="Additional Rating"/>
        <constraint xsi:type="primary" referenceId="PRIMARY">
            <column name="review_id"/>
        </constraint>
        <constraint xsi:type="foreign" referenceId="MAGEFINE_CUSTOM_REVIEWS_PARENT_ID_REVIEW_REVIEW_ID" table="magefine_custom_reviews" column="parent_review_id" referenceTable="review" referenceColumn="review_id" onDelete="CASCADE"/>
    </table>
</schema>

Extending the Review Model

Now let’s create our custom review model. First, the model class at Model/Review.php:

<?php
namespace Magefine\CustomReviews\Model;

class Review extends \Magento\Review\Model\Review
{
    protected function _construct()
    {
        parent::_construct();
        $this->_init('Magefine\CustomReviews\Model\ResourceModel\Review');
    }
    
    public function getCustomField()
    {
        return $this->getData('custom_field');
    }
    
    public function getCustomRating()
    {
        return $this->getData('custom_rating');
    }
}

And its resource model at Model/ResourceModel/Review.php:

<?php
namespace Magefine\CustomReviews\Model\ResourceModel;

class Review extends \Magento\Review\Model\ResourceModel\Review
{
    protected function _construct()
    {
        $this->_init('review', 'review_id');
    }
    
    protected function _afterLoad(\Magento\Framework\Model\AbstractModel $object)
    {
        $connection = $this->getConnection();
        $select = $connection->select()
            ->from($this->getTable('magefine_custom_reviews'))
            ->where('parent_review_id = ?', $object->getId());
            
        $customData = $connection->fetchRow($select);
        
        if ($customData) {
            $object->addData($customData);
        }
        
        return parent::_afterLoad($object);
    }
}

Creating the Admin Form

To let admins see our custom fields, we need to extend the review edit form. Create view/adminhtml/ui_component/review_form.xml:

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <fieldset name="review_details">
        <field name="custom_field">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Custom Field</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="source" xsi:type="string">review</item>
                    <item name="dataScope" xsi:type="string">custom_field</item>
                </item>
            </argument>
        </field>
        <field name="custom_rating">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">number</item>
                    <item name="label" xsi:type="string" translate="true">Additional Rating</item>
                    <item name="formElement" xsi:type="string">input</item>
                    <item name="validation" xsi:type="array">
                        <item name="validate-number" xsi:type="boolean">true</item>
                        <item name="validate-greater-than-zero" xsi:type="boolean">true</item>
                        <item name="validate-less-than-equal-ten" xsi:type="boolean">true</item>
                    </item>
                    <item name="source" xsi:type="string">review</item>
                    <item name="dataScope" xsi:type="string">custom_rating</item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

Frontend Display

To show our custom data on the frontend, we’ll need to override the review templates. First, create a layout update at view/frontend/layout/catalog_product_view.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>
        <referenceBlock name="reviews.tab">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Magefine_CustomReviews::product/view/review.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Then create the template at view/frontend/templates/product/view/review.phtml:

<?php
/** @var \Magento\Review\Block\Product\Review $block */
/** @var \Magento\Framework\Escaper $escaper */
?>
<div id="product-review-container" data-role="product-review"></div>
<?= $block->getChildHtml() ?>

<script type="text/x-magento-init">
{
    "*": {
        "Magento_Review/js/process-reviews": {
            "productReviewUrl": "<?= $block->escapeJs($block->getProductReviewUrl()) ?>",
            "reviewsTabSelector": "#tab-label-reviews"
        }
    }
}
</script>

<!-- Our custom review display -->
<div class="custom-review-fields">
    <?php foreach ($block->getReviewsCollection() as $review): ?>
        <?php if ($review->getCustomField() || $review->getCustomRating()): ?>
            <div class="custom-review-item">
                <?php if ($review->getCustomField()): ?>
                    <p class="custom-field"><?= $escaper->escapeHtml($review->getCustomField()) ?></p>
                <?php endif; ?>
                <?php if ($review->getCustomRating()): ?>
                    <div class="custom-rating">
                        <span>Additional Rating: <?= (int)$review->getCustomRating() ?>/10</span>
                    </div>
                <?php endif; ?>
            </div>
        <?php endif; ?>
    <?php endforeach; ?>
</div>

Saving Custom Data

We need to save our custom fields when a review is submitted. Create an observer at Observer/SaveCustomReviewData.php:

<?php
namespace Magefine\CustomReviews\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;

class SaveCustomReviewData implements ObserverInterface
{
    protected $resourceConnection;
    
    public function __construct(
        \Magento\Framework\App\ResourceConnection $resourceConnection
    ) {
        $this->resourceConnection = $resourceConnection;
    }
    
    public function execute(Observer $observer)
    {
        $review = $observer->getEvent()->getObject();
        $request = $observer->getEvent()->getRequest();
        
        $customData = [
            'parent_review_id' => $review->getId(),
            'custom_field' => $request->getParam('custom_field'),
            'custom_rating' => $request->getParam('custom_rating')
        ];
        
        $connection = $this->resourceConnection->getConnection();
        $connection->insertOnDuplicate(
            $this->resourceConnection->getTableName('magefine_custom_reviews'),
            $customData,
            ['custom_field', 'custom_rating']
        );
    }
}

Register this observer in etc/events.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="review_save_after">
        <observer name="magefine_custom_reviews_save" instance="Magefine\CustomReviews\Observer\SaveCustomReviewData"/>
    </event>
</config>

Adding Custom Fields to the Review Form

Finally, let’s add our custom fields to the customer review form. Create view/frontend/layout/review_product_list.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>
        <referenceBlock name="product.review.form">
            <block class="Magento\Review\Block\Form" name="product.review.form.custom.fields" template="Magefine_CustomReviews::form/custom_fields.phtml"/>
        </referenceBlock>
    </body>
</page>

And the template at view/frontend/templates/form/custom_fields.phtml:

<div class="field custom-field">
    <label for="custom_field" class="label"><span><?= $block->escapeHtml(__('Custom Feedback')) ?></span></label>
    <div class="control">
        <textarea name="custom_field" id="custom_field" cols="5" rows="3" class="input-text"></textarea>
    </div>
</div>

<div class="field custom-rating">
    <label for="custom_rating" class="label"><span><?= $block->escapeHtml(__('Additional Rating (1-10)')) ?></span></label>
    <div class="control">
        <input type="number" name="custom_rating" id="custom_rating" min="1" max="10" class="input-text"/>
    </div>
</div>

Testing Your Custom Review System

After setting up everything:

  1. Run bin/magento setup:upgrade
  2. Clear cache with bin/magento cache:clean
  3. Submit a test review on a product page
  4. Check the admin panel to see if your custom fields appear
  5. Verify the frontend display shows your custom data

If everything works, congratulations! You’ve successfully implemented a custom review system in Magento 2.

Final Thoughts

Building a custom review system gives you the flexibility to collect exactly the feedback you need from customers. While this example covers basic custom fields, you could extend it further with:

  • Photo/video uploads in reviews
  • Review verification badges
  • Custom moderation workflows
  • Advanced filtering options

Remember to always back up your store before making core changes, and consider using our Magefine extensions for ready-made solutions that save development time.