How to Build a Custom "Waitlist" Feature for Out-of-Stock Products in Magento 2

How to Build a Custom "Waitlist" Feature for Out-of-Stock Products in Magento 2
Running an eCommerce store means dealing with stock fluctuations—some products sell out faster than expected, leaving potential customers disappointed. But what if you could turn that frustration into an opportunity? A custom "waitlist" feature lets customers sign up to be notified when an out-of-stock product is back in stock, keeping them engaged and increasing future sales.
In this guide, we'll walk through building a waitlist module from scratch in Magento 2, integrating email notifications, optimizing UX for sign-ups, and leveraging waitlist data for marketing. Whether you're a developer or a store owner looking to implement this feature, we’ll keep things clear and actionable.
Why a Waitlist Feature Matters
Before diving into the code, let’s quickly cover why a waitlist is worth the effort:
- Reduce lost sales: Customers who sign up are more likely to purchase when the product restocks.
- Gather demand insights: Track which products have high waitlist demand to prioritize restocking.
- Boost engagement: Keep customers connected to your brand even when inventory runs low.
Step-by-Step Guide to Creating a Waitlist Module
We’ll build a custom module named Magefine_Waitlist
. Here’s how to structure it:
1. Set Up the Module Structure
First, create the basic module files in app/code/Magefine/Waitlist
:
app/code/Magefine/Waitlist/
├── etc/
│ ├── module.xml
│ ├── di.xml
│ └── events.xml
├── Controller/
│ └── Index/
│ └── Add.php
├── Model/
│ ├── Waitlist.php
│ └── ResourceModel/
│ ├── Waitlist.php
│ └── Waitlist/
│ └── Collection.php
├── view/
│ └── frontend/
│ ├── layout/
│ │ └── waitlist_index_add.xml
│ └── templates/
│ └── form.phtml
└── registration.php
2. Define the Module
In registration.php
:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Magefine_Waitlist',
__DIR__
);
In 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_Waitlist" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Customer"/>
</sequence>
</module>
</config>
3. Create the Database Table
Set up Setup/InstallSchema.php
to create a table for storing waitlist entries:
<?php
namespace Magefine\Waitlist\Setup;
use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
class InstallSchema implements InstallSchemaInterface
{
public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
$table = $installer->getConnection()->newTable(
$installer->getTable('magefine_waitlist')
)->addColumn(
'waitlist_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,
['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
'Waitlist ID'
)->addColumn(
'product_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,
['unsigned' => true, 'nullable' => false],
'Product ID'
)->addColumn(
'customer_id',
\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
null,
['unsigned' => true, 'nullable' => true],
'Customer ID'
)->addColumn(
'email',
\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
255,
['nullable' => false],
'Customer Email'
)->addColumn(
'created_at',
\Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP,
null,
['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT],
'Creation Time'
)->addForeignKey(
$installer->getFkName(
'magefine_waitlist',
'product_id',
'catalog_product_entity',
'entity_id'
),
'product_id',
$installer->getTable('catalog_product_entity'),
'entity_id',
\Magento\Framework\DB\Ddl\Table::ACTION_CASCADE
)->addForeignKey(
$installer->getFkName(
'magefine_waitlist',
'customer_id',
'customer_entity',
'entity_id'
),
'customer_id',
$installer->getTable('customer_entity'),
'entity_id',
\Magento\Framework\DB\Ddl\Table::ACTION_CASCADE
);
$installer->getConnection()->createTable($table);
$installer->endSetup();
}
}
4. Build the Model and Resource Model
In Model/Waitlist.php
:
<?php
namespace Magefine\Waitlist\Model;
use Magento\Framework\Model\AbstractModel;
class Waitlist extends AbstractModel
{
protected function _construct()
{
$this->_init(\Magefine\Waitlist\Model\ResourceModel\Waitlist::class);
}
}
In Model/ResourceModel/Waitlist.php
:
<?php
namespace Magefine\Waitlist\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Waitlist extends AbstractDb
{
protected function _construct()
{
$this->_init('magefine_waitlist', 'waitlist_id');
}
}
5. Create the Frontend Form
Add a form to the product page when a product is out of stock. In view/frontend/templates/form.phtml
:
<div class="waitlist-form">
<form action="<?= $block->escapeUrl($block->getFormAction()) ?>" method="post">
<input type="email" name="email" placeholder="your@email.com" required />
<button type="submit">Notify Me When Available</button>
</form>
</div>
6. Handle Form Submission
Create a controller to process waitlist submissions (Controller/Index/Add.php
):
<?php
namespace Magefine\Waitlist\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magefine\Waitlist\Model\WaitlistFactory;
class Add extends Action
{
protected $waitlistFactory;
public function __construct(
Context $context,
WaitlistFactory $waitlistFactory
) {
$this->waitlistFactory = $waitlistFactory;
parent::__construct($context);
}
public function execute()
{
$productId = $this->getRequest()->getParam('product_id');
$email = $this->getRequest()->getParam('email');
$waitlist = $this->waitlistFactory->create();
$waitlist->setProductId($productId);
$waitlist->setEmail($email);
$waitlist->save();
$this->messageManager->addSuccessMessage(__('You’ve been added to the waitlist!'));
$this->_redirect('catalog/product/view', ['id' => $productId]);
}
}
Integrating Email Notifications
Now, let’s notify customers when the product is back in stock. We’ll use Magento’s observer system.
1. Set Up an 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="catalog_product_save_after">
<observer name="magefine_waitlist_notify" instance="Magefine\Waitlist\Observer\ProductSaveAfter"/>
</event>
</config>
2. Create the Observer
In Observer/ProductSaveAfter.php
:
<?php
namespace Magefine\Waitlist\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magefine\Waitlist\Model\ResourceModel\Waitlist\CollectionFactory;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Store\Model\StoreManagerInterface;
class ProductSaveAfter implements ObserverInterface
{
protected $collectionFactory;
protected $transportBuilder;
protected $storeManager;
public function __construct(
CollectionFactory $collectionFactory,
TransportBuilder $transportBuilder,
StoreManagerInterface $storeManager
) {
$this->collectionFactory = $collectionFactory;
$this->transportBuilder = $transportBuilder;
$this->storeManager = $storeManager;
}
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
if ($product->getStockData()['is_in_stock']) {
$waitlistEntries = $this->collectionFactory->create()
->addFieldToFilter('product_id', $product->getId());
foreach ($waitlistEntries as $entry) {
$this->sendNotificationEmail($entry->getEmail(), $product);
$entry->delete();
}
}
}
protected function sendNotificationEmail($email, $product)
{
$storeId = $this->storeManager->getStore()->getId();
$templateVars = [
'product_name' => $product->getName(),
'product_url' => $product->getProductUrl()
];
$transport = $this->transportBuilder
->setTemplateIdentifier('waitlist_notification')
->setTemplateOptions(['area' => 'frontend', 'store' => $storeId])
->setTemplateVars($templateVars)
->setFrom(['email' => 'sales@example.com', 'name' => 'Store Support'])
->addTo($email)
->getTransport();
$transport->sendMessage();
}
}
3. Create the Email Template
In your Magento admin, go to Marketing > Email Templates and create a new template with this content:
<!--@subject Your Waitlisted Product is Back in Stock! @-->
<!--@vars {
"var product_name":"Product Name",
"var product_url":"Product URL"
} @-->
<p>Great news! The product you waitlisted is back in stock:</p>
<p><a href="{{var product_url}}">{{var product_name}}</a></p>
<p>Hurry—quantities may be limited!</p>
Best Practices for UX Design
A well-designed waitlist feature should be:
- Visible but not intrusive: Place the waitlist form near the "Out of Stock" message.
- Minimal fields: Only ask for email (autofill for logged-in customers).
- Clear value proposition: Explain benefits like "First access when restocked."
- Mobile-friendly: Ensure the form works well on all devices.
Leveraging Waitlist Data for Marketing
Your waitlist isn’t just about restock notifications—it’s a goldmine for marketing:
- Exclusive offers: Send waitlist customers a 10% discount code when the product restocks.
- Demand forecasting: Analyze which products have the most waitlist signups to prioritize inventory.
- Personalized campaigns: Segment customers by waitlisted products for targeted emails.
Custom vs. Third-Party Extensions
While building a custom solution gives you full control, third-party extensions like Magefine’s Waitlist Pro offer:
- Faster implementation: No coding required.
- Advanced features: Auto-import to cart, SMS notifications, etc.
- Ongoing support: Regular updates and bug fixes.
However, if you need a lightweight, tailored solution, building your own module (as shown above) is a great option.
Final Thoughts
A waitlist feature turns stockouts into opportunities. By following this guide, you can implement a basic version or extend it with advanced functionality like SMS alerts or integration with your CRM. Whether you build it yourself or use an extension, the key is making it seamless for customers to stay engaged with your store.
Got questions or need help optimizing your Magento store? Reach out to our team—we’re happy to help!