Updating Prices and Stock in 1C-Bitrix with Push & Pull
Sometimes product prices and stock in a 1C-Bitrix catalogue need to update on an open page without a reload or any further action from the user.
For example, this may be needed when:
In 1C-Bitrix, this can be done using the module Push & Pull.
An event is sent only when a change actually occurs.
The simplest approach is to add the product ID to a data attribute on the container:
For example:
For example, you may need to update:
For example:
This approach is useful when price calculations are complex and retrieving the final server-side result is easier than sending all the information through a Pull event.
For example, when adding a product to the basket or placing an order, the server must still recheck:
Push & Pull updates the interface, but the server always makes the final decision.
It must therefore be used only for data that may be disclosed to all connected users.
For example:
In that case, send events to a specific user or group of users.
For example:
JavaScript can still modify the loaded DOM, but dynamic catalogue data should be designed with composite mode in mind.
Prices, stock, personal discounts and other frequently changing values should usually belong to the dynamic part of the page.
It can be used for:
The basic process is straightforward:
Back to blog
For example, this may be needed when:
- 1C regularly exports new prices and stock levels;
- Other customers are actively reserving products;
- Prices change automatically;
- The catalogue page remains open for a long time;
- The catalogue serves as an operational B2B interface.
In 1C-Bitrix, this can be done using the module Push & Pull.
How Push & Pull Works
The overall flow is:
PHP → Push server → WebSocket → JavaScript → DOM
The sequence is:- A product's price or stock changes on the server.
- PHP sends a Push & Pull event.
- The queue server delivers the event to connected browsers.
- JavaScript receives the event.
- The relevant product's price or stock is updated on the page.
An event is sent only when a change actually occurs.
Load the Push & Pull Module
First, load the required modules on the server:
use Bitrix\Main\Loader;
Loader::includeModule('pull');
Loader::includeModule('catalog');
Next, create two functions: one for price changes and one for stock changes.Send an Event When a Price Changes
For a public catalogue, the event can be sent to a shared channel:
function sendPriceUpdateEvent(
int $productId,
float $price,
string $currency = 'RUB'
): void
{
\CPullStack::AddShared([
'module_id' => 'catalog_live',
'command' => 'priceChanged',
'params' => [
'productId' => $productId,
'price' => $price,
'currency' => $currency,
'timestamp' => time(),
],
]);
}
The event contains:- The product ID;
- The new price;
- The currency;
- The event creation time.
Send an Event When Stock Changes
Create a similar event for the product quantity:
function sendStockUpdateEvent(
int $productId,
float $quantity
): void
{
\CPullStack::AddShared([
'module_id' => 'catalog_live',
'command' => 'stockChanged',
'params' => [
'productId' => $productId,
'quantity' => $quantity,
'timestamp' => time(),
],
]);
}
These functions must now be called whenever catalogue data changes.Track Price Changes
Register the handlers, for example, in:
/local/php_interface/init.php
Load EventManager:
use Bitrix\Main\EventManager;
$eventManager = EventManager::getInstance();
Then register a price-change handler:
$eventManager->addEventHandler(
'catalog',
'Bitrix\Catalog\Model\Price::OnAfterUpdate',
static function ($event) {
$id = (int)$event->getParameter('id');
$fields = (array)$event->getParameter('fields');
if (
$id > 0
&& !empty($fields['PRODUCT_ID'])
&& array_key_exists('PRICE', $fields)
) {
sendPriceUpdateEvent(
(int)$fields['PRODUCT_ID'],
(float)$fields['PRICE'],
(string)($fields['CURRENCY'] ?? 'RUB')
);
}
}
);
After a price update, Push & Pull receives an event containing the new product data.Track Stock Changes
Add a separate handler for product quantities:
$eventManager->addEventHandler(
'catalog',
'Bitrix\Catalog\Model\Product::OnAfterUpdate',
static function ($event) {
$id = (int)$event->getParameter('id');
$fields = (array)$event->getParameter('fields');
if (
$id > 0
&& array_key_exists('QUANTITY', $fields)
) {
sendStockUpdateEvent(
$id,
(float)$fields['QUANTITY']
);
}
}
);
If the project uses warehouse inventory management and needs updates for individual warehouses, adapt the handler to the stock storage model in use.Prepare the Product Card HTML
JavaScript needs to know which page element corresponds to the product that changed.The simplest approach is to add the product ID to a data attribute on the container:
<div
class="product-card"
data-product-id="<?=$arItem['ID']?>"
>
<span class="product-price">
<?=$arItem['PRICE']?> руб.
</span>
<span class="product-stock in-stock">
В наличии: <?=$arItem['QUANTITY']?> шт.
</span>
</div>
When an event arrives, the relevant card can then be found using data-product-id.Subscribe to Price Changes in JavaScript
Add a subscription in the component template's script.js file:
BX.ready(function () {
BX.PULL.subscribe({
moduleId: 'catalog_live',
command: 'priceChanged',
callback: function (params) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (!card) {
return;
}
var priceElement = card.querySelector('.product-price');
if (!priceElement) {
return;
}
priceElement.textContent = new Intl.NumberFormat(
'ru-RU',
{
style: 'currency',
currency: params.currency || 'RUB'
}
).format(params.price);
priceElement.classList.add('price-updated');
setTimeout(function () {
priceElement.classList.remove('price-updated');
}, 2000);
}
});
});
When the server sends the command priceChanged, JavaScript finds the product card and replaces its price directly in the DOM.Subscribe to Stock Changes
Add a second subscription:
BX.PULL.subscribe({
moduleId: 'catalog_live',
command: 'stockChanged',
callback: function (params) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (!card) {
return;
}
var stockElement = card.querySelector('.product-stock');
if (!stockElement) {
return;
}
if (params.quantity > 0) {
stockElement.textContent =
'В наличии: ' + params.quantity + ' шт.';
stockElement.className =
'product-stock in-stock';
} else {
stockElement.textContent =
'Нет в наличии';
stockElement.className =
'product-stock out-of-stock';
}
}
});
Stock quantities now update without reloading the page as well.Highlight Changes Visually
To help users notice a price update, briefly highlight the value.For example:
.price-updated {
animation: priceFlash 0.6s ease-in-out 3;
}
@keyframes priceFlash {
0%,
100% {
background-color: transparent;
}
50% {
background-color: #fff3cd;
}
}
.product-stock.in-stock {
color: #28a745;
}
.product-stock.out-of-stock {
color: #dc3545;
}
After a change, the price flashes several times and the stock element receives the appropriate class.When the Entire Card Needs Updating
A price change may affect more than one field.For example, you may need to update:
- The price;
- The previous price;
- The discount amount;
- Stock;
- Whether the ‘Buy’ button is enabled;
- The SKU;
- Other card parameters.
For example:
BX.PULL.subscribe({
moduleId: 'catalog_live',
command: 'priceChanged',
callback: function (params) {
BX.ajax.runComponentAction(
'vendor:catalog.product',
'getProductData',
{
mode: 'class',
data: {
productId: params.productId
}
}
).then(function (response) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (!card) {
return;
}
var price = card.querySelector('.product-price');
var stock = card.querySelector('.product-stock');
if (price) {
price.textContent =
response.data.price + ' руб.';
}
if (stock) {
stock.textContent =
response.data.quantity > 0
? 'В наличии: '
+ response.data.quantity
+ ' шт.'
: 'Нет в наличии';
}
});
}
});
The component name and action here are placeholders. Implement the server-side action in your own component.This approach is useful when price calculations are complex and retrieving the final server-side result is easier than sending all the information through a Pull event.
Why a Push Event Must Not Be Trusted at Checkout
Although the interface updates in near real time, Push & Pull must not become the source of truth for critical operations.For example, when adding a product to the basket or placing an order, the server must still recheck:
- The current price;
- The available quantity;
- Discounts;
- Basket rules;
- Availability of the selected SKU.
Push & Pull updates the interface, but the server always makes the final decision.
Public and Customer-Specific Prices
The example above uses:
\CPullStack::AddShared()
This method sends an event to a shared channel.It must therefore be used only for data that may be disclosed to all connected users.
For example:
- A public product price;
- Overall stock;
- Availability status.
In that case, send events to a specific user or group of users.
For example:
\Bitrix\Pull\Event::add(
$userId,
[
'module_id' => 'catalog_live',
'command' => 'priceChanged',
'params' => [
'productId' => $productId,
'price' => $price,
'currency' => $currency,
],
]
);
Composite Mode
If the website uses composite caching, the initial page HTML may be cached.JavaScript can still modify the loaded DOM, but dynamic catalogue data should be designed with composite mode in mind.
Prices, stock, personal discounts and other frequently changing values should usually belong to the dynamic part of the page.
Where to Put the Code
One possible project structure is:- /local/php_interface/init.php — handler registration;
- /local/php_interface/pull_events.php — functions that send Push & Pull events;
- script.js in the component template — JavaScript subscriptions;
- template.php — product data attributes;
- style.css — styling and update animations.
/local/php_interface/pull_events.php
Then include it from init.php:
require_once(
$_SERVER['DOCUMENT_ROOT']
. '/local/php_interface/pull_events.php'
);
This avoids collecting all handlers and helper logic in one large init.php file.Test Push & Pull
After installation, test the entire chain.- Open the catalogue page.
- Open the browser console.
- Check that BX.PULL exists.
- Change a product's price in the admin panel.
- Check that the price updates on the open page.
- Change the stock quantity.
- Check that the quantity changes without a reload.
console.log(BX.PULL);
If Push & Pull does not work, check the queue server and module settings separately.If Events Do Not Arrive
First, check:- Whether the pull module is loaded;
- Whether BX.PULL is initialised on the page;
- Whether the queue server is running;
- Whether moduleId and command match in PHP and JavaScript;
- Whether the server-side product-change handler runs;
- Whether the page contains an element with the required data-product-id;
- Whether the console reports JavaScript errors.
Where Real-Time Updates Are Useful
Push & Pull is useful beyond a standard product catalogue.It can be used for:
- Stock updates after a 1C exchange;
- Displaying price changes during promotions;
- B2B catalogues;
- Product availability monitoring;
- Auctions;
- Dynamic pricing;
- Product reservations;
- Status updates;
- Internal panels and dashboards.
Summary
Push & Pull updates 1C-Bitrix catalogue data without constant AJAX polling or page reloads.The basic process is straightforward:
- Track data changes on the server.
- Send a Push & Pull event.
- Subscribe to it in the browser.
- Find the product using its data attribute.
- Update the relevant DOM elements.