Real Estate Website Development on 1C-Bitrix
A real estate website is a catalog where the user isn't browsing products — they're searching for a place to live. The average visitor spends 8-12 minutes on a property portal, applies 4-6 filters, switches between list and map views, and saves 3-5 listings to favorites. If filtering lags, the map loads in chunks, or the property card doesn't answer "how far to the subway" — the visitor leaves for Zillow. Bitrix can power this kind of portal, but only if the data architecture is right before the first line of code.
Info Block Architecture: Storing Property Listings
The first question — one info block for all property types or separate info blocks for apartments, houses, and commercial. The answer depends on how many unique properties each type has.
Single info block with sections per type works when properties overlap by 70%+. Area, price, address, coordinates, photos — shared across all types. Room count is apartments only, lot size is houses only. Empty properties don't create rows in b_iblock_element_property (no row if value is null). Advantage: a single bitrix:catalog.smart_filter across all types, unified search results, one list template.
Separate info blocks make sense when commercial properties have 15 fields that residential doesn't — building class, lease type, ceiling height, freight elevator, electrical capacity. Mixing them into one info block clutters the filter panel. Downside: aggregated search across all types requires a custom component with UNION logic or Elasticsearch.
Recommended structure for a portal with 10K+ listings:
- Info block "Residential" — sections: Apartments, Houses, Townhouses
- Info block "Commercial" — sections: Offices, Retail, Warehouses
-
Highload block "Residential Complexes" — reference table, linked from listings via
PROPERTY_COMPLEX_ID - Highload block "Districts" — reference with polygon data for map-based search
- Highload block "Developers" — companies with details and logos
A property listing needs at least 30 fields. The ones critical for filtering:
| Property | Type | Indexing |
|---|---|---|
| PRICE | N (number) | Faceted index |
| AREA | N | Faceted index |
| ROOMS | L (list) | Faceted index |
| FLOOR | N | Faceted index |
| FLOORS_TOTAL | N | Standard |
| DISTRICT | S:Highload | Faceted index |
| COMPLEX_ID | S:Highload | Faceted index |
| COORDINATES | S (string, "lat,lng") | None |
| DEAL_TYPE | L (sale/rent) | Faceted index |
Coordinates are stored as a string "55.7558,37.6173" — parsed on the frontend for map rendering. Splitting into two properties (LATITUDE, LONGITUDE) adds no value — there's no scenario where you filter by a single coordinate.
Filtering and Map Integration: The Hard Part
The built-in bitrix:catalog.smart_filter handles about 60% of the job — checkboxes, dropdowns, ranges. But real estate demands things it can't do out of the box.
Range sliders for price and area. Smart filter exposes MIN_VALUE and MAX_VALUE for numeric properties in $arResult['ITEMS']. On the frontend, you build two inputs plus a slider using noUiSlider or rc-slider. Values go through GET parameters like ?arrFilter_P1_MIN=200000&arrFilter_P1_MAX=500000. The problem: every slider change triggers a page reload. Solution — AJAX loading via bitrix:catalog.section with AJAX_MODE=Y and custom JavaScript that intercepts the filter form submit.
Drawing a search area on the map. The user draws a polygon or circle on the map, and the system returns listings inside that area. Smart filter has no concept of geospatial queries. Implementation:
-
Frontend — Yandex.Maps API (or Google Maps),
ymaps.Polygonwith a drawing tool. The user draws an area, JavaScript collects the vertex coordinates. -
AJAX request to the backend with polygon coordinates. Server-side — Ray Casting algorithm (point-in-polygon): check each listing's coordinates against the polygon. With 50K listings, brute force takes 20-40ms — acceptable. At 200K+ — you need a spatial index.
-
Spatial index on MySQL/MariaDB: a
POINTcolumn of type GEOMETRY, SPATIAL INDEX, queried viaST_Contains(polygon, point). But Bitrix doesn't store info block properties as GEOMETRY. Solution — a supplementary tableproject_realty_geowith fieldsELEMENT_ID,LOCATION POINT, and a SPATIAL INDEX. Synced via theOnAfterIBlockElementUpdateevent handler. -
The AJAX response returns an array of
ELEMENT_IDvalues, passed into the mainCIBlockElement::GetListvia['ID' => $geoFilteredIds]. This way spatial filtering combines with smart filter parameters.
Marker clustering. Beyond 5K markers, Yandex.Maps starts lagging. ymaps.Clusterer groups nearby markers automatically. But when zoomed into a block with 200 apartments in one building (a residential complex), the cluster explodes into noise. Solution — a custom ClusterPlacemark showing "14 apartments in Sunrise Complex" with a link to a filtered list.
Synchronizing map and list updates. User moves the price slider — the list updates — the map markers update. User pans the map — the list shows only listings in the visible area. This requires a single state controller on the frontend. Architecture: a JavaScript controller (React component, Vue component, or vanilla pub/sub) that holds current filters + the map's visible bounding box. Any parameter change triggers one AJAX request. The response contains both the list HTML and a JSON array of marker coordinates.
// Map + list sync pseudocode
function updateResults() {
const filters = collectFilters();
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);
Property Card
A property card has 30+ fields, a photo gallery, video tour, 3D panorama, map with nearby infrastructure, and a mortgage calculator. All rendered through a single bitrix:news.detail call with a custom template.
Photo gallery — a multiple file-type property. Rendered with Swiper.js and lazy loading. Thumbnails via CFile::ResizeImageGet() at 400x300 with BX_RESIZE_IMAGE_PROPORTIONAL. Full-screen view — originals up to 1920px.
3D panorama and video tour. Panorama — iframe with Matterport, Kuula, or a self-hosted viewer on Pannellum.js. Video tour — YouTube/Vimeo embed. Both stored as string properties with URLs. Template conditionally renders a "3D Tour" tab when PROPERTY_PANORAMA_URL is populated.
Mortgage calculator — pure JavaScript, no server calls. Annuity payment formula, three sliders (price, down payment, term), output: monthly payment. Rates are pulled from a "Partner Banks" Highload block on page load.
XML Feeds for Aggregators
CIAN, Avito, Yandex.Realty — each has its own XML format. The shared pattern: a Bitrix agent (CAgent) runs hourly, selects active listings, generates XML, writes to /upload/feeds/.
-
Yandex.Realty —
realty-feedformat, root element<realty-feed>, child<offer>nodes with required fieldstype,category,location,price,area,image -
CIAN —
cian-feedformat,<object>elements, its own category system (flatSale,flatRent,commercialSale), different required fields -
Avito — Avito Autoload format,
<Ad>elements, category "Real Estate" with subcategories by type
Each feed is a separate generator class extending an abstract BaseFeedGenerator. Property-to-XML mapping lives in config, not in code — adding a new aggregator doesn't require a developer.
Feed size: 10K listings produce ~15MB of XML. Generation takes 30-60 seconds. At 50K+ listings, generation can hit 5 minutes — offload to a background task or split into chunks.
CRM Integration and Agent Profiles
An inquiry from a property card creates a lead in Bitrix24 via REST API: crm.lead.add with fields TITLE, SOURCE_ID, UF_CRM_REALTY_ID (custom field — listing ID). Webhook or OAuth — depends on whether it's one portal or multiple.
Agents/realtors — a separate info block or Highload block. Each listing is linked to an agent via PROPERTY_AGENT_ID. The agent's page shows their listings, contact info, and rating. An authorized agent can edit their own listings through bitrix:iblock.element.edit.form restricted by CREATED_BY.
Favorites and Comparison
Favorites for unauthenticated users — cookies or localStorage. An array of listing IDs, capped at 50. Server-side — middleware in init.php checks the REALTY_FAVORITES cookie on each request and adds an IN_FAVORITES flag to $arResult. For authenticated users — a Highload block UserFavorites with USER_ID, ELEMENT_ID, DATE_ADD.
Comparison works similarly but renders a side-by-side property table in two or three columns. The component reads IDs from cookies/Highload, runs GetList on the ID array, and renders a horizontally scrollable table.
SEO at Scale
Writing meta tags by hand for 10K apartments isn't realistic. SEO templates via info block settings handle this:
-
#ELEMENT_NAME#— listing title - District name injected via
OnBeforeIBlockElementSeohandler - Formula:
Buy {type} {rooms}-bed in {district} — ${price} | {site}
Schema.org RealEstateListing microdata in the property card template:
{
"@context": "https://schema.org",
"@type": "RealEstateListing",
"name": "2-bed apartment, 65 sqm, Central District",
"url": "https://site.com/apartments/123/",
"datePosted": "2025-01-15",
"offers": {
"@type": "Offer",
"price": "120000",
"priceCurrency": "USD"
}
}
Canonical URLs, hreflang for multilingual setups, XML sitemap via the seo module — split by info blocks, up to 50K URLs per file.
Performance with 50K+ Listings
Faceted indexes are non-negotiable. Without them, bitrix:catalog.smart_filter on 50K elements with 15 filterable properties takes 3-5 seconds. With faceted indexes — 50-150ms. Rebuilt via Bitrix\Iblock\PropertyIndex\Manager::buildIndex($iblockId), triggered by cron after bulk updates.
Pagination — bitrix:system.pagenavigation with infinite scroll. LIMIT + OFFSET degrades on large datasets — at OFFSET 40000, MySQL still scans 40K rows. The alternative is cursor-based pagination on ID > $lastId, but built-in components don't support it. For the first 200 pages, OFFSET is acceptable.
Stages and Timelines
- Analysis, prototyping (1-2 weeks) — data structure, filter map, Figma prototypes
- Design (2-3 weeks) — property card UI, list view, map view, mobile layouts
- Core development (4-6 weeks) — info blocks, filtering, map, property cards
- Integrations (2-3 weeks) — XML feeds, CRM, mortgage calculator
- Testing, optimization (1-2 weeks) — load testing, SEO, cross-browser
- Launch (3-5 days) — deployment, data import, monitoring
| Scale | Timeline |
|---|---|
| Agency site, up to 500 listings | 6-10 weeks |
| City portal, 5-10K listings, map + filters | 10-16 weeks |
| National portal, 50K+ listings, feeds, CRM | 14-24 weeks |
Timelines exclude content population and ad campaign setup — those are parallel processes that start during the testing phase.







