Custom Cart Development for 1C-Bitrix
The standard bitrix:sale.basket.basket component covers the basics but breaks down under non-standard business requirements: there is no cross-session cart persistence without authentication, it is impossible to add custom fields to a line item (such as engraving text or packaging colour), and the component's markup is a complex template with an outdated structure. In such cases, the cart is built from scratch or the standard component is heavily reworked.
How the Bitrix Cart Works
The cart is stored in the b_sale_basket table. Each line item is a row with the following fields: FUSER_ID (anonymous user identifier from the session), PRODUCT_ID, QUANTITY, PRICE, CUSTOM_PRICE (manual price flag), NOTES, PRODUCT_XML_ID, and a set of PROPS fields — serialised line-item properties stored in b_sale_basket_props.
Working with the cart via API:
// Adding a product
$basket = \Bitrix\Sale\Basket::loadItemsForFUser(
\Bitrix\Sale\Fuser::getId(),
\Bitrix\Main\Context::getCurrent()->getSite()
);
$item = $basket->createItem('catalog', $productId);
$item->setFields([
'QUANTITY' => 2,
'CURRENCY' => 'RUB',
'LID' => 's1',
'PRODUCT_PROVIDER_CLASS' => '\Bitrix\Catalog\Product\CatalogProvider',
]);
$basket->save();
For AJAX cart operations (add, change quantity, remove), either the \Bitrix\Sale\Compatible\DiscountCompatibility controller or a custom AJAX handler via \Bitrix\Main\Engine\Controller is used.
What Actually Requires Custom Development
Custom line-item properties. The standard cart does not allow adding arbitrary fields to a line item — such as engraving text, selected packaging size, or a specific delivery date for an item. Under the hood, this is the b_sale_basket_props table with NAME, VALUE, and CODE fields. For custom properties you need to:
- Add UI to the product page template (modal window, form fields)
- Pass properties in the AJAX request when adding to the cart
- In the handler, add them via
$item->getPropertyCollection()->createItem() - Display them in the cart template
Cart persistence without authentication. By default, FUSER_ID is a session ID, and the cart disappears when the browser or device changes. To persist it, store FUSER_ID in a cookie with a long TTL (30–90 days) and create a record in b_sale_fuser on the first visit, linked to that cookie. On login — merge the anonymous cart with the user's cart via \Bitrix\Sale\Basket::mergeBasket.
Mini-cart in the header with AJAX updates. The standard bitrix:sale.basket.small component queries the database on every request. On high-traffic stores this is noticeable: 200 RPS × N queries to b_sale_basket. Solution: store the cart counter in Redis (via \Bitrix\Main\Data\Cache with tagging) and update it only on changes, not on every page render.
Multi-currency cart. When selling in multiple currencies, prices must be correctly converted via CCurrencyRates::ConvertCurrency, storing both the original price in the foreign currency and its equivalent in the base currency. The standard component handles this, but without the ability to show "price in your currency" alongside the USD price.
Case Study: Cart with a Configurator for a Construction Supply Store
Client — an online store for construction materials. Problem: a customer selects tiles and needs to specify the laying area, breakage percentage, and laying pattern (straight/diagonal). The final number of boxes and the total price must be calculated in real time directly in the cart.
Solution: a cart component was developed in Vue.js that communicates with the backend via REST API (custom controller). The configurator is a separate component embedded in the cart line item row. Configuration data is stored in b_sale_basket_props with the codes AREA, BREAKAGE_PCT, and LAYOUT_TYPE. When parameters change — recalculation happens via AJAX without a page reload. The complexity: catalog prices are stored per unit, but purchases are made by the box — conversion in both directions had to be implemented.
Result: cart conversion increased because the customer sees the final price before placing the order, rather than receiving clarification by phone.
Architectural Decisions During Development
The choice of approach depends on requirements:
| Approach | When to use | Effort |
|---|---|---|
| Custom component template | Visual changes only | 1–2 days |
| Component override | Logic changes without third-party data | 3–5 days |
| Custom component + REST API | Complex logic, Vue/React frontend | 2–4 weeks |
| Headless (Next.js + Bitrix API) | Full frontend control | 4+ weeks |
When developing a custom cart, always account for:
- Maintaining compatibility with the
salemodule — discounts, coupons, and cart rules must continue to work - Correct recalculation on quantity change via
OnSaleBasketBeforeSaved - Server-side validation: never trust a price coming from the client
- Handling the scenario where a product sold out while sitting in the cart
Timeline and Phases
Custom cart development from analysis to delivery takes 5 working days to 6 weeks depending on complexity:
- Cart audit and requirements specification — 1–2 days
- Server-side development (API, event handlers) — 3–10 days
- Frontend development — 2–15 days
- Integration with
sale,catalog, andcurrencymodules — 1–5 days - Testing (unit, integration, load) — 1–3 days







