Hotel 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
    1175
  • 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
    747
  • 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

Hotel Website Development on 1C-Bitrix

A hotel website is not a standard catalog. The visitor isn't choosing a product — they're selecting a time slot. A room by itself is just a set of specs: square meters, bed configuration, view from the window. A room without available dates is a dead card. The entire project revolves around the availability calendar and booking engine, not around layout polish. If the booking engine works, everything else is templates and content. If it doesn't, no amount of panoramic tours or guest reviews will save the conversion rate.

Room Types: Info Block Structure

Each room type is an element in the "Room Inventory" info block. Important distinction: a room type is not a physical room. "Standard Double" might represent 20 physical rooms. Separating types from physical units is the foundational architectural decision.

Info block properties for room types:

Property Type Purpose
CAPACITY N (number) Base occupancy
CAPACITY_EXTRA N Extra beds (rollaway, crib)
AREA N Square meters
AMENITIES L (list, multiple) Wi-Fi, AC, minibar, safe, balcony
BED_TYPE L (list) Double, twin, king
VIEW L (list) Sea, city, garden, courtyard
FLOOR_RANGE S (string) Floors: "3-5"
GALLERY F (file, multiple) Photo gallery
PANORAMA_URL S 360 panorama link
ROOM_COUNT N Physical rooms of this type
MIN_STAY N Minimum nights
BASE_RATE N Base nightly rate (before seasonal adjustments)

Amenities are a multiple list property — not a Highload block — because the set is fixed (30-50 items) and doesn't need standalone management. Each list value (WIFI, AC, MINIBAR, SAFE, BALCONY, BATHTUB) maps to a frontend icon via a config file.

Photo gallery and virtual tour. Multiple file property for the gallery, rendered with Swiper.js and lazy loading. Thumbnails via CFile::ResizeImageGet() at 600x400 with BX_RESIZE_IMAGE_PROPORTIONAL. For 360 panoramas — Pannellum.js accepts an equirectangular image and renders an interactive viewer inside a <div>. The panorama URL is stored in a string property pointing to /upload/panoramas/. Hotspots (labeled points of interest within the panorama) can be stored in a JSON property or a dedicated Highload block if the hotel staff need to edit them through the admin panel.

Booking Engine and Availability Management

This is the core. Everything else is decoration. The task: a guest selects check-in and check-out dates, the system returns available room types with prices, the guest books, and the room is locked.

Storing availability — Highload block RoomInventory. Each row represents one night for one physical room.

Field Type Description
UF_DATE date Night date (2025-07-15 = night of July 15-16)
UF_ROOM_ID integer Physical room ID (not the type)
UF_ROOM_TYPE_ID integer Room type ID (info block element)
UF_STATUS integer 0 = available, 1 = booked, 2 = blocked, 3 = checked-in
UF_BOOKING_ID integer Order ID (from the sale module)
UF_RATE float Rate for this night (season-adjusted)

Why a Highload block instead of a raw table? Highload blocks in Bitrix are an ORM wrapper over a regular table with auto-generated API. HighloadBlockTable::compileEntity() produces a class with getList, add, update, delete methods. For 100 rooms over a 365-day horizon — 36,500 rows. For 500 rooms — 182,500 rows. Highload blocks handle this, but indexes are mandatory: a composite index on (UF_DATE, UF_ROOM_TYPE_ID, UF_STATUS) for availability queries and (UF_BOOKING_ID) for order lookups.

The availability check algorithm. The guest provides: check_in, check_out, guests. The system must return room types that have at least one physical room free on every night of the range.

The naive query WHERE UF_STATUS = 0 AND UF_DATE IN (...) returns rooms free on at least one night — not the same thing. The correct approach: find rooms that are free on all requested nights using GROUP BY with HAVING COUNT(*) = {nights}.

SELECT UF_ROOM_ID, UF_ROOM_TYPE_ID
FROM hl_room_inventory
WHERE UF_DATE IN ('2025-07-15','2025-07-16','2025-07-17')
  AND UF_STATUS = 0
GROUP BY UF_ROOM_ID, UF_ROOM_TYPE_ID
HAVING COUNT(*) = 3

This returns physical rooms available on all three nights. Group by UF_ROOM_TYPE_ID to get types with available unit counts.

The alternative — exclusion-based: find rooms busy on any night in the range, then select everything not in that set. Both approaches perform comparably at this data scale.

Frontend availability calendar. Two fields: check-in and check-out. Implementation — flatpickr in range mode or a custom React component built on react-day-picker. On calendar open, an AJAX request fetches the availability matrix: an array of dates with availability flags and minimum rates.

{
  "2025-07": {
    "15": {"available": true, "min_rate": 85},
    "16": {"available": true, "min_rate": 85},
    "17": {"available": false, "min_rate": null},
    "18": {"available": true, "min_rate": 120}
  }
}

Unavailable dates are disabled in the picker. The minimum rate displays on hover. This matrix query is heavy — it aggregates the entire RoomInventory for a month. Caching is mandatory: Bitrix\Main\Data\Cache keyed by availability_{month}_{year}, invalidated on any RoomInventory change via an OnAfterUpdate handler.

Seasonal rate management. The nightly rate depends on season, day of week, and occupancy levels. Storage — Highload block RatePlan:

Field Type
UF_ROOM_TYPE_ID integer
UF_DATE_FROM date
UF_DATE_TO date
UF_WEEKDAY_RATE float
UF_WEEKEND_RATE float
UF_PRIORITY integer

When calculating a booking total, the system iterates each night, finds the matching RatePlan (by date range and room type, highest priority wins), checks the day of week, and picks UF_WEEKDAY_RATE or UF_WEEKEND_RATE. The total is the sum across all nights. A Bitrix agent pre-fills UF_RATE in RoomInventory for each row — this is a calculated cache so the availability endpoint doesn't need to compute rates on every request.

Race conditions during booking. Two guests searching at the same time see the same room as available. Both click "Book." Without protection, both bookings go through — overbooking. Solution: a transactional lock at the moment of booking confirmation. Before creating the order, the code runs SELECT ... FOR UPDATE on the relevant RoomInventory rows, checks status, sets UF_STATUS = 1, then commits. If the status was already changed by another transaction, the booking is rejected with a "room no longer available" message. This must happen inside a single database transaction — $connection->startTransaction() / $connection->commitTransaction().

Channel Manager Integration

A hotel doesn't sell rooms only on its own site. Booking.com, Expedia, Ostrovok — all need synchronized availability. Without sync, overbooking is inevitable. The channel manager is the middleware layer.

iCal synchronization — the simplest approach. Booking.com and Airbnb both consume and produce .ics files with blocked dates. A Bitrix agent runs every 15 minutes:

  1. Fetches .ics from each channel's URL (cURL or file_get_contents)
  2. Parses VEVENT blocks — extracts DTSTART, DTEND, SUMMARY
  3. Updates RoomInventory: sets UF_STATUS = 2 (blocked) for the corresponding dates and rooms
  4. Generates an outbound .ics with the site's bookings, writes to /upload/ical/room_{id}.ics

iCal limitations: no rate transmission, no booking confirmation, sync delay up to 15 minutes. Acceptable for a small property (under 30 rooms). For 100+ rooms with high occupancy — you need an API connector.

OTA API integration — Booking.com Connectivity API uses OTA (OpenTravel Alliance) XML messages: OTA_HotelAvailNotifRQ for availability updates, OTA_HotelRatePlanNotifRQ for rates, OTA_HotelResNotifRS for receiving bookings. This is enterprise-grade and requires connectivity certification.

PMS Integration

PMS (Property Management System) handles check-in, check-out, housekeeping, accounting. Common systems: 1C:Hotel, Fidelio, Opera PMS.

1C:Hotel — exchange via COM object or an HTTP service on the 1C side. Bitrix sends booking data (guest, dates, room, amount), 1C creates a reservation document. Reverse sync: 1C notifies the website of status changes (checked-in, checked-out, cancelled) via webhook to a Bitrix endpoint.

Opera / Fidelio — SOAP interface. WSDL descriptor, method calls: CreateReservation, ModifyReservation, CancelReservation. Authentication via WS-Security. Bitrix-side implementation — a wrapper class over SoapClient with request/response logging.

Online Payment and Deposits

Booking goes through the sale module. An order equals one line item: "Stay in {room type}, {check_in} — {check_out}". Price — sum of nightly rates. Order properties: PROPERTY_CHECK_IN, PROPERTY_CHECK_OUT, PROPERTY_ROOM_TYPE_ID, PROPERTY_GUESTS.

Deposit payment — typically 20-30% or the first night's rate, not the full amount. Implementation: a custom handler on OnSaleBeforeOrderAdd that recalculates the payable amount. Full amount stored in PROPERTY_TOTAL_AMOUNT, payable amount in the cart's PRICE field. Remainder collected at check-in.

Payment gateways: the sale module supports YooKassa, CloudPayments, Stripe out of the box. Configuration through the admin panel, no code required.

Guest Personal Account

Authentication — email/password, OAuth via Google, Apple. After login:

  • My Bookings — order list from sale filtered by USER_ID. Statuses: awaiting payment, paid, confirmed, checked-in, completed, cancelled
  • Stay History — completed bookings with the option to leave a review
  • Loyalty Program — points earned per booking. Highload block LoyaltyPoints: USER_ID, POINTS, OPERATION (credit/debit), BOOKING_ID, DATE. Points credited via OnSaleStatusOrderChange event handler when order transitions to "Completed"

Multilingual Support

For international guests — at minimum 2-3 languages. Bitrix handles multilingual via site language versions (/en/, /de/). Info block content — through dedicated properties (NAME_EN, DESCRIPTION_EN) or through the multi-site module with info blocks bound to site IDs.

hreflang tags in <head>:

<link rel="alternate" hreflang="ru" href="https://hotel.com/rooms/standard/" />
<link rel="alternate" hreflang="en" href="https://hotel.com/en/rooms/standard/" />

Currency switcher — not real-time conversion but fixed rates in the currency module settings. Rates updated daily by a Bitrix agent pulling from a central bank API or set manually.

Schema.org Markup

Type Hotel combined with LodgingBusiness:

{
  "@context": "https://schema.org",
  "@type": "Hotel",
  "name": "Grand Hotel Riviera",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "15 Waterfront St",
    "addressLocality": "Sochi"
  },
  "starRating": {
    "@type": "Rating",
    "ratingValue": "4"
  },
  "amenityFeature": [
    {"@type": "LocationFeatureSpecification", "name": "Pool", "value": true},
    {"@type": "LocationFeatureSpecification", "name": "Parking", "value": true}
  ]
}

Each room type gets HotelRoom markup with occupancy, bed, and amenityFeature. Offers use Offer with priceSpecification and availabilityStarts.

Photo-Heavy Design

A hotel site is 60% photography. Loading requirements: WebP with JPEG fallback, <picture> elements with srcset for retina displays, lazy loading via loading="lazy". Originals up to 3000px on the long side, thumbnails — 800x600 for cards, 400x300 for lists. The iblock module auto-generates resizes through CFile::ResizeImageGet(), but WebP conversion requires server-side support (GD or Imagick with WebP) and a custom handler.

Reviews

Internal review system — Highload block Reviews: USER_ID, ROOM_TYPE_ID, RATING (1-5), TEXT, DATE, STATUS (pending/published). Publication after manual moderation or auto-publish after 24 hours. Aggregate rating recalculated on new review submission, stored in the info block property AVG_RATING.

External review integration (TripAdvisor, Google Reviews) — via widgets or API, not stored in Bitrix.

Stages and Timelines

Scale Timeline
Boutique hotel, 10-20 rooms, basic booking 4-8 weeks
Mid-size hotel, 50-100 rooms, Channel Manager, PMS 10-16 weeks
Hotel chain, multi-site, loyalty program 16-24 weeks
  1. Analysis and prototyping (1-2 weeks) — room inventory map, booking logic, wireframes
  2. Design (2-3 weeks) — photo-centric UI, mobile version, calendar components
  3. Booking engine core (3-5 weeks) — RoomInventory, availability checks, order flow, payment
  4. Integrations (2-4 weeks) — PMS, Channel Manager, payment gateways
  5. Content and SEO (1-2 weeks) — Schema.org markup, multilingual setup, meta templates
  6. Testing and launch (1-2 weeks) — load testing, cross-browser, deployment

Timelines exclude professional room photography and 360 panorama creation — those are parallel processes best started during the design phase.