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:

  1. Run php bin/magento setup:upgrade to install the module
  2. Compile your code with php bin/magento setup:di:compile
  3. Deploy static content with php bin/magento setup:static-content:deploy
  4. 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.