Создадим кастомный тип пользовательского поля, который отображает HTML‑виджет прямо в карточке сделки.
Задача
Чтобы виджет появлялся сразу после добавления поля на форму через «Выбрать поле», и работал как обычное поле Bitrix24.
Что было изначально
У нас уже был готовый виджет «Услуги КРИА»:
- JS подключался по URL страницы сделки.
- Виджет монтировался в нужную секцию формы.
- Данные брались и сохранялись через компонент
iko4v:kria.serviceи HL‑блоки.
Проблема: виджет появлялся на всех сделках, а нужно было показывать его только там, где поле добавлено на форму через «Выбрать поле».
Свой тип пользовательского поля в Битрикс24
Логично выбрать готовый механизм и идти стандартным путём Bitrix24: создал кастомный тип пользовательского поля, который рендерится через компонент, а внутри шаблонов подключает нужный JS/CSS и маркер для монтирования.
Основная логика:
- Пользователь добавляет на форму поле
UF_KRIA_SERVICES_WIDGET. - Поле всегда рендерит маркер и подключает JS.
- JS ищет контейнер поля и вставляет виджет внутрь него.
Шаг 1. Класс кастомного типа поля
Делаем класс на базе Bitrix\Main\UserField\Types\BaseType.
Файл: /local/php_interface/crm/kria_services_userfield.php
<?php
namespace Local\Crm\UserField;
use Bitrix\Main\UserField\Types\BaseType;
class KriaServicesWidget extends BaseType
{
protected const USER_TYPE_ID = 'kria_services_widget';
protected const RENDER_COMPONENT = 'iko4v:kria.userfield.widget';
protected static function getDescription(): array
{
return [
'DESCRIPTION' => 'Услуги КРИА (виджет)',
'BASE_TYPE' => 'string',
];
}
public static function getDbColumnType(): string
{
return 'text';
}
}
Шаг 2. Регистрация типа поля
Подключаем класс и регистрируем тип через OnUserTypeBuildList.
Файл: /public_html/local/php_interface/init.php
$eventManager->addEventHandler(
'main',
'OnUserTypeBuildList',
'KriaServicesRegisterUserField'
);
function KriaServicesRegisterUserField(): array
{
require_once __DIR__ . '/crm/kria_services_userfield.php';
return \Local\Crm\UserField\KriaServicesWidget::getUserTypeDescription();
}
Шаг 3. Компонент рендера UF‑типа
Создаем компонент iko4v:kria.userfield.widget на базе BaseUfComponent, чтобы Bitrix рендерил поле в разных режимах:
main.edit- в режиме редактированиmain.view- в режиме просмотраmain.public_text- при экспорте
Файл: /local/components/iko4v/kria.userfield.widget/class.php
<?php
use Bitrix\Main\Component\BaseUfComponent;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
die();
}
class KriaUserfieldWidgetComponent extends BaseUfComponent
{
protected static function getUserTypeId(): string
{
return 'kria_services_widget';
}
}
Шаблон main.edit
Файл: /local/components/iko4v/kria.userfield.widget/templates/main.edit/.default.php
<?php
use Bitrix\Main\Page\Asset;
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) {
die();
}
$userField = $arResult['userField'] ?? [];
$dealId = (int) ($userField['ENTITY_VALUE_ID'] ?? 0);
$asset = Asset::getInstance();
$asset->addCss('/local/js/kria-deal-services/kria-deal-services.css');
$asset->addJs('/local/js/kria-deal-services/kria-deal-services.js');
?>
<div data-kria-services-field="1" data-kria-deal-id="<?= $dealId ?>"></div>
Шаблоны main.view, main.public_text, .default
Сделал так, чтобы в режиме просмотра поле рендерило тот же маркер (иначе показывалось «не заполнено»). Логика такая же, как в main.edit, код в шаблонах в моем случае одинаковый.
Шаг 4. Миграция поля
Создаем миграцию, которая добавляет только одно поле UF_KRIA_SERVICES_WIDGET. Или можно создать руками это поле.
Файл: /local/migrations/2026_02_10_create_kria_services_widget_userfield.php
<?php
// /local/migrations/2026_02_10_create_kria_services_widget_userfield.php
/**
* Миграция: добавляет UF_KRIA_SERVICES_WIDGET в CRM_DEAL
*/
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_before.php");
use Bitrix\Main\Loader;
use Bitrix\Main\SystemException;
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
header('Content-Type: text/html; charset=utf-8');
echo "<pre>";
try {
if (!Loader::includeModule('main')) {
throw new SystemException('Module main is not installed');
}
$entityId = 'CRM_DEAL';
$fieldName = 'UF_KRIA_SERVICES_WIDGET';
$filter = [
'ENTITY_ID' => $entityId,
'FIELD_NAME' => $fieldName,
];
$exists = CUserTypeEntity::GetList([], $filter)->Fetch();
if ($exists) {
echo "UF поле {$fieldName} уже существует (ID={$exists['ID']}).\n";
echo "Готово.\n";
echo "</pre>";
return;
}
$fieldDef = [
'FIELD_NAME' => $fieldName,
'USER_TYPE_ID' => 'kria_services_widget',
'XML_ID' => $fieldName,
'SORT' => 500,
'MULTIPLE' => 'N',
'MANDATORY' => 'N',
'SHOW_FILTER' => 'N',
'SHOW_IN_LIST' => 'N',
'EDIT_IN_LIST' => 'N',
'IS_SEARCHABLE' => 'N',
'SETTINGS' => [],
'EDIT_FORM_LABEL' => [
'ru' => 'Услуги КРИА',
'en' => 'Show KRIA services',
],
'LIST_COLUMN_LABEL' => [
'ru' => 'Услуги КРИА',
'en' => 'Show KRIA services',
],
'LIST_FILTER_LABEL' => [
'ru' => 'Услуги КРИА',
'en' => 'Show KRIA services',
],
];
$ufe = new CUserTypeEntity();
$fieldDef['ENTITY_ID'] = $entityId;
$id = (int) $ufe->Add($fieldDef);
if (!$id) {
global $APPLICATION;
$e = $APPLICATION->GetException();
$msg = $e ? $e->GetString() : 'unknown';
throw new SystemException("Fail add UF {$fieldName}: {$msg}");
}
echo "Добавлено UF поле {$fieldName} (ID={$id}).\n";
echo "Готово.\n";
} catch (Throwable $e) {
echo "ERROR: " . $e->getMessage() . "\n";
}
echo "</pre>";
Шаг 5. Подключение JS на странице сделки
Чтобы JS запускался в режиме просмотра, подключаем его в общем файле crm/js.php для страницы сделки (этот файл подключается в init.php):
if (CSite::InDir('/crm/deal/details/')) {
$asset->addCss('/local/js/kria-deal-services/kria-deal-services.css');
$asset->addString('<script src="/local/js/kria-deal-services/kria-deal-services.js"></script>');
}
Иначе при загрузке карточки Сделки наш виджет не отобразится. Будет заглушка с «не заполнено» и виджет отобразится только когда кликнем по полю. То есть перейдем в режим редактирования.
Шаг 6. Монтирование виджета в контейнер поля
Основная проблема была в том, что Bitrix в режиме просмотра показывал «не заполнено». Как и упомянул выше, решаем так:
- JS ищет блок поля по
data-cid="UF_KRIA_SERVICES_WIDGET". - Вставляет виджет внутрь
.ui-entity-editor-content-blockэтого поля. - Убирает классы
ui-entity-editor-content-block-click-emptyи...-empty.
Фрагмент:
const block = document.querySelector('[data-cid="UF_KRIA_SERVICES_WIDGET"]');
const content = block ? block.querySelector('.ui-entity-editor-content-block') : null;
if (content) {
block.classList.remove('ui-entity-editor-content-block-click-empty');
block.classList.remove('ui-entity-editor-content-block-empty');
content.appendChild(widget);
}
Итог
Теперь поле работает как родное поле Bitrix24:
- Добавляем его через «Выбрать поле».
- Виджет сразу отображается в режиме просмотра.
- Данные сохраняются в HL‑блоках через существующий компонент.
Если вам нужно реализовать похожий виджет в другой сущности, логика будет ровно такой же: кастомный тип UF + компонент рендера + JS, который монтирует виджет в контейнер поля.