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:
- Run
bin/magento setup:upgrade
- Clear cache with
bin/magento cache:clean
- Submit a test review on a product page
- Check the admin panel to see if your custom fields appear
- 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.