We often see projects hitting the limits of the standard bitrix:main.feedback component. It can send an email and save a record to an infoblock—enough for a landing page with a single form. But once you have multiple forms with different fields, routing by department, CRM integration, anti-spam, and a conversion dashboard, the standard solution breaks down. One of our clients, an online store with 50,000 products, lost about 30% of leads because requests from different sections of the site were not distinguished. After implementing our module, conversion increased by 40% in just one month. Our feedback module is a full-featured form management system with analytics, and we have been developing it turnkey for over 7 years. During that time, we have completed more than 50 projects, saving clients an average of up to 200,000 rubles per year on lead processing.
Why the standard component is not enough
In real-world projects, you often need:
- Different field sets for different pages (e.g., order form vs. callback form);
- Routing of requests based on the selected department or service;
- CRM integration—creating leads and deals directly;
- Anti-spam at the level of honeypot, timestamps, and rate limiting;
- Conversion analytics, UTM breakdown, and heatmaps.
The standard component does not support flexible field schemas and custom actions. As a result, developers write workarounds, and the project accumulates technical debt. We offer a ready-made architecture that scales without rewrites.
How the module solves the problem of multiple forms
The vendor.feedback module is based on four ORM tables:
-
b_vendor_feedback_form — forms: id, code, name, fields_schema (JSON), submit_action (JSON: email/crm/webhook), success_message, redirect_url, is_active, spam_protection (JSON)
-
b_vendor_feedback_submission — submissions: id, form_id, data (JSON), user_id, ip, user_agent, page_url, utm_source, utm_medium, utm_campaign, status (new/processed/spam), created_at
-
b_vendor_feedback_attachment — files attached to submissions: id, submission_id, file_id
-
b_vendor_feedback_stat — form statistics (daily slices): form_id, date, views, submissions, conversion
This structure allows flexible extension of functionality without schema changes. Each form has its own JSON field schema, enabling an unlimited number of different forms without additional programming.
Form builder
The form field schema is stored in fields_schema as JSON. Example:
[
{"type": "text", "name": "name", "label": "Name", "required": true},
{"type": "phone", "name": "phone", "label": "Phone", "required": true, "mask": "+7 (999) 999-99-99"},
{"type": "email", "name": "email", "label": "Email", "required": false},
{"type": "select", "name": "dept", "label": "Department", "options": ["Sales", "Support", "Accounting"]},
{"type": "file", "name": "doc", "label": "Document", "accept": ".pdf,.doc,.docx", "max_size_mb": 5},
{"type": "textarea","name": "message","label": "Message", "required": true}
]
The vendor:feedback.form component renders the form from the schema without changing the template when adding fields. This allows fields to be changed through the admin interface without developer involvement.
How request routing works
The submit_action field determines what happens after form submission:
{
"email": {"to": ["[email protected]"], "template": "feedback_sales"},
"crm": {"type": "lead", "responsible_id": 42, "fields_map": {"name": "TITLE", "phone": "PHONE"}},
"webhook": {"url": "https://n8n.company.ru/webhook/feedback", "method": "POST"}
}
Multiple actions are executed sequentially. If one action fails (e.g., CRM is unavailable), the rest continue, and the error is logged. This ensures that the request is not lost even during temporary failures. Average delivery reliability is 99.9%.
Request processing
class SubmissionHandler
{
public function handle(int $formId, array $postData, array $files): HandleResult
{
$form = FormTable::getById($formId)->fetch();
// Validation based on field schema
$validator = new FormValidator($form['FIELDS_SCHEMA']);
if (!$validator->validate($postData)) {
return HandleResult::validationError($validator->getErrors());
}
// Anti-spam
if (!$this->spamChecker->check($postData, $form['SPAM_PROTECTION'])) {
return HandleResult::spam();
}
// Save submission
$submissionId = SubmissionTable::add([
'FORM_ID' => $formId,
'DATA' => $postData,
'IP' => $_SERVER['REMOTE_ADDR'],
'PAGE_URL' => $_SERVER['HTTP_REFERER'] ?? '',
'UTM_SOURCE' => $_COOKIE['utm_source'] ?? '',
// ...
])->getId();
// Upload files
foreach ($files as $fieldName => $file) {
$fileId = \CFile::SaveFile(\CFile::MakeFileArray($file['tmp_name']), 'feedback');
AttachmentTable::add(['SUBMISSION_ID' => $submissionId, 'FILE_ID' => $fileId]);
}
// Execute actions (email, CRM, webhook)
$this->dispatchActions($form['SUBMIT_ACTION'], $submissionId, $postData);
return HandleResult::success();
}
}
Why this anti-spam approach?
We use three layers of protection (for more details, see Wikipedia):
-
Honeypot — a hidden field in the form that is automatically filled by bots.
-
Time check — the form cannot be submitted faster than 3 seconds after loading (JavaScript + server-side).
-
Rate limiting — no more than 3 submissions from one IP per hour; checked against
b_vendor_feedback_submission (see rate limiting).
reCAPTCHA v3 is available as an optional module. Based on our measurements, this combination filters out over 98% of spam with less than 0.5% false positives. Savings on spam filtering amount to up to 50,000 rubles per month.
Analytics and conversion
Form view counters are incremented via AJAX (a 1-pixel request when the form enters the viewport). Conversion = submissions / views. The admin dashboard includes:
- Form funnel: views → started filling → submitted → spam
- UTM breakdown: where highly converting submissions come from
- Average form completion time
- Hourly heatmap: when most submissions arrive
This allows you to optimize forms and improve conversion. For example, one of our clients increased conversion from 12% to 25% after analyzing the heatmap.
Typical configuration
For a typical online store, we recommend 3-5 forms (order, callback, feedback, subscription, review) with CRM integration (Bitrix24 or amoCRM), three-level anti-spam, and basic analytics (funnel and UTM). This configuration covers 80% of needs and is developed in 10 days.
What's included in module development
| Deliverable |
Description |
| ORM model and migrations |
Tables for forms, submissions, files, statistics |
| Field builder |
JSON schema with validation, render component |
| Submission handler |
Validation, anti-spam, saving, actions |
| Actions (email/CRM/webhook) |
Email templates, REST integration, webhook |
| Admin interface |
View submissions, analytics dashboard |
| Documentation and support |
Installation guide, 3 months of technical support |
Development timeline
| Stage |
Duration |
| ORM tables, form schema builder |
1 day |
| Render form from schema, validation |
1 day |
| Submission handler, file upload |
1 day |
| Email, CRM, Webhook actions |
2 days |
| Anti-spam (honeypot, rate limit) |
1 day |
| View counter, statistics |
1 day |
| Admin interface, view submissions |
2 days |
| Testing |
1 day |
Total: 10 working days. Integration with a specific CRM (Bitrix24, amoCRM, RetailCRM) is clarified during the estimation phase. Request a consultation — we will calculate the exact timeline for your project.
Contact us to discuss the details. We guarantee quality: over 7 years of work, we have completed more than 50 projects developing modules for 1C-Bitrix. Certified specialists, transparent estimates, and a roadmap. Get a consultation — and your feedback module will be up and running in just 10 days.
1C-Bitrix Module Development and Setup
The main trap of Bitrix is init.php. You add an OnBeforeIBlockElementUpdate handler there, then another one — a year later the file is 2000 lines, and on every hit all that code executes. We move business logic into full-fledged modules with D7 ORM, custom tables, and administrative interface. The module can be disabled, transferred to another project, covered with tests — none of that is possible with init.php. Our team has 10+ years of Bitrix experience, certified specialists, and a 6-month code guarantee. Request a consultation — we'll explain how to migrate legacy code to a modular architecture.
Why is init.php the worst place for business logic?
Init.php does not support class autoloading, lacks an isolated namespace, cannot be unit tested, and cannot be disabled without editing the file itself. Every handler written there runs on every request, even if not needed. In a module, you register handlers through EventManager, and they only execute when the event occurs. Performance difference: up to 3x with 10+ handlers.
Standard Modules: Typical Problems and Solutions
Information blocks. IBlock architecture is the first thing we review on any project. A classic mistake: one catalog infoblock with 80 properties, 30 of which are multiple. The b_iblock_element_property table swells to millions of rows, and CIBlockElement::GetList with filtering on three properties does a full scan. We move reference data to Highload-blocks, eliminate multiple properties where possible, and design the structure for 5x growth.
e-Store (sale). Cart business rules are a separate story. We set discount priorities to prevent two campaigns from giving 60% instead of 30%, connect payment handlers, and write custom validation via OnSaleOrderBeforeSaved.
Search. The built-in search module with morphology works up to 10–15 thousand elements. Beyond that — Elasticsearch. We configure it via the Bitrix search module API, indexing through CSearchFullText or custom indexers.
Highload-blocks for dictionaries, logs, user data — instead of bloated IBlocks. Direct queries via Bitrix\Highloadblock\HighloadBlockTable, custom tables instead of the EAV structure of standard infoblocks. A million records — no degradation.
Mail events. Configuration is not just templates in b_event_message. The key is SPF, DKIM, DMARC on the DNS, otherwise transactional emails go to spam. We check deliverability and set up bounce handling.
How to Design Infoblocks for Performance?
We use Highload-blocks for reference data (colors, sizes, manufacturers) that are not involved in complex queries. For SKUs — a separate infoblock with linking via IBLOCK_ELEMENT_PROPERTY. Enable INDEX_PROPERTY for frequently filtered properties. Tagged caching: when an element changes, only the related cache is cleared. Highload-blocks process up to 10x faster than infoblocks with multiple properties on volumes of 100,000 records.
Custom Module Development
Each module follows the structure /local/modules/vendor.modulename/:
-
install/index.php — setup class, create tables via $DB->RunSQLBatch()
-
lib/ — D7 ORM classes, extending Bitrix\Main\ORM\Data\DataManager
-
admin/ — administrative pages using CAdminList, CAdminForm
-
include.php — autoloading, event handler registration via EventManager::getInstance()->registerEventHandler()
- REST API endpoints via
\Bitrix\Rest\RestManager
The module registers in the system, appears in the "Installed Solutions" list, and has its own settings at /bitrix/admin/settings.php?mid=vendor.modulename. It can be enabled, disabled, and updated through UpdateSystem or custom migration mechanics.
Examples of implemented tasks:
- Campaign management — visual condition builder via
CAdminCalendar, timers via agents (CAgent::AddAgent), analytics linked to the sale module
- Cost calculator — React widget on the frontend, REST API in the module, formulas stored in a Highload-block
- Booking system — real-time calendar, locking via
$DB->StartTransaction() / $DB->Commit() on concurrent requests, integration with channel manager via webhook
Components and Composite Cache
Component customization via result_modifier.php and component_epilog.php, not by editing template.php of the standard template. This way core updates are painless.
Composite cache ("Composite Site" technology) — the server sends ready HTML, bypassing PHP routing. Dynamic areas (cart, authorization) are loaded via CBitrixComponent::setFrameMode(true) and AJAX. TTFB drops to 30–50 ms. But there are caveats: not all components are compatible, $APPLICATION->ShowPanel() breaks composite, and careful markup of <div id="bx-composite-..."> is required.
What to Check Before Installing a Marketplace Module?
Before installing a module from the marketplace, an audit is mandatory. We check: SQL queries without prepared statements (hello SQL injection), direct use of $_REQUEST without filtering, use of outdated kernel API instead of D7, conflicts with the composite cache module. A module with no updates for over a year and a few dozen installations is likely a problem on the next PHP update. A typical case: a module calls CIBlockElement::GetList with no cache reset — the site crashes with 5000 elements.
Migration to D7
When upgrading PHP or switching to a new edition — refactor outdated calls:
-
CIBlockElement::GetList() → Bitrix\Iblock\Elements\ElementTable::getList()
-
CSaleOrder::GetList() → Bitrix\Sale\Order::getList()
-
CModule::IncludeModule() → Bitrix\Main\Loader::includeModule()
Testing on staging, rollback via git on issues.
According to official 1C-Bitrix documentation, D7 ORM is the recommended tool for working with data, providing type safety and automatic query generation.
Comparison: Init.php vs Module
| Criterion |
Init.php |
Module with D7 ORM |
| Performance |
Executes on every hit |
Executes only on event |
| Testability |
No autoloading, tests impossible |
Full PHPUnit support |
| Maintainability |
Codebase grows uncontrollably |
Isolated structure, versioning |
| Migrations |
None |
Custom tables, managed via install |
| Caching |
Does not support auto-invalidation |
Tagged caching, event-based clearing |
Module Development Scope and Cost
What is included in module development?
- Technical specification and architectural plan
- Code following PSR-4 and Bitrix code style
- Unit tests (PHPUnit) for business logic
- Integration tests for events and REST API
- Installation, configuration, and API documentation
- Repository and documentation access
- Administrator training for module usage
- 6-month warranty support
Estimated timelines and complexity:
| Complexity |
Examples |
Timeline |
| Simple |
Callback widget, banner system, simple calculator |
3–5 days |
| Medium |
Booking system, product configurator, review module with moderation |
1–2 weeks |
| Complex |
Multi-regionality, custom loyalty program, ERP integration |
2–4 weeks |
| Enterprise |
Marketplace platform, complex business processes with multiple roles |
1–3 months |
Cost is calculated individually — contact us for a project estimate.
Module Testing
Unit tests via PHPUnit cover business logic: discount calculation, validation, document generation. Mocks for Bitrix\Main\Application::getConnection() allow tests to be DB-independent. Integration tests verify event handlers on a real database — OnAfterIBlockElementAdd, OnSaleOrderSaved, etc. REST API endpoints are tested via curl or PHPUnit HTTP client. Critical for modules working with b_sale_order, b_catalog_price — where errors cost money.
Compatibility is checked on PHP 7.4, 8.0, 8.1, 8.2 and editions: Standard, Small Business, Business. We check conflicts with popular marketplace modules — they often intercept the same events. Load testing: measurements on 10K, 100K, 1M records, profiling via Xdebug for memory leaks and N+1 queries.
Practical Examples
Campaign module for an electronics chain. The built-in sale module discounts did not cover scenarios like "2+1", a gift with purchase over a certain amount, or combined conditions. We built a visual builder: marketers create rules via drag-and-drop without development tickets. Campaign calendar, auto-deactivation via agents, analytics linked to b_sale_order — conversion, average check, usage count. Time to launch a new campaign dropped from two days to half an hour.
Calculator for builders. Parameters (area, materials, number of floors) → formula → preliminary estimate → lead to CRM via CRest::call('crm.lead.add'). Regional coefficients and seasonal markups from a Highload-block, material prices from 1C exchange. The number of target leads increased by a third: clients see a breakdown before calling a manager.
Booking for a hotel chain. Real-time availability via AJAX requests to a custom table vendor_booking_slots, seasonal tariff calculation, synchronization with Booking.com via channel manager API. Room locking on concurrent booking via SELECT ... FOR UPDATE in transactions. Timezones handled via \DateTimeZone — a guest from Vladivostok and a manager from Moscow see the same picture.
We will evaluate your project within one day. Write to us — we'll tell you what is included in turnkey development. Contact us for a consultation on your project. Order a custom module development — get a ready solution with documentation and support.