Fitness Club Website Development on 1C-Bitrix
A fitness club website is not just a showcase with photos of workout spaces. It's a working tool where a member books a training session, purchases a membership, tracks remaining classes, and communicates with trainers. All of this must function automatically without manual administrator intervention, sync with the club's internal systems, and handle peak loads on Monday evenings when half the city decides to start a new life.
Let's examine how this is implemented on 1C-Bitrix — from data structure to booking logic.
Class Schedule: Highload Blocks and Filtering
The schedule is the central element of the site. Members visit it most frequently, and if the schedule is slow or inconvenient to filter — they'll switch to a competitor's Telegram bot.
Why Highload Block Instead of a Standard Infoblock. A standard infoblock (b_iblock_element) stores data using an EAV model: each property is a separate row in b_iblock_element_property. With 300 classes per week, 15 workout spaces, and 40 trainers, the properties table grows to tens of thousands of rows. Filtering by the combination of "space + day + trainer + training type" becomes a series of JOINs that on a production server take 800-1200 ms.
A Highload Block (b_hlblock_entity) is a flat table in MySQL/PostgreSQL. One row equals one class, all fields are columns. Filtering works through standard indexes.
Highload Block FitnessSchedule Structure:
| Field | Type | Purpose |
|---|---|---|
| UF_DATE | date | Class date |
| UF_TIME_START | string | Start time (HH:MM format) |
| UF_TIME_END | string | End time |
| UF_HALL_ID | integer | Workout space ID (link to HL FitnessHalls) |
| UF_TRAINER_ID | integer | Trainer ID (link to trainers infoblock) |
| UF_DIRECTION_ID | integer | Training direction: yoga, crossfit, swimming pool... |
| UF_CAPACITY | integer | Maximum number of participants |
| UF_BOOKED | integer | Current number of registered members |
| UF_STATUS | enumeration | active / cancelled / full |
| UF_IS_RECURRING | boolean | Recurring by template |
| UF_TEMPLATE_ID | integer | Link to schedule template |
For recurring classes, a separate Highload Block ScheduleTemplate is created with fields for day of week and time. A cron job (agents in Bitrix or system cron) generates specific classes for the next week from templates once a week. This allows a trainer to cancel a specific class on March 15 without breaking the entire schedule.
Frontend Filtering. We use Bitrix\Highloadblock\HighloadBlockTable::compileEntity() to get the ORM entity, then standard DataManager::getList() with a filter:
$result = $entityClass::getList([
'filter' => [
'UF_DATE' => $selectedDate,
'UF_HALL_ID' => $hallId,
'UF_STATUS' => 'active',
],
'order' => ['UF_TIME_START' => 'ASC'],
]);
On the frontend, the schedule is rendered as a grid: horizontally — workout spaces, vertically — time slots. The member switches days, filters by training type or trainer. AJAX requests work through the bitrix:highloadblock.list component or a custom REST endpoint.
Online Booking with Capacity Limits and Waitlist
Booking a class isn't just "clicking a button and getting on the list." It's a transactional operation with capacity checking, concurrent access control, and a waiting mechanism.
Main Scenario:
- Member clicks "Book" on a specific class
- System checks:
UF_BOOKED < UF_CAPACITY - If true — creates a booking record in the Highload Block
FitnessBooking, incrementsUF_BOOKED - If false — offers to join the waitlist
Concurrent Access Problem. Two members simultaneously click "Book" on a class with one remaining spot. Without locking, both get confirmation, but the trainer has an extra person.
The solution is using \Bitrix\Main\Application::getConnection() with transaction and row locking:
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
$row = $entityClass::getList([
'filter' => ['ID' => $scheduleId],
'select' => ['UF_BOOKED', 'UF_CAPACITY'],
'runtime' => [/* FOR UPDATE via raw SQL */],
])->fetch();
if ($row['UF_BOOKED'] < $row['UF_CAPACITY']) {
// create booking, increment UF_BOOKED
$connection->commitTransaction();
} else {
$connection->rollbackTransaction();
// offer waitlist
}
In practice, Bitrix's pure ORM doesn't support SELECT ... FOR UPDATE, so the critical section is wrapped in raw SQL through $connection->query().
Waitlist. A separate Highload Block FitnessWaitlist with fields: UF_SCHEDULE_ID, UF_USER_ID, UF_POSITION, UF_CREATED_AT. When someone cancels a booking, a Bitrix agent checks the waitlist and automatically moves the first person in queue to the main list, sending SMS/push through the messageservice module.
Booking Cancellation. Clubs typically allow cancellation 2-4 hours before start time. The time check logic is in the event handler, which compares UF_TIME_START with current time and blocks cancellation if the deadline has passed.
Membership Sales Through the Sale Module
Club memberships aren't catalog products. They have their own logic: validity period, visit limits, freezing.
Membership Types are implemented as infoblock elements with properties:
-
DURATION_DAYS— validity period in days -
VISIT_LIMIT— visit limit (0 = unlimited) -
TYPE— single / monthly / annual -
FREEZE_ALLOWED— can be frozen -
FREEZE_MAX_DAYS— maximum freeze duration
When purchased through \Bitrix\Sale\Order::create(), the membership is added to the cart as a regular product, but after payment, the OnSaleOrderPaid event handler triggers. It creates a record in the Highload Block UserSubscription with fields: UF_USER_ID, UF_START_DATE, UF_END_DATE, UF_VISITS_LEFT, UF_IS_FROZEN, UF_FREEZE_START.
Membership Freezing. A member clicks "Freeze" in their account. The system checks FREEZE_ALLOWED and FREEZE_MAX_DAYS, sets UF_IS_FROZEN = true, saves UF_FREEZE_START. On unfreezing — recalculates UF_END_DATE by adding the frozen days.
Integration with Club CRM Systems
Fitness clubs rarely operate only through a website. Main accounting systems are 1C:Fitness Club and Mobifitness.
1C:Fitness Club. Exchange through the standard CommerceML protocol or through 1C's REST API (HTTP services). Synchronized: service directory, schedule, client database, membership sales. Exchange runs by cron every 15-30 minutes.
Mobifitness API. REST API with token authorization. Main endpoints: /api/v1/schedule, /api/v1/bookings, /api/v1/clients. Bitrix acts as the frontend, Mobifitness — as the master schedule system. In this case, the Highload Block FitnessSchedule is filled not manually, but through API sync.
The architecture choice depends on which system is the master data source. If the club is already using Mobifitness — the site pulls the schedule from there. If the club is going digital from scratch — Bitrix can be the main system.
Member Account
The member account is built on the standard main module (authentication, profile) with extensions:
-
Visit History — query from
FitnessBookingbyUF_USER_IDwith JOIN to schedule -
Remaining Classes — field
UF_VISITS_LEFTfromUserSubscription -
Membership Renewal — button that creates an order in
salelinked to current subscription - Freeze/Unfreeze — interface for managing subscription status
Authentication is through phone with SMS code (messageservice module + custom component). Fitness audience doesn't like passwords.
Trainer Profiles
Trainers are a separate infoblock with links to training types through a multiple "Element Link" property. Each trainer has: photo, certificates (file properties), experience, description, link to their schedule (filter by UF_TRAINER_ID).
On the trainer's detail page, their schedule for the current week is automatically displayed — one AJAX request to FitnessSchedule filtered by trainer.
Implementation Timeline
| Project Scale | Scope | Timeline |
|---|---|---|
| Small club (1 space, 5-7 training types) | Schedule, booking, memberships, member account | 8-10 weeks |
| Network of 3-5 clubs | + multisite, unified database, Mobifitness integration | 14-18 weeks |
| Large network (10+ clubs) | + B2B portal for corporate clients, mobile app via Bitrix REST API, complex pricing | 20-28 weeks |
Before Starting
Before development, determine the master system for schedules: will the schedule be maintained in Bitrix or in a third-party CRM? This determines synchronization direction and Highload Block architecture. The second question is the payment gateway: for memberships with recurring billing, you need acquiring that supports recurring payments, which is a separate integration through sale.paysystem.







