Restaurant Website Development on 1C-Bitrix
A restaurant website that actually works is a system connected to the kitchen. Orders placed online go straight into the POS terminal. Table reservations land in the CRM. Menu changes in the cash register appear on the site within minutes. All of this runs on info blocks, custom components, and REST API integrations with iiko, r_keeper, or Poster. If the mapping between the Bitrix menu info block and the POS product catalog is wrong from the start, the site will show dishes the kitchen stopped serving two weeks ago.
Menu Info Block Structure
The menu is an info block where sections are categories and elements are dishes. Sections: "Breakfast", "Salads", "Mains", "Desserts", "Drinks", "Wine List". One level of nesting handles subcategories (e.g., "Red Wine" inside "Wine List").
Element (dish) properties:
-
WEIGHT— integer, grams. Displayed on the card and used in Schema.org markup -
CALORIES— integer, kcal. Optionally extended toPROTEINS,FATS,CARBSas separate numeric properties -
ALLERGENS— multiple list: gluten, lactose, nuts, seafood, eggs, soy. Filtering viaCIBlockElement::GetList()withPROPERTY_ALLERGENSin the filter array -
PRICE— numeric. Stored as a plain info block property when there is no cart. When online payment is needed, the info block is connected to the trade catalog viaCCatalog::Add() -
PHOTO— file, primary dish image. Additional shots in a multiple propertyMORE_PHOTOS -
IS_NEW— checkbox, "New" badge in the listing -
IS_SPICY— checkbox, spicy dish marker -
STOP_LIST— checkbox. Dish temporarily unavailable (ingredient ran out). The element stays in the database; it is hidden by a filter in the component template -
SORT_ORDER— integer. Controls order within a section, lets the chef push signature dishes to the top via the admin panel -
IIKO_PRODUCT_ID/RK_MENU_ITEM_ID/POSTER_PRODUCT_ID— string, UUID or integer ID from the POS system. The bridge between Bitrix and the cash register
For restaurants with seasonal menus, a SEASON property (multiple list: spring, summer, autumn, winter) and a filter by current season in component.php.
Online Ordering and POS Integration — the Hard Part
This is where most of the engineering effort goes. The restaurant runs a POS system — iiko, r_keeper, or Poster. The website must send orders into the POS in real time and track their status back.
iiko Transport API integration:
Authentication — POST /api/1/access_token with apiLogin. The token expires after 60 minutes; it is cached in a Highload block record with a TTL field.
Menu synchronization — GET /api/1/nomenclature with organizationId. The response contains the full product tree: groups (categories) and products (dishes) with UUIDs, prices, weight, modifiers, and stop-list status. A CAgent runs every 15 minutes, pulls the nomenclature, and reconciles it with the Bitrix menu info block:
- Match products by
IIKO_PRODUCT_ID. If a product exists in iiko but not in Bitrix — create an element (draft status, not published until the admin reviews it) - Compare prices. If the iiko price differs from
PRICEin the info block — update viaCIBlockElement::SetPropertyValuesEx() - Sync stop-list status. iiko marks items as unavailable; the agent sets
STOP_LIST = Yon the corresponding element - Pull modifiers (toppings, sides, sauces) into a separate "Modifiers" info block with
IIKO_MODIFIER_ID
Order creation — POST /api/1/deliveries/create. The request body maps Bitrix basket items to iiko product UUIDs:
{
"organizationId": "...",
"order": {
"phone": "+375...",
"orderTypeId": "delivery-type-uuid",
"items": [
{
"productId": "iiko-product-uuid",
"amount": 2,
"modifiers": [
{ "productId": "modifier-uuid", "amount": 1 }
]
}
],
"address": { "street": "...", "house": "...", "flat": "..." },
"comment": "No onions",
"payments": [
{ "paymentTypeId": "online-payment-uuid", "sum": 45.00 }
]
}
}
The response contains correlationId. The site stores it in the "Orders" Highload block and uses it to poll order status via GET /api/1/deliveries/by_id.
Order status tracking:
iiko supports webhooks for delivery status changes. The endpoint /api/iiko-webhook/ receives POST requests with the order ID and new status: "Confirmed", "Cooking", "On the way", "Delivered", "Cancelled". The handler updates the Highload block record and, if the client is on the /my-orders/ page, the frontend picks up the change via AJAX polling every 30 seconds.
For POS systems without webhooks — a cron job (CAgent) queries the status API every 60 seconds for all orders in "active" state.
r_keeper integration differences:
r_keeper uses UCS DeliveryPOS API. The protocol is XML-RPC, not JSON REST. Requests are wrapped in XML envelopes and parsed via SimpleXMLElement. Product mapping uses MenuItemID instead of UUID. The main complication: r_keeper typically requires a VPN tunnel to the restaurant's on-premise server, whereas iiko and Poster operate in the cloud.
Poster POS integration:
Poster exposes a REST API with OAuth. Order creation — POST /api/incomingOrders.createIncomingOrder. Product mapping by product_id. Poster sends webhook notifications on order status changes. Of the three systems, Poster is the simplest to integrate.
Table Reservation
A custom component project:table.reservation with fields: date, time, number of guests, name, phone, comment.
Data layer:
- Reservations stored in a Highload block:
DATE,TIME,GUESTS,NAME,PHONE,STATUS,TABLE_ID - Tables in a separate Highload block:
TABLE_NUMBER,CAPACITY,ZONE(main hall, terrace, VIP room) - Availability check: query reservations for the selected
DATEwith a 2-hour time window around the requestedTIME, match remaining capacity to available tables
CRM integration:
Each reservation creates a lead via crm.lead.add REST API call. The hostess sees reservations in Bitrix24 CRM, confirms them, and the status update propagates back to the Highload block. The guest receives an SMS confirmation through the messageservice module or an external SMS gateway.
Food Photography and Image Optimization
Restaurant images are heavy. Raw files from a photographer run 5-10 MB each. The site needs three sizes: thumbnail for the menu list (400x300), medium for the dish card (800x600), full-size for the lightbox (1600x1200).
Resizing via CFile::ResizeImageGet() with BX_RESIZE_IMAGE_PROPORTIONAL. Results are cached in /upload/resize_cache/.
WebP conversion — either in PHP via imagewebp() in an OnBeforeFileResize handler, or at the Nginx level with ngx_http_image_filter_module. WebP reduces file size by 25-35% compared to JPEG at equivalent visual quality.
Responsive images:
<img
src="/upload/resize_cache/menu/800x600/dish.webp"
srcset="/upload/resize_cache/menu/400x300/dish.webp 400w,
/upload/resize_cache/menu/800x600/dish.webp 800w,
/upload/resize_cache/menu/1600x1200/dish.webp 1600w"
sizes="(max-width: 640px) 400px, (max-width: 1024px) 800px, 1600px"
loading="lazy"
alt="Dish name"
>
The loading="lazy" attribute provides native lazy loading. For older browsers — IntersectionObserver as a fallback. On a menu page with 50+ dishes, this saves 30-40 MB of initial page weight.
Mobile-First Layout
Over 80% of restaurant website traffic comes from phones — people searching "restaurant near me" or "delivery menu". The template is built mobile-first:
- Category navigation — horizontal scroll with
overflow-x: auto, not a dropdown - Dish card — full-width photo, name, weight, price. "Add to order" button fixed to the bottom of the screen via
position: sticky - Order form — minimal fields. Phone + address. Name and comments are optional. Address autocomplete via Dadata API or Google Places API
- Reservation form — native
<input type="date">and<input type="time">instead of custom datepickers
Three breakpoints: 375px (phone), 768px (tablet), 1280px (desktop). Target Lighthouse Performance score above 90, LCP under 2.5 seconds.
Schema.org: Restaurant + Menu Markup
JSON-LD generated in result_modifier.php:
{
"@context": "https://schema.org",
"@type": "Restaurant",
"name": "Restaurant Name",
"servesCuisine": "Italian",
"address": {
"@type": "PostalAddress",
"streetAddress": "...",
"addressLocality": "Minsk"
},
"openingHoursSpecification": [...],
"menu": {
"@type": "Menu",
"hasMenuSection": [
{
"@type": "MenuSection",
"name": "Mains",
"hasMenuItem": [
{
"@type": "MenuItem",
"name": "Ribeye Steak",
"nutrition": {
"@type": "NutritionInformation",
"calories": "850 cal"
},
"offers": {
"@type": "Offer",
"priceCurrency": "BYN"
}
}
]
}
]
}
}
Output via $APPLICATION->AddHeadString(). Google recognizes Restaurant, Menu, and MenuItem types for Rich Snippets in search results.
Multilingual Menu
For restaurants in tourist areas — the menu in multiple languages. Two approaches in 1C-Bitrix:
-
Multi-site — separate site bindings (
LID=s1for Russian,s2for English). The info block is linked to both sites, with additional propertiesNAME_EN,DESCRIPTION_EN. Separate URLs (/menu/vs/en/menu/), properhreflangtags, independent SEO settings -
Language property — a
LANGUAGElist property (ru, en, de) with filtering in the component byLANGUAGE_ID
The multi-site approach is more robust and better for SEO.
Delivery Aggregator Integration
Yandex.Eda and Delivery Club provide partner APIs. The integration is bidirectional:
- Menu export — an XML/JSON feed generated by an agent every 30 minutes from the menu info block. Contains items, prices, photos, stop-list status
-
Order intake — a webhook from the aggregator hits
/api/aggregator-order/. The handler creates an order in the Highload block and forwards it to the POS system
This eliminates the need for staff to manually update menus in each aggregator's dashboard.
Promotions and Special Offers
A "Promotions" info block (type promotions). Properties: DATE_START, DATE_END, PROMO_TYPE (business lunch, happy hour, seasonal), DISCOUNT_PERCENT, LINKED_DISHES (multiple link to menu elements).
Display on the homepage via news.list filtered by date: >=DATE_START and <=DATE_END relative to the current date. Expired promotions hide automatically.
For business lunch — a dedicated menu section visible only between 12:00 and 16:00, controlled by a server-time check in component.php.
Development Stages
- Planning (1-2 weeks) — info block structure, POS integration scheme, page wireframes, data mapping between Bitrix and the POS
- Design (1-2 weeks) — layouts: homepage, menu (list + card), reservation, delivery, promotions
- Backend (2-4 weeks) — info blocks, menu and reservation components, POS integration, CRM, order processing
- Frontend (1-3 weeks) — responsive templates, image optimization, order and reservation forms, AJAX status updates
- Integrations (1-2 weeks) — POS system, delivery aggregators, SMS notifications, payment gateway
- Testing (1-2 weeks) — functional, test orders through POS, mobile testing, load testing
- Launch (3-5 days) — deployment, POS sync monitoring, validation on real orders
| Project Scope | Estimated Timeline |
|---|---|
| Brochure site with menu and reservations | 3-5 weeks |
| Site with online ordering and POS integration | 6-9 weeks |
| Full system: ordering, POS, aggregators, multilingual | 8-12 weeks |
Timelines depend on the POS system chosen (iiko integrates faster than r_keeper due to cloud API), the number of languages, and requirements for the customer account area.







