Restaurant website development using 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    745
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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 to PROTEINS, FATS, CARBS as separate numeric properties
  • ALLERGENS — multiple list: gluten, lactose, nuts, seafood, eggs, soy. Filtering via CIBlockElement::GetList() with PROPERTY_ALLERGENS in 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 via CCatalog::Add()
  • PHOTO — file, primary dish image. Additional shots in a multiple property MORE_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:

  1. 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)
  2. Compare prices. If the iiko price differs from PRICE in the info block — update via CIBlockElement::SetPropertyValuesEx()
  3. Sync stop-list status. iiko marks items as unavailable; the agent sets STOP_LIST = Y on the corresponding element
  4. 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 DATE with a 2-hour time window around the requested TIME, 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:

  1. Multi-site — separate site bindings (LID = s1 for Russian, s2 for English). The info block is linked to both sites, with additional properties NAME_EN, DESCRIPTION_EN. Separate URLs (/menu/ vs /en/menu/), proper hreflang tags, independent SEO settings
  2. Language property — a LANGUAGE list property (ru, en, de) with filtering in the component by LANGUAGE_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

  1. Planning (1-2 weeks) — info block structure, POS integration scheme, page wireframes, data mapping between Bitrix and the POS
  2. Design (1-2 weeks) — layouts: homepage, menu (list + card), reservation, delivery, promotions
  3. Backend (2-4 weeks) — info blocks, menu and reservation components, POS integration, CRM, order processing
  4. Frontend (1-3 weeks) — responsive templates, image optimization, order and reservation forms, AJAX status updates
  5. Integrations (1-2 weeks) — POS system, delivery aggregators, SMS notifications, payment gateway
  6. Testing (1-2 weeks) — functional, test orders through POS, mobile testing, load testing
  7. 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.