How to Implement a Custom Auction Module in Magento 2

Why Build a Custom Auction Module in Magento 2?
Adding an auction feature to your Magento 2 store can be a game-changer. It creates urgency, engages customers, and can drive higher sales. While there are pre-built extensions available, sometimes you need a custom solution tailored to your specific business needs.
In this guide, we'll walk through creating a basic auction module from scratch. You'll learn how to set up the database structure, create backend interfaces, and implement frontend bidding functionality.
Module Structure Setup
First, let's create the basic module structure. In your Magento 2 installation, navigate to app/code
and create the following directory structure:
Magefine/
Auction/
etc/
module.xml
db_schema.xml
Controller/
Adminhtml/
Auction/
Index.php
Block/
Adminhtml/
Auction/
Grid.php
Edit.php
Model/
Auction.php
ResourceModel/
Auction.php
Auction/
Collection.php
view/
adminhtml/
layout/
auction_auction_index.xml
ui_component/
auction_auction_listing.xml
templates/
auction/
grid.phtml
form.phtml
frontend/
layout/
catalog_product_view.xml
templates/
auction/
bidding.phtml
Registering the Module
Create app/code/Magefine/Auction/etc/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_Auction" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Customer"/>
</sequence>
</module>
</config>
Database Schema
Create app/code/Magefine/Auction/etc/db_schema.xml
to define our auction tables:
<?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_auction" resource="default" engine="innodb" comment="Auction Table">
<column xsi:type="int" name="auction_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Auction ID"/>
<column xsi:type="int" name="product_id" padding="10" unsigned="true" nullable="false" comment="Product ID"/>
<column xsi:type="datetime" name="start_date" nullable="false" comment="Auction Start Date"/>
<column xsi:type="datetime" name="end_date" nullable="false" comment="Auction End Date"/>
<column xsi:type="decimal" name="starting_price" scale="4" precision="12" nullable="false" comment="Starting Price"/>
<column xsi:type="decimal" name="reserve_price" scale="4" precision="12" nullable="true" comment="Reserve Price"/>
<column xsi:type="decimal" name="current_bid" scale="4" precision="12" nullable="true" comment="Current Bid"/>
<column xsi:type="int" name="current_bidder" padding="10" unsigned="true" nullable="true" comment="Current Bidder ID"/>
<column xsi:type="smallint" name="status" padding="5" unsigned="true" nullable="false" default="0" comment="Status"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="auction_id"/>
</constraint>
<constraint xsi:type="foreign" referenceId="MAGEFINE_AUCTION_PRODUCT_ID_CATALOG_PRODUCT_ENTITY_ENTITY_ID" table="magefine_auction" column="product_id" referenceTable="catalog_product_entity" referenceColumn="entity_id" onDelete="CASCADE"/>
<constraint xsi:type="foreign" referenceId="MAGEFINE_AUCTION_CURRENT_BIDDER_CUSTOMER_ENTITY_ENTITY_ID" table="magefine_auction" column="current_bidder" referenceTable="customer_entity" referenceColumn="entity_id" onDelete="SET NULL"/>
<index referenceId="MAGEFINE_AUCTION_PRODUCT_ID" indexType="btree">
<column name="product_id"/>
</index>
</table>
<table name="magefine_auction_bid" resource="default" engine="innodb" comment="Auction Bids Table">
<column xsi:type="int" name="bid_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Bid ID"/>
<column xsi:type="int" name="auction_id" padding="10" unsigned="true" nullable="false" comment="Auction ID"/>
<column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="false" comment="Customer ID"/>
<column xsi:type="decimal" name="amount" scale="4" precision="12" nullable="false" comment="Bid Amount"/>
<column xsi:type="datetime" name="created_at" nullable="false" comment="Bid Time"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="bid_id"/>
</constraint>
<constraint xsi:type="foreign" referenceId="MAGEFINE_AUCTION_BID_AUCTION_ID_MAGEFINE_AUCTION_AUCTION_ID" table="magefine_auction_bid" column="auction_id" referenceTable="magefine_auction" referenceColumn="auction_id" onDelete="CASCADE"/>
<constraint xsi:type="foreign" referenceId="MAGEFINE_AUCTION_BID_CUSTOMER_ID_CUSTOMER_ENTITY_ENTITY_ID" table="magefine_auction_bid" column="customer_id" referenceTable="customer_entity" referenceColumn="entity_id" onDelete="CASCADE"/>
<index referenceId="MAGEFINE_AUCTION_BID_AUCTION_ID" indexType="btree">
<column name="auction_id"/>
</index>
<index referenceId="MAGEFINE_AUCTION_BID_CUSTOMER_ID" indexType="btree">
<column name="customer_id"/>
</index>
</table>
</schema>
Creating the Model and Resource Model
Now let's create our model files. First, the main auction model at app/code/Magefine/Auction/Model/Auction.php
:
<?php
namespace Magefine\Auction\Model;
use Magento\Framework\Model\AbstractModel;
use Magefine\Auction\Model\ResourceModel\Auction as ResourceModel;
class Auction extends AbstractModel
{
const STATUS_PENDING = 0;
const STATUS_ACTIVE = 1;
const STATUS_COMPLETED = 2;
const STATUS_CANCELLED = 3;
protected function _construct()
{
$this->_init(ResourceModel::class);
}
public function getStatusLabels()
{
return [
self::STATUS_PENDING => __('Pending'),
self::STATUS_ACTIVE => __('Active'),
self::STATUS_COMPLETED => __('Completed'),
self::STATUS_CANCELLED => __('Cancelled')
];
}
public function getStatusLabel()
{
$statuses = $this->getStatusLabels();
return $statuses[$this->getStatus()] ?? __('Unknown');
}
public function isActive()
{
return $this->getStatus() == self::STATUS_ACTIVE;
}
}
Admin Interface Setup
Let's create the admin grid and form. First, create the grid UI component at app/code/Magefine/Auction/view/adminhtml/ui_component/auction_auction_listing.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">auction_auction_listing.auction_auction_listing_data_source</item>
<item name="deps" xsi:type="string">auction_auction_listing.auction_auction_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">auction_columns</item>
</argument>
<dataSource name="auction_auction_listing_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
<argument name="name" xsi:type="string">auction_auction_listing_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">auction_id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
<item name="update_url" xsi:type="url" path="mui/index/render">
<param name="_secure">true</param>
</item>
<item name="storageConfig" xsi:type="array">
<item name="indexField" xsi:type="string">auction_id</item>
</item>
</item>
</argument>
</argument>
</dataSource>
<columns name="auction_columns">
<column name="auction_id">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">ID</item>
<item name="sortOrder" xsi:type="number">10</item>
</item>
</argument>
</column>
<column name="product_id">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Product ID</item>
<item name="sortOrder" xsi:type="number">20</item>
</item>
</argument>
</column>
<column name="starting_price">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Starting Price</item>
<item name="sortOrder" xsi:type="number">30</item>
</item>
</argument>
</column>
<column name="current_bid">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Current Bid</item>
<item name="sortOrder" xsi:type="number">40</item>
</item>
</argument>
</column>
<column name="status">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Magefine\Auction\Model\Auction</item>
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">select</item>
<item name="component" xsi:type="string">Magento_Ui/js/grid/columns/select</item>
<item name="dataType" xsi:type="string">select</item>
<item name="label" xsi:type="string" translate="true">Status</item>
<item name="sortOrder" xsi:type="number">50</item>
</item>
</argument>
</column>
</columns>
</listing>
Frontend Implementation
Now let's implement the frontend bidding interface. First, create a layout update at app/code/Magefine/Auction/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>
<referenceContainer name="product.info.main">
<block class="Magefine\Auction\Block\Product\View\Auction" name="product.auction" template="Magefine_Auction::auction/bidding.phtml" after="product.info.price">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="auctionBidding" xsi:type="array">
<item name="component" xsi:type="string">Magefine_Auction/js/bidding</item>
<item name="config" xsi:type="array">
<item name="bidUrl" xsi:type="url" path="auction/bid/submit"/>
<item name="updateUrl" xsi:type="url" path="auction/bid/update"/>
</item>
</item>
</item>
</argument>
</arguments>
</block>
</referenceContainer>
</body>
</page>
JavaScript Component
Create the bidding component at app/code/Magefine/Auction/view/frontend/web/js/bidding.js
:
define([
'jquery',
'Magento_Customer/js/customer-data',
'mage/translate'
], function ($, customerData, $t) {
'use strict';
return function (config, element) {
let biddingComponent = {
init: function() {
this.bidUrl = config.bidUrl;
this.updateUrl = config.updateUrl;
this.productId = $(element).data('product-id');
this.customer = customerData.get('customer');
this.bindEvents();
this.startPolling();
},
bindEvents: function() {
let self = this;
$(element).find('#place-bid').on('click', function() {
self.placeBid();
});
},
placeBid: function() {
let bidAmount = parseFloat($(element).find('#bid-amount').val());
let self = this;
if (isNaN(bidAmount) || bidAmount <= 0) {
alert($t('Please enter a valid bid amount'));
return;
}
$.ajax({
url: this.bidUrl,
type: 'POST',
data: {
product_id: this.productId,
amount: bidAmount,
form_key: $.mage.cookies.get('form_key')
},
showLoader: true,
success: function(response) {
if (response.success) {
self.updateBidInfo(response);
} else {
alert(response.message);
}
}
});
},
startPolling: function() {
let self = this;
setInterval(function() {
self.updateBidData();
}, 5000);
},
updateBidData: function() {
let self = this;
$.ajax({
url: this.updateUrl,
type: 'GET',
data: {
product_id: this.productId
},
success: function(response) {
if (response.success) {
self.updateBidInfo(response);
}
}
});
},
updateBidInfo: function(data) {
$(element).find('.current-bid').text(data.current_bid);
$(element).find('.bid-count').text(data.bid_count);
$(element).find('.time-left').text(data.time_left);
if (data.is_leading) {
$(element).find('.bid-status').text($t('You are currently the highest bidder!'));
} else {
$(element).find('.bid-status').text('');
}
}
};
biddingComponent.init();
};
});
Final Steps
After implementing all these components, you'll need to:
- Run
php bin/magento setup:upgrade
to install the module - Compile your code with
php bin/magento setup:di:compile
- Deploy static content with
php bin/magento setup:static-content:deploy
- Flush the cache with
php bin/magento cache:flush
This gives you a basic auction system that you can now extend with additional features like automatic bidding, bid increments, auction extensions when bids are placed near the end time, and more complex reporting.
Remember to thoroughly test your auction module before deploying to production, especially for concurrency issues when multiple users bid simultaneously.