Как работать с полем Привязка к элементам CRM
Scope:
crmКто может выполнять методы: чтобы пройти сценарий целиком, нужно самое строгое из перечисленных прав — административный доступ к разделу CRM
- crm.deal.userfield.add — администратор CRM
- crm.item.update — пользователь с правом «изменения» элементов объекта CRM
- crm.item.get — пользователь с правом «чтения» элементов объекта CRM
- crm.enum.ownertype — любой пользователь
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
Поле «Привязка к элементам CRM» хранит ссылку на элемент CRM — лид, сделку, контакт или компанию. Значение выглядит как C_1: код типа объекта и номер элемента.
Разберем на примере сделок. Заведем два поля: «Ответственный контакт» на одно значение и «Подрядчики» на несколько. Заполним их в конкретной сделке, прочитаем обратно и найдем по сохраненным значениям сами элементы.
Сценарий состоит из четырех шагов.
- Создадим поля методом crm.deal.userfield.add
- Запишем значения методом crm.item.update
- Прочитаем привязки методом crm.item.get
- Развернем значения в записи методами crm.enum.ownertype и crm.item.get
Что нужно до начала
Подготовьте данные сценария:
- Сделка, в которой заполним поля. Понадобится ее
id. У сделокentityTypeIdравен2. Сами поля создаются сразу для всех сделок, а не для одной - Элементы, к которым будем привязывать. В примере это контакт и две компании. Их идентификаторы вернут методы crm.item.list или crm.contact.list и crm.company.list
- Доступ к REST. Вебхук или приложение с правом
crm. Поля создает только администратор CRM
Как использовать примеры в документации
1. Создадим поля привязки
Создадим два поля методом crm.deal.userfield.add с параметрами:
FIELD_NAME— имя поля. ПрефиксUF_CRM_метод добавит сам, поэтому передаем толькоBIND_ONEиBIND_MANYUSER_TYPE_ID— укажемcrm, это и есть тип «Привязка к элементам CRM»MULTIPLE—Nдля одного значения,Yдля несколькихSETTINGS— какие объекты разрешены в поле. КлючиLEAD,CONTACT,COMPANY,DEALсо значениямиYилиN
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/'
const one = await $b24.actions.v2.call.make({
method: 'crm.deal.userfield.add',
params: {
fields: {
FIELD_NAME: 'BIND_ONE',
USER_TYPE_ID: 'crm',
MULTIPLE: 'N',
EDIT_FORM_LABEL: { ru: 'Ответственный контакт' },
SETTINGS: { LEAD: 'N', CONTACT: 'Y', COMPANY: 'N', DEAL: 'N' }
}
},
requestId: 'userfield-add-one'
})
const many = await $b24.actions.v2.call.make({
method: 'crm.deal.userfield.add',
params: {
fields: {
FIELD_NAME: 'BIND_MANY',
USER_TYPE_ID: 'crm',
MULTIPLE: 'Y',
EDIT_FORM_LABEL: { ru: 'Подрядчики' },
SETTINGS: { LEAD: 'N', CONTACT: 'N', COMPANY: 'Y', DEAL: 'N' }
}
},
requestId: 'userfield-add-many'
})
console.log(one.getData().result, many.getData().result)
from b24pysdk import BitrixWebhook
token = BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="user_id/webhook_key",
)
# Вызов через ядро SDK: типизированной обертки для пользовательских полей нет
one = token.call_method("crm.deal.userfield.add", {
"fields": {
"FIELD_NAME": "BIND_ONE",
"USER_TYPE_ID": "crm",
"MULTIPLE": "N",
"EDIT_FORM_LABEL": {"ru": "Ответственный контакт"},
"SETTINGS": {"LEAD": "N", "CONTACT": "Y", "COMPANY": "N", "DEAL": "N"},
},
})["result"]
many = token.call_method("crm.deal.userfield.add", {
"fields": {
"FIELD_NAME": "BIND_MANY",
"USER_TYPE_ID": "crm",
"MULTIPLE": "Y",
"EDIT_FORM_LABEL": {"ru": "Подрядчики"},
"SETTINGS": {"LEAD": "N", "CONTACT": "N", "COMPANY": "Y", "DEAL": "N"},
},
})["result"]
print(one, many)
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
$serviceBuilder = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/');
// Вызов через ядро SDK: типизированной обертки для пользовательских полей нет
$one = $serviceBuilder->core->call('crm.deal.userfield.add', ['fields' => [
'FIELD_NAME' => 'BIND_ONE',
'USER_TYPE_ID' => 'crm',
'MULTIPLE' => 'N',
'EDIT_FORM_LABEL' => ['ru' => 'Ответственный контакт'],
'SETTINGS' => ['LEAD' => 'N', 'CONTACT' => 'Y', 'COMPANY' => 'N', 'DEAL' => 'N'],
]])->getResponseData()->getResult();
$many = $serviceBuilder->core->call('crm.deal.userfield.add', ['fields' => [
'FIELD_NAME' => 'BIND_MANY',
'USER_TYPE_ID' => 'crm',
'MULTIPLE' => 'Y',
'EDIT_FORM_LABEL' => ['ru' => 'Подрядчики'],
'SETTINGS' => ['LEAD' => 'N', 'CONTACT' => 'N', 'COMPANY' => 'Y', 'DEAL' => 'N'],
]])->getResponseData()->getResult();
print_r([$one, $many]);
Метод вернет идентификатор созданного поля.
{
"result": 125
}
Полные имена полей стали UF_CRM_BIND_ONE и UF_CRM_BIND_MANY. Универсальные методы обращаются к ним в другом виде — ufCrmBindOne и ufCrmBindMany.
Имя преобразуется по-разному. Если в нем есть цифра, оно остается без изменений после приставки ufCrm_: поле UF_CRM_1688736288 придет как ufCrm_1688736288. Не собирайте имя вручную — возьмите готовое из ответа crm.item.fields, там же видно type и isMultiple каждого поля. Список полей с исходными именами вернет crm.deal.userfield.list.
2. Запишем значения
Коды всех типов объектов перечислены в разделе Формат значений для пользовательского поля «Привязка к элементам CRM». В примере ниже C_1 — это контакт с id 1, а CO_1 и CO_2 — компании.
Здесь и проходит главное отличие двух полей:
- простое поле принимает строку —
"C_1" - множественное принимает массив строк —
["CO_1", "CO_2"]
Запишем оба значения одним вызовом crm.item.update.
const updated = await $b24.actions.v2.call.make({
method: 'crm.item.update',
params: {
entityTypeId: 2,
id: 10,
fields: {
ufCrmBindOne: 'C_1',
ufCrmBindMany: ['CO_1', 'CO_2']
}
},
requestId: 'crm-item-update'
})
if (!updated.isSuccess) {
throw new Error(updated.getErrorMessages().join('; '))
}
token.call_method("crm.item.update", {
"entityTypeId": 2,
"id": 10,
"fields": {
"ufCrmBindOne": "C_1",
"ufCrmBindMany": ["CO_1", "CO_2"],
},
})
$serviceBuilder->core->call('crm.item.update', [
'entityTypeId' => 2,
'id' => 10,
'fields' => [
'ufCrmBindOne' => 'C_1',
'ufCrmBindMany' => ['CO_1', 'CO_2'],
],
]);
3. Прочитаем привязки
Прочитаем сделку методом crm.item.get и посмотрим, что сохранилось.
const response = await $b24.actions.v2.call.make({
method: 'crm.item.get',
params: { entityTypeId: 2, id: 10 },
requestId: 'crm-item-get'
})
const item = response.getData().result.item
console.log(item.ufCrmBindOne) // 'C_1'
console.log(item.ufCrmBindMany) // ['CO_1', 'CO_2']
item = token.call_method("crm.item.get", {
"entityTypeId": 2,
"id": 10,
})["result"]["item"]
print(item["ufCrmBindOne"]) # 'C_1'
print(item["ufCrmBindMany"]) # ['CO_1', 'CO_2']
$item = $serviceBuilder->core->call('crm.item.get', [
'entityTypeId' => 2,
'id' => 10,
])->getResponseData()->getResult()['item'];
print_r($item['ufCrmBindOne']); // 'C_1'
print_r($item['ufCrmBindMany']); // ['CO_1', 'CO_2']
Поля возвращаются в том же виде, в каком записывались: простое строкой, множественное массивом. Незаполненное поле приходит со значением null.
{
"result": {
"item": {
"id": 10,
"title": "Покупка серверов",
"ufCrmBindOne": "C_1",
"ufCrmBindMany": ["CO_1", "CO_2"]
}
}
}
4. Развернем значения в записи
Строка C_1 сама по себе ничего не говорит пользователю. Чтобы показать имя контакта или название компании, значение нужно разобрать на код и номер, а затем получить элемент.
Соответствие кодов и типов объектов вернет метод crm.enum.ownertype. В ответе SYMBOL_CODE_SHORT — это тот самый код из значения, а ID — идентификатор типа объекта, который принимает crm.item.get в параметре entityTypeId.
const enumResponse = await $b24.actions.v2.call.make({
method: 'crm.enum.ownertype',
params: {},
requestId: 'crm-enum-ownertype'
})
const codes = {}
for (const row of enumResponse.getData().result) {
codes[row.SYMBOL_CODE_SHORT] = { entityTypeId: Number(row.ID), name: row.NAME }
}
const values = [item.ufCrmBindOne, ...item.ufCrmBindMany]
for (const value of values) {
const separator = value.lastIndexOf('_')
const code = value.slice(0, separator)
const elementId = Number(value.slice(separator + 1))
const type = codes[code]
const element = await $b24.actions.v2.call.make({
method: 'crm.item.get',
params: { entityTypeId: type.entityTypeId, id: elementId },
requestId: 'crm-item-get-linked'
})
console.log(value, type.name, element.getData().result.item)
}
rows = token.call_method("crm.enum.ownertype")["result"]
codes = {r["SYMBOL_CODE_SHORT"]: {"entity_type_id": int(r["ID"]), "name": r["NAME"]} for r in rows}
values = [item["ufCrmBindOne"], *item["ufCrmBindMany"]]
for value in values:
code, _, element_id = value.rpartition("_")
entity_type = codes[code]
element = token.call_method("crm.item.get", {
"entityTypeId": entity_type["entity_type_id"],
"id": int(element_id),
})["result"]["item"]
print(value, entity_type["name"], element)
$rows = $serviceBuilder->core->call('crm.enum.ownertype')
->getResponseData()->getResult();
$codes = [];
foreach ($rows as $row) {
$codes[$row['SYMBOL_CODE_SHORT']] = ['entityTypeId' => (int)$row['ID'], 'name' => $row['NAME']];
}
$values = array_merge([$item['ufCrmBindOne']], $item['ufCrmBindMany']);
foreach ($values as $value) {
$separator = strrpos($value, '_');
$code = substr($value, 0, $separator);
$elementId = (int)substr($value, $separator + 1);
$type = $codes[$code];
$element = $serviceBuilder->core->call('crm.item.get', [
'entityTypeId' => $type['entityTypeId'],
'id' => $elementId,
])->getResponseData()->getResult()['item'];
echo $value . ' ' . $type['name'] . PHP_EOL;
print_r($element);
}
Разбирайте значение по последнему подчеркиванию: код типа сам может его содержать. Например, у смарт-процессов код выглядит как T80, а у реквизитов — RQ.
Проверяйте, что код нашелся в справочнике. Поле принимает и произвольный префикс, поэтому в значении может оказаться код, которого в ответе метода нет — в примерах ниже такая проверка есть.
Метод вернет соответствие кодов и типов.
{
"result": [
{ "ID": "1", "SYMBOL_CODE": "LEAD", "SYMBOL_CODE_SHORT": "L", "NAME": "Лид" },
{ "ID": "3", "SYMBOL_CODE": "CONTACT", "SYMBOL_CODE_SHORT": "C", "NAME": "Контакт" },
{ "ID": "4", "SYMBOL_CODE": "COMPANY", "SYMBOL_CODE_SHORT": "CO", "NAME": "Компания" }
]
}
Проверим результат
Сценарий выполнен успешно, если:
- в поле
ufCrmBindOneлежит строка видаC_1, а не массив и не значениеArray - в поле
ufCrmBindManyлежит массив строк - каждое значение разобралось на известный код и номер, и метод crm.item.get вернул по нему элемент
В интерфейсе Битрикс24 откройте карточку сделки. Созданные поля появляются в ней автоматически, но не обязательно в первом разделе — пролистайте карточку до конца. В поле «Ответственный контакт» будет имя контакта, в поле «Подрядчики» — названия компаний через запятую.
Карточка подставляет названия сама, а метод crm.item.get возвращает только коды и номера. Поэтому в интеграции имена приходится получать отдельно — этим и занят четвертый шаг.
Ошибки и диагностика
Если метод вернул ошибку, проверьте данные запроса.
|
Код |
Причина и действие |
|
|
Во множественное поле передана строка. Передавайте массив строк |
|
|
Элемента с таким |
|
|
Поле с таким именем уже заведено. Возьмите другое имя или используйте существующее поле |
|
|
В |
|
Пустой код с текстом |
Не передано имя поля в |
Неверное значение чаще сохраняется, чем вызывает ошибку. Если ошибки не было, а результат неверный, проверьте:
- в простом поле оказалась строка
Array— в него передали массив вместо строки. Значение потеряно, запишите его заново - сохранился объект не того типа — тип, запрещенный в
SETTINGS, все равно сохраняется. Настройки поля ограничивают выбор в интерфейсе, но не проверяют запись через REST - значение есть, а элемент не открывается — привязка к несуществующему элементу тоже сохраняется. Существование элемента при записи не проверяется
- код в значении не найден в справочнике — произвольный префикс вроде
XX_1сохранится без ошибки. Сверьте код с ответом crm.enum.ownertype
Что важно учитывать
- Поле не проверяет значение при записи. Собирайте и сверяйте его на своей стороне: неверная привязка сохранится молча, и ошибка всплывет позже
- Проверка контейнера несимметрична: множественное поле отклонит строку, а простое молча примет массив. Тип значения выбирайте по
MULTIPLE, а не по тому, сколько элементов привязываете сейчас - Значение хранит только код типа и номер. Имя контакта или название компании нужно получать отдельным запросом
- Код типа берите из crm.enum.ownertype, а не составляйте вручную. У смарт-процессов он вычисляется по особому правилу, описанному в справочнике
- Не путайте формат привязки с форматом целевого объекта в автоматизации: там используется полное имя типа —
DEAL_25, а неD_25
Где еще встречаются поля привязки
Поля этого типа есть не только в CRM и не только пользовательские.
|
Где |
Поле |
Методы |
|
Объекты CRM: лид, сделка, контакт, компания, предложение |
Пользовательское поле типа |
|
|
Задачи |
Системное поле |
|
|
Списки |
Свойство элемента с типом привязки |
|
|
Торговый каталог |
Свойство товара с типом |
|
|
События календаря |
Параметр |
Формат значения везде одинаковый — код типа и номер элемента. Отличается только то, как поле называется и каким методом заполняется.
Сценарий привязки задачи к элементу смарт-процесса разобран отдельно в туториале Как прикрепить задачу к смарт-процессу.
Пример кода
Код проходит все четыре шага: создает поля, записывает значения, читает их и разворачивает в записи CRM.
Замените вебхук, идентификатор сделки и идентификаторы элементов.
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
const entityTypeId = 2
const dealId = 10
async function call(method, params, requestId) {
const response = await $b24.actions.v2.call.make({ method, params, requestId })
if (!response.isSuccess) {
throw new Error(method + ': ' + response.getErrorMessages().join('; '))
}
return response.getData().result
}
// 1. Создаем простое и множественное поля привязки
await call('crm.deal.userfield.add', { fields: {
FIELD_NAME: 'BIND_ONE',
USER_TYPE_ID: 'crm',
MULTIPLE: 'N',
EDIT_FORM_LABEL: { ru: 'Ответственный контакт' },
SETTINGS: { LEAD: 'N', CONTACT: 'Y', COMPANY: 'N', DEAL: 'N' }
}}, 'userfield-add-one')
await call('crm.deal.userfield.add', { fields: {
FIELD_NAME: 'BIND_MANY',
USER_TYPE_ID: 'crm',
MULTIPLE: 'Y',
EDIT_FORM_LABEL: { ru: 'Подрядчики' },
SETTINGS: { LEAD: 'N', CONTACT: 'N', COMPANY: 'Y', DEAL: 'N' }
}}, 'userfield-add-many')
// 2. Записываем значения: строку в простое поле, массив во множественное
await call('crm.item.update', {
entityTypeId,
id: dealId,
fields: { ufCrmBindOne: 'C_1', ufCrmBindMany: ['CO_1', 'CO_2'] }
}, 'crm-item-update')
// 3. Читаем привязки
const { item } = await call('crm.item.get', { entityTypeId, id: dealId }, 'crm-item-get')
// 4. Разворачиваем значения в записи
const rows = await call('crm.enum.ownertype', {}, 'crm-enum-ownertype')
const codes = {}
for (const row of rows) {
codes[row.SYMBOL_CODE_SHORT] = { entityTypeId: Number(row.ID), name: row.NAME }
}
for (const value of [item.ufCrmBindOne, ...item.ufCrmBindMany]) {
const separator = value.lastIndexOf('_')
const type = codes[value.slice(0, separator)]
if (!type) {
console.warn('Неизвестный код типа в значении', value)
continue
}
const linked = await call('crm.item.get', {
entityTypeId: type.entityTypeId,
id: Number(value.slice(separator + 1))
}, 'crm-item-get-linked')
const label = linked.item.title || [linked.item.name, linked.item.lastName].filter(Boolean).join(' ')
console.log(value, '->', type.name, label)
}
from b24pysdk import BitrixWebhook
token = BitrixWebhook(
domain="your-domain.bitrix24.com",
webhook_token="user_id/webhook_key",
)
entity_type_id = 2
deal_id = 10
# 1. Создаем простое и множественное поля привязки
token.call_method("crm.deal.userfield.add", {"fields": {
"FIELD_NAME": "BIND_ONE",
"USER_TYPE_ID": "crm",
"MULTIPLE": "N",
"EDIT_FORM_LABEL": {"ru": "Ответственный контакт"},
"SETTINGS": {"LEAD": "N", "CONTACT": "Y", "COMPANY": "N", "DEAL": "N"},
}})
token.call_method("crm.deal.userfield.add", {"fields": {
"FIELD_NAME": "BIND_MANY",
"USER_TYPE_ID": "crm",
"MULTIPLE": "Y",
"EDIT_FORM_LABEL": {"ru": "Подрядчики"},
"SETTINGS": {"LEAD": "N", "CONTACT": "N", "COMPANY": "Y", "DEAL": "N"},
}})
# 2. Записываем значения: строку в простое поле, массив во множественное
token.call_method("crm.item.update", {
"entityTypeId": entity_type_id,
"id": deal_id,
"fields": {"ufCrmBindOne": "C_1", "ufCrmBindMany": ["CO_1", "CO_2"]},
})
# 3. Читаем привязки
item = token.call_method("crm.item.get", {
"entityTypeId": entity_type_id,
"id": deal_id,
})["result"]["item"]
# 4. Разворачиваем значения в записи
rows = token.call_method("crm.enum.ownertype")["result"]
codes = {r["SYMBOL_CODE_SHORT"]: {"entity_type_id": int(r["ID"]), "name": r["NAME"]} for r in rows}
for value in [item["ufCrmBindOne"], *item["ufCrmBindMany"]]:
code, _, element_id = value.rpartition("_")
entity_type = codes.get(code)
if not entity_type:
print("Неизвестный код типа в значении", value)
continue
linked = token.call_method("crm.item.get", {
"entityTypeId": entity_type["entity_type_id"],
"id": int(element_id),
})["result"]["item"]
label = linked.get("title") or " ".join(filter(None, [linked.get("name"), linked.get("lastName")]))
print(value, "->", entity_type["name"], label)
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
$serviceBuilder = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->initFromWebhook('https://your-domain.bitrix24.com/rest/USER_ID/TOKEN/');
$entityTypeId = 2;
$dealId = 10;
function call($serviceBuilder, $method, $params = [])
{
return $serviceBuilder->core->call($method, $params)->getResponseData()->getResult();
}
// 1. Создаем простое и множественное поля привязки
call($serviceBuilder, 'crm.deal.userfield.add', ['fields' => [
'FIELD_NAME' => 'BIND_ONE',
'USER_TYPE_ID' => 'crm',
'MULTIPLE' => 'N',
'EDIT_FORM_LABEL' => ['ru' => 'Ответственный контакт'],
'SETTINGS' => ['LEAD' => 'N', 'CONTACT' => 'Y', 'COMPANY' => 'N', 'DEAL' => 'N'],
]]);
call($serviceBuilder, 'crm.deal.userfield.add', ['fields' => [
'FIELD_NAME' => 'BIND_MANY',
'USER_TYPE_ID' => 'crm',
'MULTIPLE' => 'Y',
'EDIT_FORM_LABEL' => ['ru' => 'Подрядчики'],
'SETTINGS' => ['LEAD' => 'N', 'CONTACT' => 'N', 'COMPANY' => 'Y', 'DEAL' => 'N'],
]]);
// 2. Записываем значения: строку в простое поле, массив во множественное
call($serviceBuilder, 'crm.item.update', [
'entityTypeId' => $entityTypeId,
'id' => $dealId,
'fields' => ['ufCrmBindOne' => 'C_1', 'ufCrmBindMany' => ['CO_1', 'CO_2']],
]);
// 3. Читаем привязки
$item = call($serviceBuilder, 'crm.item.get', [
'entityTypeId' => $entityTypeId,
'id' => $dealId,
])['item'];
// 4. Разворачиваем значения в записи
$codes = [];
foreach (call($serviceBuilder, 'crm.enum.ownertype') as $row) {
$codes[$row['SYMBOL_CODE_SHORT']] = ['entityTypeId' => (int)$row['ID'], 'name' => $row['NAME']];
}
foreach (array_merge([$item['ufCrmBindOne']], $item['ufCrmBindMany']) as $value) {
$separator = strrpos($value, '_');
$code = substr($value, 0, $separator);
if (!isset($codes[$code])) {
echo 'Неизвестный код типа в значении ' . $value . PHP_EOL;
continue;
}
$linked = call($serviceBuilder, 'crm.item.get', [
'entityTypeId' => $codes[$code]['entityTypeId'],
'id' => (int)substr($value, $separator + 1),
])['item'];
$label = $linked['title'] ?? trim(($linked['name'] ?? '') . ' ' . ($linked['lastName'] ?? ''));
echo $value . ' -> ' . $codes[$code]['name'] . ' ' . $label . PHP_EOL;
}