A visitor to a real estate website applies 4–6 filters, switches between list and map view, and saves properties to favorites. If the search is sluggish, they leave for aggregators. Our building real estate platforms using 1C-Bitrix with separate information blocks and a spatial index solves this problem three times faster than typical solutions, delivering real savings — the portal pays for itself in 6–8 months (given the average check of 5 million RUB).
Information Block Architecture: How to Store Real Estate Objects
When developing property portals on Bitrix, the first question is one information block for all types or separate blocks for apartments, houses, and commercial properties. The answer depends on the number of unique properties.
One information block plus sections by type works if properties overlap by 70% or more. Area, price, address, coordinates, photos — they are common. Number of rooms applies only to apartments, land area only to houses. Empty properties do not occupy space in b_iblock_element_property (a row is not created if the value is null). Advantages: unified bitrix:catalog.smart_filter across all types, unified search results, and a single list template.
Separate information blocks are justified when commercial real estate has 15 properties that residential lacks (class of premises, lease type, ceiling height, freight elevator, electrical capacity). Mixing them in one block clogs the filter. Disadvantage: aggregated search across all types requires a custom component with UNION logic or Elasticsearch.
Recommended structure for a portal with 10K+ objects:
- Information block "Residential Real Estate" — sections: Apartments, Houses, Townhouses
- Information block "Commercial Real Estate" — sections: Offices, Retail, Warehouses
- Highload block "Residential Complexes" — directory of complexes linked to objects via
PROPERTY_COMPLEX_ID - Highload block "Districts" — directory with polygons for map search
- Highload block "Developers" — companies with details and logos
Property object properties — at least 30 fields. Critically important for filtering:
| Property | Type | Indexing |
|---|---|---|
| PRICE | N (number) | Facet index |
| AREA | N | Facet index |
| ROOMS | L (list) | Facet index |
| FLOOR | N | Facet index |
| FLOORS_TOTAL | N | Regular |
| DISTRICT | S:Highload | Facet index |
| COMPLEX_ID | S:Highload | Facet index |
| COORDINATES | S (string, "lat,lng") | No |
| DEAL_TYPE | L (sale/rent) | Facet index |
Coordinates are stored as a string "55.7558,37.6173" — parsed on the front end for map display. Storing them in two separate properties (LATITUDE, LONGITUDE) is not worth it — there is no scenario where filtering by a single coordinate is needed.
Organizing Map Filtering
Developing a real estate website on 1C-Bitrix requires thoughtful map filtering. The built-in bitrix:catalog.smart_filter covers 60% of the tasks — checkboxes, lists, ranges. But real estate needs things it cannot do out of the box.
Range sliders for price and area. Smart filter provides MIN_VALUE and MAX_VALUE for numeric properties through the $arResult['ITEMS'] array. On the front end, we build two inputs plus a slider using noUiSlider or rc-slider. Values are passed via GET parameters ?arrFilter_P1_MIN=3000000&arrFilter_P1_MAX=8000000. Problem: each slider change causes a page reload. Solution — AJAX loading of results via bitrix:catalog.section with AJAX_MODE=Y and custom JavaScript that intercepts the filter form submission.
Map filtering — drawing a search area. The user draws a polygon or circle on the map, and the system returns objects inside that area. The built-in smart filter cannot do this. Implementation:
- On the front end — Yandex.Maps API,
ymaps.GeoObjectwitheditable: trueorymaps.Polygonvia the drawing tool. The user draws the area, JavaScript collects the array of polygon vertex coordinates. - AJAX request to the backend with the polygon coordinates. On the PHP side — Ray Casting algorithm (point-in-polygon): for each object, we check whether its coordinates fall inside the polygon. For 50K objects, a full scan takes 20-40ms, acceptable. For 200K+, a spatial index is needed.
- Spatial index on MySQL/MariaDB: a
POINTcolumn of type GEOMETRY, a SPATIAL index, a query usingST_Contains(polygon, point). However, Bitrix does not store information block properties as GEOMETRY. Solution — an additional tableproject_realty_geowith fieldsELEMENT_ID,LOCATION POINT, and a SPATIAL INDEX. Synchronization through theOnAfterIBlockElementUpdateevent handler.
// Coordinate synchronization handler
EventManager::getInstance()->addEventHandler(
'iblock', 'OnAfterIBlockElementUpdate',
function ($arFields) {
if ($arFields['IBLOCK_ID'] !== REALTY_IBLOCK_ID) return;
GeoIndexService::updatePoint(
$arFields['ID'],
$arFields['PROPERTY_VALUES']['COORDINATES']
);
}
);
- The AJAX request returns an array of
ELEMENT_ID, which is passed to the mainCIBlockElement::GetListvia the filter['ID' => $geoFilteredIds]. Thus, spatial filtering is combined with the smart filter.
Marker clustering. With 5K+ markers on the map, Yandex.Maps starts to lag. ymaps.Clusterer automatically groups nearby markers. But when zooming into a district with 200 objects in one building (residential complex), the cluster expands into a mess. Solution — a custom ClusterPlacemark with a tooltip like "14 apartments in the Solnechny complex" and a link to the filtered list.
AJAX update of both map and list synchronously. The user moves the price slider → the list updates → markers on the map update. The user moves the map → the list updates to show objects in the visible area. This requires a single state controller on the front end. Architecture: a React/Vue component (or vanilla JS with pub/sub) that holds the current filters plus the visible map area (bounding box), and when any parameter changes, makes one AJAX request. The response contains both the HTML of the list and JSON coordinates for markers.
// Pseudocode for map and list synchronization
function updateResults() {
const filters = collectFilters(); // smart filter values
const bounds = map.getBounds(); // [[lat1,lng1],[lat2,lng2]]
filters.geo_bounds = bounds;
fetch('/api/realty/search/', {
method: 'POST',
body: JSON.stringify(filters)
})
.then(r => r.json())
.then(data => {
renderList(data.html);
renderMarkers(data.markers); // [{id, lat, lng, price, title}]
});
}
map.events.add('boundschange', debounce(updateResults, 300));
filterForm.addEventListener('change', updateResults);
Why Is Spatial Indexing Important?
Without a spatial index, polygon search on 50K objects using Ray Casting takes 20-40ms, which is acceptable. But with 500K objects, the time rises to 400ms — the user will notice a delay. A SPATIAL index on a POINT column reduces the query to 5-10ms. Therefore, for large portals, we always create a separate geo-table synchronized via the OnAfterIBlockElementUpdate event.
According to the documentation, faceted indexes are mandatory for catalogs with a large number of elements — their use reduces filtering time by up to 50 times.1C-Bitrix Documentation
Property Card
An apartment card has 30+ fields, a photo gallery, video tour, 3D panorama, location on a map, nearby infrastructure, and a mortgage calculator. All of this is one bitrix:news.detail call with a custom template.
Photo gallery — a multiple property of type "File". Output via Swiper.js with lazy loading. Preview — CFile::ResizeImageGet() with BX_RESIZE_IMAGE_PROPORTIONAL, 400x300. Fullscreen view — original up to 1920px.
3D panorama and video tour. Panorama — an iframe with Matterport, Kuula, or a custom viewer based on Pannellum.js. Video tour — YouTube/Vimeo embed. Both are stored as string properties with URLs. In the template — conditional rendering: if PROPERTY_PANORAMA_URL is filled, show the "3D Tour" tab.
Mortgage calculator — pure JavaScript, no server requests. Annuity payment formula, three sliders (price, down payment, term), result — monthly payment. Rates are pulled from the Highload block "Partner Banks" when the page loads.
Integrations with External Systems
XML feeds for aggregators. CIAN, Avito, Yandex.Realty — each has its own XML format. Common logic: a Bitrix agent (CAgent) runs once per hour, selects active objects, generates XML, and places it in /upload/feeds/.
-
Yandex.Realty — format
realty-feed, root element<realty-feed>, inside<offer>with mandatory fieldstype,category,location,price,area,image -
CIAN — format
cian-feed, element<object>, its own category system (flatSale,flatRent,commercialSale), mandatory fields differ from Yandex -
Avito — format Avito Autoload, element
<Ad>, categoryНедвижимость, subcategory depends on type
Each feed is a separate generator class inheriting the abstract BaseFeedGenerator. Mapping of information block properties to XML fields is in a config file, not in code. This allows adding a new aggregator without a developer.
Feed size: 10K objects → XML ~15MB. Generation takes 30-60 seconds. For 50K+ objects, generation can take 5 minutes — we move it to a background task or chunk it.
CRM integration and agents. A request from a property card → lead in Bitrix24. REST API: crm.lead.add with fields TITLE, SOURCE_ID, UF_CRM_REALTY_ID (custom field — object ID). Webhook or OAuth depends on whether it's one portal or multiple.
Realtors — a separate information block or Highload block. Each object is linked to an agent via PROPERTY_AGENT_ID. On the agent's page — their objects, contacts, rating. An authorized agent can edit their objects via bitrix:iblock.element.edit.form with a restriction by CREATED_BY.
User Functionality: Favorites and Comparison
Favorites for unauthenticated users — cookies or localStorage. An array of object IDs, max 50. On the server — a middleware in init.php that checks the REALTY_FAVORITES cookie on each request and adds a flag IN_FAVORITES to $arResult. For authenticated users — a Highload block UserFavorites with fields USER_ID, ELEMENT_ID, DATE_ADD.
Comparison — similar, but with a table of properties in two or three columns. The component reads IDs from cookies/Highload, runs GetList on the array of IDs, and renders a table with horizontal scrolling.
SEO for Thousands of Pages
Manual entry of meta tags for each of the 10K apartments is impossible. SEO templates through information block settings:
-
#ELEMENT_NAME#— object name -
UF_DISTRICT— district, substituted via theOnBeforeIBlockElementSeohandler - Formula:
Buy {type} {rooms}-room in {district} — {price} ₽ | {site}
Schema.org RealEstateListing microdata — in template.php of the property card:
{
"@context": "https://schema.org",
"@type": "RealEstateListing",
"name": "2-room apartment, 65 m², Central District",
"url": "https://site.ru/kvartiry/123/",
"datePosted": "2025-01-15",
"offers": {
"@type": "Offer",
"price": "8500000",
"priceCurrency": "RUB"
}
}
Canonical URLs, hreflang for multilingual content, XML sitemap via the seo module split by information blocks — up to 50K URLs per file.
Performance on 50K+ Objects
Faceted indexes — mandatory. Without them, bitrix:catalog.smart_filter on 50K elements with 15 filterable properties takes 3-5 seconds. With a faceted index — 50-150ms — a 50x improvement. Rebuilding via Bitrix\Iblock\PropertyIndex\Manager::buildIndex($iblockId), triggered by cron after mass updates.
Pagination — bitrix:system.pagenavigation with lazy scroll (load on scroll). LIMIT + OFFSET on large selections degrades — at OFFSET 40000, MySQL still scans 40K rows. Alternative — cursor pagination by ID > $lastId, but built-in components do not support it. For the first 200 pages, OFFSET is acceptable.
Development Stages
1. Analytics, prototyping — 1-2 weeks: data structure, filter map, Figma prototypes. 2. Design — 2-3 weeks: UI for card, list, map, mobile version. 3. Core development — 4-6 weeks: information blocks, filtering, map, property card. 4. Integrations — 2-3 weeks: XML feeds, CRM, mortgage calculator. 5. Testing, optimization — 1-2 weeks: load, SEO, cross-browser. 6. Launch — 3-5 days: deployment, data import, monitoring.| Scale | Timelines | Estimated Cost Range (RUB) |
|---|---|---|
| Agency site, up to 500 objects | 6-10 weeks | 600,000 – 1,000,000 |
| City portal, 5-10K objects, map + filters | 10-16 weeks | 1,500,000 – 2,500,000 |
| Federal portal, 50K+ objects, feeds, CRM | 14-24 weeks | 3,000,000 – 5,000,000 |
Deliverables and What’s Included
- Detailed technical documentation (architecture, setup guide, API description)
- Adaptive page design (card, list, map)
- Development of filtering with map and area drawing mode
- Integration with aggregators (CIAN, Avito, Yandex.Realty) via XML feeds
- Integration of a mortgage calculator
- SEO template setup and microdata generation
- CRM integration (Bitrix24, amoCRM) with lead sync
- Load testing and performance optimization report (e.g., achieving TTFB under 300ms)
- Administrator training (2 sessions of 2 hours)
- Full source code access and deployment instructions
- 3 months of warranty support after launch (bug fixes, consultations)
- Optional: ongoing maintenance contract with monthly SEO reports
Timelines do not include content filling and advertising campaign setup — these are parallel processes that start at the testing stage. Contact us for an evaluation of your project — we will prepare a detailed proposal within a day. Get a consultation and understand how your future portal will generate leads.
With over 10 years of experience in Bitrix development and 50+ real estate projects, we guarantee results. On a recent project, savings on server resources after implementing a spatial index amounted to more than 150,000 RUB per year, and maintenance costs were reduced by 20%. Typical conversion rates for our portals range from 3% to 5%, with average lead costs 30% lower than industry benchmarks.







