Integrating 1C-Bitrix with Zebra Label Printers
Zebra is the industrial standard for thermal label printing. ZPL (Zebra Programming Language) is the command language that controls the printer. The integration goal with 1C-Bitrix is to automatically generate and send a label to the printer when a specific event occurs — goods receipt, order shipment, price change — without any operator involvement.
How Zebra Printing Works
Zebra printers communicate over the network via TCP/IP (port 9100 — RAW print) or through a web interface. ZPL commands are sent as plain text to a TCP socket:
function printZpl(string $printerIp, int $printerPort, string $zpl): bool
{
$socket = fsockopen($printerIp, $printerPort, $errno, $errstr, 5);
if (!$socket) {
\Bitrix\Main\Diag\Debug::writeToFile("Zebra: $errstr ($errno)", '', '/bitrix/zebra_errors.log');
return false;
}
fwrite($socket, $zpl);
fclose($socket);
return true;
}
For Windows environments with a shared printer — an alternative approach using a local Python/Node.js agent that receives jobs from 1C-Bitrix via HTTP and forwards them to the printer via WinSpool.
Generating a ZPL Product Label
Product label (price + barcode + SKU):
class ZebraLabelGenerator
{
public function generatePriceLabel(array $product, string $priceType = 'BASE'): string
{
$price = \CCatalogProduct::GetOptimalPrice($product['ID'])['PRICE']['PRICE'] ?? 0;
$barcode = $this->getBarcode($product['ID']);
$name = mb_substr($product['NAME'], 0, 30); // truncated for 58mm label
return implode("\n", [
'^XA', // Start of label
'^CI28', // UTF-8 encoding
'^PW464', // Width 58mm (8 dots/mm)
'^LL200', // Height 25mm
// Product name
'^FO20,10^A0N,24,24^FD' . $name . '^FS',
// SKU
'^FO20,40^A0N,18,18^FD' . ($product['PROPERTY_ARTICLE_VALUE'] ?? '') . '^FS',
// Price
'^FO20,65^A0N,36,36^FD' . number_format($price, 2, '.', ' ') . '^FS',
// EAN-13 barcode
'^FO20,110^BY2^BCN,50,Y,N,N^FD' . $barcode . '^FS',
'^XZ', // End of label
]);
}
private function getBarcode(int $productId): string
{
$row = \Bitrix\Catalog\ProductBarcodeTable::getList([
'filter' => ['PRODUCT_ID' => $productId],
'limit' => 1,
])->fetch();
return $row['BARCODE'] ?? str_pad($productId, 12, '0', STR_PAD_LEFT);
}
}
Label Templates in the Database
ZPL templates are stored in the bl_zebra_templates table rather than in code. This allows the layout to be changed without a deployment:
CREATE TABLE bl_zebra_templates (
id SERIAL PRIMARY KEY,
code VARCHAR(64) UNIQUE NOT NULL, -- 'price_label', 'warehouse_label', 'shipment_label'
name VARCHAR(255),
zpl_template TEXT NOT NULL, -- ZPL with placeholders {{NAME}}, {{PRICE}}, {{BARCODE}}
label_width SMALLINT DEFAULT 58, -- mm
label_height SMALLINT DEFAULT 40,
active BOOLEAN DEFAULT true
);
The ZebraTemplateEngine class replaces placeholders with real data and sends the result to the printer.
Automatic Printing on Events
On price change (event OnProductUpdate or agent):
AddEventHandler('catalog', 'OnProductUpdate', function($productId) {
$needPrint = \Bitrix\Main\Config\Option::get('zebra_module', 'auto_print_price_change', 'N');
if ($needPrint !== 'Y') return;
$product = \CIBlockElement::GetByID($productId)->GetNext();
$printerIp = \Bitrix\Main\Config\Option::get('zebra_module', 'default_printer_ip');
$zpl = (new ZebraLabelGenerator())->generatePriceLabel($product);
ZebraPrinter::send($printerIp, 9100, $zpl);
});
On goods receipt — after processing the receipt document:
// In the receipt document handler
foreach ($receiptItems as $item) {
for ($i = 0; $i < $item['qty']; $i++) {
$jobs[] = [
'product_id' => $item['product_id'],
'copies' => 1,
'template' => 'warehouse_label',
'printer_id' => $item['warehouse_printer_id'],
];
}
}
ZebraPrintQueue::push($jobs);
The print queue bl_zebra_print_queue is processed by an agent every minute. This is more reliable than direct printing inside an event handler — the printer may be temporarily unavailable.
Queue and Retries
CREATE TABLE bl_zebra_print_queue (
id SERIAL PRIMARY KEY,
printer_id INT NOT NULL,
template_id INT NOT NULL,
data_json JSONB NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
attempts SMALLINT DEFAULT 0,
scheduled_at TIMESTAMP DEFAULT NOW(),
printed_at TIMESTAMP
);
On a print error, the status is set to retry and attempts is incremented. After 5 attempts the status becomes failed and the administrator is notified.
Timeline
| Phase | Duration |
|---|---|
| ZPL send class | 1 day |
| Template engine with placeholders | 2 days |
| Print queue + agent | 1 day |
| Event handlers (price change, goods receipt) | 2 days |
| Printer management admin interface | 2 days |
| Testing with a real printer | 1 day |
| Total | 9–11 days |

