Custom Classified Ads Board Development on 1C-Bitrix
A classifieds board is not just a "product list." Users create content, requiring moderation, flexible search with filters, status and expiration management. Without a ready solution, you have to build this logic from scratch. We have already implemented such projects, so we know the typical pitfalls: for example, search slowdown at 50,000 ads, or caching issues with a large number of VIP ads. The cost of fixing these mistakes can be comparable to the initial development, so it's crucial to lay the correct architecture from day one.
Our approach is to use infoblocks, HL-blocks, components 2.0, and tagged caching. According to official documentation, component 2.0 allows caching of individual template parts, which is critical for performance. In this article, we'll break down the key blocks of a classifieds board: from data structure to the expiration agent.
How is the infoblock structure for ads?
The infoblock is the main storage. Symbolic code: classifieds. Type: ADS. Required properties:
| Property | Code | Type |
|---|---|---|
| Price | PRICE |
Number |
| Deal type | DEAL_TYPE |
List (Sell/Buy/Exchange/Free) |
| City | CITY |
Directory (HL-block) |
| Seller phone | PHONE |
String |
| Status | AD_STATUS |
List (Active/Moderation/Rejected/Expired) |
| Expiration date | EXPIRE_DATE |
Date |
| Views | VIEW_COUNT |
Number |
| VIP | IS_VIP |
Flag |
| User | USER_ID |
Number (FK to b_user) |
| Photos | PHOTOS |
File (multiple) |
| Creation date | DATE_CREATE |
Date (automatic) |
Infoblock sections — ad categories (Transport, Real Estate, etc.). Section hierarchy — tree via b_iblock_section. This structure ensures fast search and filtering by categories.
User ad submission: step-by-step
The add form is at /ads/add/. Process consists of several steps:
- Authorization: if not logged in, redirect to
/auth/. - Form filling: user enters title, description, price, category, uploads photos.
- Validation: check required fields, phone format, image sizes.
- Send to moderation: ad is saved with
ACTIVE='N'and statusMODERATION; moderators receive notification. - Confirmation: after successful save, user sees success message.
Key point: user uploads content — need spam protection and mandatory moderation.
// /local/components/local/ads.add/class.php
namespace Local\Ads;
class AdsAddComponent extends \CBitrixComponent
{
public function executeComponent(): void
{
if (!$GLOBALS['USER']->IsAuthorized()) {
LocalRedirect('/auth/?backurl=/ads/add/');
return;
}
if ($this->request->isPost() && check_bitrix_sessid()) {
$this->addAd();
}
$this->includeComponentTemplate();
}
private function addAd(): void
{
$el = new \CIBlockElement();
// Process uploaded photos
$photos = [];
if (!empty($_FILES['PHOTOS']['tmp_name'])) {
foreach ($_FILES['PHOTOS']['tmp_name'] as $i => $tmpName) {
if (is_uploaded_file($tmpName)) {
$photos[] = [
'name' => $_FILES['PHOTOS']['name'][$i],
'size' => $_FILES['PHOTOS']['size'][$i],
'tmp_name' => $tmpName,
'type' => $_FILES['PHOTOS']['type'][$i],
];
}
}
}
$adId = $el->Add([
'IBLOCK_ID' => CLASSIFIEDS_IBLOCK_ID,
'NAME' => htmlspecialchars($this->request->getPost('title')),
'DETAIL_TEXT' => htmlspecialchars($this->request->getPost('description')),
'IBLOCK_SECTION_ID' => (int)$this->request->getPost('category_id'),
'ACTIVE' => 'N', // Initially inactive until moderation
'PROPERTY_VALUES' => [
'PRICE' => (float)$this->request->getPost('price'),
'DEAL_TYPE' => $this->request->getPost('deal_type'),
'PHONE' => htmlspecialchars($this->request->getPost('phone')),
'USER_ID' => $GLOBALS['USER']->GetID(),
'AD_STATUS' => 'MODERATION',
'EXPIRE_DATE' => date('d.m.Y', strtotime('+30 days')),
'VIEW_COUNT' => 0,
'PHOTOS' => $photos,
],
]);
if ($adId) {
$this->arResult['SUCCESS'] = true;
$this->arResult['AD_ID'] = $adId;
// Notify moderators
$this->notifyModerators($adId);
} else {
$this->arResult['ERROR'] = $el->LAST_ERROR;
}
}
}
How does ad moderation work?
The moderator page is a standard infoblock element list with filter AD_STATUS = MODERATION. Moderator actions change status and activity:
// Approve ad
$el = new \CIBlockElement();
$el->Update($adId, ['ACTIVE' => 'Y']);
\CIBlockElement::SetPropertyValues($adId, CLASSIFIEDS_IBLOCK_ID, 'ACTIVE', 'AD_STATUS');
// Reject with reason
\CIBlockElement::SetPropertyValues($adId, CLASSIFIEDS_IBLOCK_ID, [
'AD_STATUS' => 'REJECTED',
'REJECT_REASON' => $reason,
]);
$el->Update($adId, ['ACTIVE' => 'N']);
// Notify user
$event = new \Bitrix\Main\Mail\Event([
'EVENT_NAME' => 'AD_MODERATION_RESULT',
'LID' => SITE_ID,
'C_FIELDS' => [
'AD_ID' => $adId,
'STATUS' => $status,
'REASON' => $reason,
],
]);
$event->send();
The moderator can also edit the ad, change price or category. All actions are logged.
Search and filtering
Ad filtering is a critical part of UX. For simple searches — standard CIBlockElement::GetList() with filter. For high-traffic projects (over 100,000 ads) — Bitrix faceted search (search module) or ElasticSearch integration. Faceted search is 3–5 times faster than standard for complex filters.
Example filter with price range:
$filter = [
'IBLOCK_ID' => CLASSIFIEDS_IBLOCK_ID,
'ACTIVE' => 'Y',
'SECTION_ID' => $categoryId,
'>PROPERTY_PRICE' => $priceMin,
'<PROPERTY_PRICE' => $priceMax,
'PROPERTY_CITY' => $cityId,
'PROPERTY_AD_STATUS' => 'ACTIVE',
];
$sort = ['PROPERTY_IS_VIP' => 'DESC', 'DATE_ACTIVE_FROM' => 'DESC'];
VIP ads always on top by sorting IS_VIP DESC. At 500,000 ads, we recommend using a search index with incremental updates.
Ad expiration
An agent checks expired ads once a day. Agent registration and processing method:
// Register agent in /local/php_interface/init.php
\CAgent::AddAgent(
'Local\\Ads\\ExpireAgent::run();',
'local.ads',
'N',
86400, // Once daily
);
// Agent method
class ExpireAgent
{
public static function run(): string
{
$today = date('d.m.Y');
$result = \CIBlockElement::GetList(
[],
[
'IBLOCK_ID' => CLASSIFIEDS_IBLOCK_ID,
'ACTIVE' => 'Y',
'<PROPERTY_EXPIRE_DATE' => $today,
],
false,
false,
['ID', 'PROPERTY_USER_ID']
);
while ($ad = $result->Fetch()) {
$el = new \CIBlockElement();
$el->Update($ad['ID'], ['ACTIVE' => 'N']);
\CIBlockElement::SetPropertyValues($ad['ID'], CLASSIFIEDS_IBLOCK_ID, 'EXPIRED', 'AD_STATUS');
// Notify user
}
return 'Local\\Ads\\ExpireAgent::run();';
}
}
For a catalog of 1,000,000 ads, the agent may run longer than 5 minutes. In such cases, we run the agent in batches of 1000 records and use locking.
User personal cabinet
Page /personal/ads/ — list of current user's ads:
$myAds = \CIBlockElement::GetList(
['DATE_CREATE' => 'DESC'],
[
'IBLOCK_ID' => CLASSIFIEDS_IBLOCK_ID,
'PROPERTY_USER_ID' => $GLOBALS['USER']->GetID(),
],
false,
['nPageSize' => 20],
['ID', 'NAME', 'ACTIVE', 'PROPERTY_AD_STATUS', 'PROPERTY_EXPIRE_DATE', 'PROPERTY_VIEW_COUNT']
);
User actions: edit, deactivate, prolong (if expired), delete. Also displays view and contact statistics.
View counter
On each detail page open — increment counter. Via AJAX to not slow initial render and avoid bots: send POST request with ad_id, get current value via CIBlockElement::GetProperty, update PROPERTY_VIEW_COUNT. For high-traffic projects (over 10,000 views per hour), we recommend async queue or Redis to avoid blocking writes on each view.
What is included in the work?
When ordering a turnkey classifieds board, we provide:
- detailed technical specification with prototype;
- infoblock, HL-block, and agent configuration;
- custom components and AJAX handlers development;
- integration with payment systems and delivery services (CDEK, Russian Post);
- documentation and staff training;
- 2 weeks of warranty support after launch.
Development timelines
| Option | Scope | Timeline |
|---|---|---|
| Basic board | Infoblock, posting, list, filter | 8–12 days |
| With moderation and cabinet | + Moderation, personal cabinet, expiration agent | 12–18 days |
| Full-featured | + VIP ads, search, notifications, statistics | 20–30 days |
Contact us for a consultation and precise estimate of your project. Our engineers have 5+ years of experience in 1C-Bitrix development and guarantee data security. Order a classifieds board that will perform reliably under load and generate profit.







