Search by

uplabteam / uplab.api

Uplab

HTTP API для «1С-Битрикс: Управление сайтом» — отдаёт инфоблоки, highload-блоки, веб-формы, меню, поиск и карту сайта headless-фронтенду с учётом прав и кеша

Package info

github.com/uplab-dev/uplab.api

Documentation

Type:bitrix-module

pkg:composer/uplabteam/uplab.api

Statistics

Installs: 58

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.12.1 2026-08-24 22:00 UTC

This package is auto-updated.

Last update: 2026-08-24 22:14:01 UTC


README

Latest Stable Version Total Downloads License

Модуль для «1С-Битрикс: Управление сайтом», предоставляющий HTTP API для чтения данных инфоблоков и highload-блоков, работы с веб-формами, меню, поиском, картой сайта и статическим контентом.

Модуль предоставляется «как есть», без каких-либо гарантий. Вы используете его на свой страх и риск и сами отвечаете за последствия — см. раздел «Гарантии и ответственность».

Модуль не превращает API в публичный автоматически: доступ определяется правами текущего пользователя Битрикс и настройками Origin/IP. Перед размещением в интернете ознакомьтесь с разделом «Безопасность» ниже.

Возможности и требования

  • ID модуля: uplab.api
  • Неймспейс: Uplab\Api\
  • PHP ≥ 8.1
  • Язык по умолчанию: ru
  • Обязательные модули Битрикс зависят от используемых endpoint-ов: iblock, highloadblock, form, search.

Установка

Через Composer

Модуль объявлен как "type": "bitrix-module" и требует composer/installers, поэтому раскладывается не в vendor/, а в каталог модулей Битрикса. Путь задаёт проект-потребитель: без настройки плагин использует каталог по умолчанию и модуль встанет в bitrix/modules/uplab.api (работать будет, но bitrix/ обычно исключён из репозитория проекта). Чтобы модуль ставился в local/modules, добавьте в корневой composer.json проекта:

{
    "extra": {
        "installer-paths": {
            "local/modules/{$name}/": ["type:bitrix-module"]
        }
    },
    "config": {
        "allow-plugins": {
            "composer/installers": true
        }
    }
}
composer require uplabteam/uplab.api

Модуль установится в local/modules/uplab.api. Дальше — шаги 2–4 из раздела «Вручную».

Альтернатива: "extra": {"bitrix-dir": "local"} — ключ, специфичный для Bitrix-установщика, даёт тот же результат. installer-paths предпочтительнее: это стандартный механизм composer/installers и он явно показывает целевой путь.

Вручную

  1. Скопируйте репозиторий в <DOCUMENT_ROOT>/local/modules/uplab.api.
  2. Установите модуль в административной части: Marketplace → Установленные решения.
  3. Подключите маршруты модуля в пользовательском файле маршрутов, например /local/routes/uplab_api.php:
<?php

use Bitrix\Main\Routing\RoutingConfigurator;
use Uplab\Api\Routing\Configurator;

return static function (RoutingConfigurator $routes): void {
    Configurator::set($routes);
};
  1. Добавьте имя файла в секцию routing файла /local/.settings.php:
'routing' => [
    'value' => [
        'config' => ['uplab_api.php'],
    ],
    'readonly' => true,
],

После установки встроенная документация доступна в административном меню Сервисы → API Документация.

Безопасность

  • Публичные выборки инфоблоков выполняются с CHECK_PERMISSIONS=Y; кеш разделяется по группам текущего пользователя.
  • HL-блок публичен, пока никому не выдано право чтения hl_element_read; как только право выдано, читают только его обладатели и администратор. Кеш также разделяется по группам.
  • Origin — управляемый клиентом заголовок и не является аутентификацией. Для закрытого API используйте права Битрикс, IP allowlist и/или авторизацию на веб-сервере.
  • Сообщайте об уязвимостях приватно по правилам из SECURITY.md, а не через публичный issue.

Лицензия

Модуль распространяется по проприетарной лицензии — LICENSE.txt. Правообладатель — ООО «Аплэб». Коротко:

  • можно безвозмездно — устанавливать и использовать Модуль на любом числе проектов, включая коммерческие; изучать код; настраивать поведение штатными средствами (наследование классов, события, точки расширения); изменять Модуль под нужды своих проектов и проектов заказчиков, в том числе исправлять ошибки; передавать Модуль (исходный или изменённый) заказчику в составе результата работ или иным лицам — безвозмездно, с сохранением текста лицензии и указаний на авторство и с пометкой, что копия изменена;
  • можно брать плату — за работы по разработке, внедрению, настройке, доработке и поддержке проектов, в которых используется Модуль: это не продажа Модуля;
  • нельзя — продавать Модуль или его изменённые версии, сдавать в аренду, сублицензировать и иначе брать плату за сам Модуль или право его использования; включать Модуль в платные тиражные решения, шаблоны и сборки; размещать его от своего имени в каталогах решений и магазинах приложений; распространять Модуль и производные от него работы под другой лицензией; выдавать за собственную разработку и снимать указания на авторство.

Изменение Модуля не даёт прав на Модуль: исключительные права остаются у правообладателя. Право на продажу и распространение на иных условиях предоставляется по отдельному договору: info@uplab.ru. Исправления и улучшения присылайте pull request'ом — они попадут в общий релиз, и правку не придётся поддерживать у себя.

Гарантии и ответственность

Модуль распространяется бесплатно и предоставляется «как есть» (as is). Правообладатель не даёт никаких гарантий: ни того, что модуль подходит для вашей задачи, ни того, что в нём нет ошибок или уязвимостей, ни того, что он будет работать без сбоев на вашей конфигурации.

  • Вы используете модуль на свой страх и риск. Решение об установке принимаете вы, и вся ответственность за работу сайта после установки — на вас.
  • Правообладатель не отвечает за последствия использования или невозможности использования модуля: простой сайта, потерю или порчу данных, недополученную прибыль, утечку данных и взлом сайта — в том числе если вектором атаки стала ошибка или уязвимость в модуле.
  • Проверяйте перед боевым запуском. Разверните модуль на тестовом контуре, пройдите разделы «Безопасность» и «Доступ к API», оцените риски применительно к своим данным. Публичный API отдаёт наружу ровно то, что вы ему разрешите настройками и правами Битрикс.
  • Если такой режим ответственности вас не устраивает — не устанавливайте модуль. Промежуточных условий нет: использование означает согласие с ними.
  • Поддержка не гарантирована. Правообладатель не обязан выпускать обновления, исправлять ошибки и отвечать на обращения. Об уязвимостях сообщайте приватно по правилам из SECURITY.md — исправления выпускаются по возможности.
  • За чужие правки ответственности нет. Если модуль изменяли (вы или подрядчик), последствия таких изменений — вне зоны ответственности правообладателя.

Полные условия — в LICENSE.txt, раздел 4 (пункты 4.1–4.6).

Настройки модуля

Настройки доступны в Битрикс: Настройки → Настройки продукта → Настройки модулей → uplab.api.

Вкладка «Общие настройки»

Параметр По умолчанию Описание
SHOW_PAGE_CONTENT_TAB N Выводить вкладку «Текстовый контент» на странице элемента инфоблока в административном интерфейсе.

Вкладка «Доступ к API»

Управляет ограничением доступа к публичным API endpoint-ам. По умолчанию всё отключено — никаких ограничений нет.

Параметр По умолчанию Описание
API_ACCESS_ENABLED N Включить проверку доступа. При выключенном чекбоксе (значение по умолчанию) все запросы пропускаются; при включённом, но с двумя пустыми списками — тоже.
API_ALLOWED_ORIGINS (пусто) Разрешённые значения заголовка Origin (по одному на строку) — для браузерных запросов. Схема необязательна: site.ru и https://site.ru равнозначны.
API_ALLOWED_IPS (пусто) Разрешённые значения REMOTE_ADDR (по одному на строку) — для серверных запросов без Origin (Nuxt SSR и т.п.). Пример: 127.0.0.1.

Логика проверки (lib/ActionFilter/ApiAccessFilter.php):

  1. Если API_ACCESS_ENABLED = N → запрос пропускается (значение по умолчанию: API открыт).
  2. Если оба списка пусты → запрос пропускается: правил нет, ограничивать нечем.
  3. Если REMOTE_ADDR есть в IP-списке → запрос пропускается.
  4. Иначе, если заголовок Origin есть в Origin-списке → запрос пропускается.
  5. Не прошёл ни по одному заданному списку → 403 Access denied.

Списки проверяются по «ИЛИ»: браузерные запросы проходят по Origin, серверные (без заголовка) — по IP. Поэтому рабочая конфигурация для схемы «браузер + SSR» — заполнить оба списка. Обхода «снять заголовок Origin» нет: запрос без заголовка обязан пройти по IP-списку, а если он пуст — не проходит.

При работе через reverse proxy убедитесь, что REMOTE_ADDR содержит адрес доверенного прокси, либо ограничивайте доступ на самом прокси. Заголовки X-Forwarded-For модуль намеренно не принимает без модели доверенных прокси.

Разработка

В composer.json объявлен config.allow-plugins для composer/installers: без него Composer 2.2+ блокирует плагин и composer install в самом репозитории модуля падает (в том числе на CI).

composer install            # локально: с dev-зависимостями (нужны для юнит-тестов)
composer install --no-dev   # на сервере: без PHPUnit и прочего dev-окружения
composer update             # обновить зависимости

composer.lock в репозиторий не коммитится (.gitignore): prod-зависимостей у модуля нет, а lock пакета потребители всё равно не читают — при установке решает lock корневого проекта. Локально он создаётся сам и фиксирует версии dev-инструментов только на вашей машине.

Сборка и линтер не настроены (линтер php -l выполняется в CI).

Dev-файлы (tests/, phpunit.xml.dist, CI-конфиг) помечены export-ignore в .gitattributes — в дистрибутив пакета они не попадают. Если модуль разворачивается копированием репозитория, папка tests/ окажется на сервере: она лежит внутри /bitrix/modules/, куда штатный /bitrix/modules/.htaccess (Deny from All) закрывает веб-доступ, а tests/bootstrap.php дополнительно прерывает выполнение вне CLI. На nginx убедитесь, что доступ к /bitrix/modules/ закрыт серверным конфигом (в bitrix-env это так по умолчанию).

Юнит-тесты (только локально, dev)

Юнит-тесты покрывают логику модуля и запускаются без ядра Bitrix — на машине разработчика, без сайта и базы.

composer install   # PHPUnit ставится как dev-зависимость
composer test      # то же самое, что vendor/bin/phpunit

Инфраструктура:

  • phpunit.xml.dist — конфиг (свой локальный можно положить в phpunit.xml, он в .gitignore);
  • tests/bootstrap.php — заглушки Bitrix, нужные только для загрузки классов (Loader, Loc, CIBlockRights::PUBLIC_READ, константа LANGUAGE_ID). К базе заглушки не обращаются;
  • tests/Unit/ — сами тесты; PSR-4 автозагрузка Uplab\Api\lib/ и Uplab\Api\Tests\tests/ объявлена в autoload-dev, поэтому на прод (composer install --no-dev) ничего из этого не попадает.

Как писать тесты без ядра. Обращения к API Bitrix вынесены в тонкие методы-обёртки (в IblockProperty это блок «Обращения к API Битрикс»: fetchPropertyValueRows(), fetchSections(), fetchEnumValues() и т.п.). Тест наследует класс, подменяет обёртки готовыми массивами строк выборки и проверяет чистую логику — см. tests/Unit/Iblock/FakeIblockProperty.php. Новую логику размещайте так же: запрос к Bitrix — в отдельной обёртке, преобразование данных — в отдельном методе.

Синхронизация документации (ОБЯЗАТЕЛЬНО)

Документация и тесты существуют в трёх местах и должны поддерживаться в актуальном состоянии при каждом изменении API-кода (эндпоинты, параметры, конфиги, значения по умолчанию, фильтры/права, формат ответа, события):

  1. README.md — этот файл (справочник по эндпоинтам).
  2. Встроенная документация в админкеlib/Admin/Docs.php (структурированные данные всех эндпоинтов/событий, отображаются в Сервисы → API Документация).
  3. Тестовая коллекцияtests/uplab.api.postman_collection.json (Postman/Insomnia): запросы и проверки (pm.test) по всем эндпоинтам.

Данные в Docs.php намеренно не парсятся из README — при правке кода обновляйте оба источника согласованно (параметры, значения по умолчанию, примеры, коды ответов, права/фильтры). Коллекцию тестов синхронизируйте с новым поведением (новые запросы, актуальные проверки статуса/формата). PR с изменением поведения API без обновления README, Docs.php и тестов считается неполным.

Версионирование и релизы

Используется семантическое версионирование (MAJOR.MINOR.PATCH).

При каждом изменении версии ОБЯЗАТЕЛЬНО обновлять оба файла:

  1. install/version.php — поднять VERSION и проставить актуальную VERSION_DATE.
  2. CHANGELOG.md — добавить новую секцию ## [VERSION] - YYYY-MM-DD с описанием изменений (### Added / ### Changed / ### Fixed / ### Removed) и ссылками на изменённые файлы. Пропускать CHANGELOG нельзя — он часть релиза наравне с version.php.

Публикация в GitHub

Разработка ведётся во внутреннем репозитории (полная история, MR, CI), в публичный репозиторий GitHub уходит один коммит-снимок на релиз — внутренняя история и ключи задач не публикуются. Публичной историей служит CHANGELOG.md.

Один раз добавьте remote:

git remote add github git@github.com:<org>/uplab.api.git

Публикация после того, как релиз влит в master и версия поднята:

tools/publish-github.sh --dry-run   # показать, что уйдёт, без отправки
tools/publish-github.sh             # собрать снимок и запушить в github/master

Скрипт берёт версию из install/version.php, собирает дерево HEAD без внутренних файлов (.gitlab-ci.yml, сам скрипт), делает коммит uplab.api X.Y.Z поверх предыдущего опубликованного состояния и отправляет его в github/master. Рабочая копия при этом не меняется. Автором публичного коммита указывается организация, а не разработчик, — личные адреса из локальной конфигурации git в снимок не попадают (переопределяется переменными PUBLISH_AUTHOR_NAME и PUBLISH_AUTHOR_EMAIL). Тег и релиз создаются на стороне GitHub (командой gh release create, которую скрипт печатает после пуша) — так имя публичного тега совпадает с внутренним, а локальные теги не конфликтуют.

Теги релизов — только аннотированные (annotated), не lightweight:

git tag -a vX.Y.Z -m "vX.Y.Z — краткое описание релиза"
git push origin vX.Y.Z

Тег ставится на master уже с влитыми изменениями. Lightweight-теги (git tag vX.Y.Z без -a) для релизов использовать нельзя — у них нет тегера, даты и аннотации.

Архитектура

Три слоя:

  • Контроллеры (lib/Controller/) — принимают HTTP-запрос, создают объект конфига из JSON body и делегируют в сервис.
  • Сервисы (lib/Model/) — бизнес-логика, запросы к Bitrix API, кеширование.
  • Конфиги (lib/Utils/Config/) — Fluent-объекты, описывающие параметры запроса. Создаются через ClassName::create(array|null $body).

Точка входа: include.phpEventManager::bindEvents(). Все маршруты — lib/Routing/Configurator.php.

Все параметры передаются через JSON body (Content-Type: application/json), в том числе для ANY/GET запросов — читаются из тела запроса (HttpRequest::getInput()).

Тело с Content-Type: application/x-www-form-urlencoded или multipart/form-data как JSON не разбирается — такие поля читает сам эндпоинт из запроса (это отправка веб-формы, POST /api/forms/{code}/add). Любое другое тело разбирается как JSON независимо от заголовка: валидный JSON-объект принимается и без Content-Type. Заголовок влияет только на реакцию на ошибку разбора — если клиент объявил JSON (application/json, *+json), некорректное или не-объектное тело даёт 400; если тип не объявлен или не JSON, такое тело игнорируется и параметры берутся по умолчанию.

Ограничения запросов

Некорректные или чрезмерно большие параметры отклоняются до обращения к БД:

Ограничение Значение
Тело запроса не более 64 КиБ, глубина JSON не более 32; превышение размера — 413
Content-Type тело формы (x-www-form-urlencoded, multipart/form-data) как JSON не разбирается; валидный JSON принимается и без заголовка; объявленный JSON с некорректным телом — 400
limit / offset 1…500 / 0…100000
filter и другие массивы условий не более 1000 элементов суммарно (с учётом вложенных), глубина не более 5
select / propertyCode не более 100 полей
order не более 10 полей; направления ASC, DESC, ASC,NULLS, DESC,NULLS, NULLS,ASC, NULLS,DESC
siteId 1–32 символа: латиница, цифры, _ и -; пустая строка = фильтр по сайту не применяется

Нарушение лимитов параметров возвращает 400 Bad Request. Эти ограничения защищают БД и кеш от случайных и намеренно дорогих запросов; при проектном расширении API не обходите конфиг-классы.

Переопределение лимитов запросов

Лимиты заданы константами MAX_* в Uplab\Api\Utils\Config\BaseConfig, а проверки обращаются к ним через static::, поэтому проект меняет любой лимит наследованием — тела проверок копировать не нужно. Наследник конфига подключается фабрикой класса конфига в контроллере (get*ConfigClass()), контроллер — своим маршрутом.

class ProjectListConfig extends \Uplab\Api\Utils\Config\Iblock\IblockListElementsConfig
{
    public const MAX_LIMIT = 2000;
}

class ProjectIblockController extends \Uplab\Api\Controller\IblockController
{
    protected function getElementListConfigClass(): string
    {
        return ProjectListConfig::class;
    }
}

Лимит можно как поднять, так и ужесточить; остальные эндпоинты продолжают работать со штатными значениями — переопределение действует только там, где подключён наследник. Помните, зачем лимиты существуют: limit и глубина filter напрямую определяют стоимость запроса к БД, а разнообразие параметров — число вариантов кеша.

Константа По умолчанию На что влияет
MAX_LIMIT 500 limit
MAX_OFFSET 100000 offset
MAX_FILTER_ITEMS 1000 число элементов в filter и других массивах условий
MAX_FILTER_DEPTH 5 глубина вложенности массивов условий
MAX_SELECT_FIELDS 100 select, propertyCode
MAX_ORDER_FIELDS 10 order
MAX_FIELD_NAME_LENGTH 128 длина имени поля в filter, select, order, propertyCode, а также длина кода в пути ({iblockCode} и т.п.)
MAX_STRING_VALUE_LENGTH 4096 длина строкового значения в массивах условий
ORDER_DIRECTIONS ASC, DESC, ASC,NULLS, DESC,NULLS, NULLS,ASC, NULLS,DESC допустимые направления сортировки

Фабрики класса конфига:

Фабрика Контроллер Эндпоинт
getIblockConfigClass() IblockController /api/iblock/{code}
getElementListConfigClass() IblockController /api/iblock/{code}/elements
getElementDetailConfigClass() IblockController /api/iblock/{code}/element/code|id/{...}
getSectionListConfigClass() IblockController /api/iblock/{code}/sections
getSectionDetailConfigClass() IblockController /api/iblock/{code}/section/code|id/{...}
getElementFilterConfigClass() IblockController /api/iblock/{code}/filter
getIblockPropertiesConfigClass() IblockController /api/iblock/{code}/properties
getElementListConfigClass() HlblockController /api/hlblock/{code}/elements
getElementFilterConfigClass() HlblockController /api/hlblock/{code}/filter
getFieldsConfigClass() HlblockController /api/hlblock/{code}/fields
getPageConfigClass() PageController /api/pages/{code}
getMenuConfigClass() MenuController /api/menus/{code}

Объект конфига создаётся в BaseController::createConfig(), так что переопределять экшены не требуется.

У поиска и административных эндпоинтов контента объекта конфига нет — там лимиты меняются переопределением методов модели или контроллера:

Метод Класс Что задаёт
getMaxLimit() Uplab\Api\Model\Search limit в /api/search
getMaxOffset() Uplab\Api\Model\Search offset в /api/search
getMaxPageSize() Uplab\Api\Controller\ContentController pageSize в POST /api/content/admin/{pageCode}
getMaxOffset() Uplab\Api\Controller\ContentController максимальное смещение там же
getMaxBulkIds() Uplab\Api\Controller\ContentController число ids в POST /api/content/bulk

Наследник Search подключается через SearchController::getModelClass(), как остальные модели.

Тексты ошибок

Сообщения ошибок приходят клиенту из языковых сообщений модуля (UPLAB_API.ERROR_*, файлы lang/ru/include.php и lang/en/include.php), а не из строк в коде. Язык выбирается штатным механизмом Битрикс, лимиты подставляются в текст через плейсхолдер #MAX#:

{ "type": "about:blank", "title": "Bad Request", "status": 400, "detail": "Параметр limit должен быть от 1 до 500." }

Чтобы изменить формулировку на проекте, переопределите нужное сообщение штатным способом Битрикс — через /local/php_interface/ или языковые файлы проекта; правка модуля не требуется. Если проект поднял лимит наследованием конфига, в текст подставится лимит наследника.

HTTP-статусы задаются кейсами Uplab\Api\Enums\HttpStatus, а не числами: ApiException принимает и кейс, и int (для обратной совместимости), а наружу отдаёт число.

Формат ответа

Сервисные эндпоинты (инфоблоки, highload-блоки, страницы, формы, меню, поиск, карта сайта) возвращают сырые данные эндпоинта в теле ответа — без обёртки:

{ "items": [ /* ... */ ], "meta": { "total": 128, "limit": 10, "offset": 0 } }

Ожидаемые ошибки (не найдено, не установлен зависимый модуль и т.п.) — соответствующий HTTP-статус (404, 424 и т.п.) и тело:

{ "message": "Инфоблок не найден." }

Намеренные клиентские ошибки. Код может бросить Uplab\Api\Exception\ApiException($message, $httpStatus) — ответ будет с указанным 4xx-статусом и телом в формате RFC 9457 (Content-Type: application/problem+json):

{ "type": "about:blank", "title": "Not Found", "status": 404, "detail": "Форма не найдена." }

Внутренние ошибки (необработанные исключения)HTTP 500 и тело RFC 9457, без раскрытия классов, сигнатур и стек-трейса:

{ "type": "about:blank", "title": "Internal Server Error", "status": 500, "detail": "Внутренняя ошибка сервера.", "code": "UPLAB_API_INTERNAL_ERROR" }

Полные детали (класс, сообщение, файл:строка, трейс, URL) пишутся в Журнал событий (тип UPLAB_API_INTERNAL_ERROR), администратору показывается уведомление со ссылкой на журнал. Перехват централизован в BaseController::getActionResponse() (плюс страховка runProcessingException/runProcessingError для исключений вне тела экшена).

nginx. На bitrix-env по умолчанию fastcgi_intercept_errors on, поэтому тело любого 5xx-ответа nginx подменяет своей HTML-страницей — на HTTP 500 problem+json до клиента не дойдёт (код 500 и запись в лог при этом корректны). Чтобы тело 500 доходило — отключите перехват для /api/ (см. раздел ниже). Ответы 4xx (включая ApiException) проходят всегда.

Исключение — административные эндпоинты контента (POST /api/content/admin/{pageCode}, POST /api/content/bulk, DELETE /api/content/admin/{elementId}): они построены на стандартном контроллере Bitrix и используют конверт { "status": "success"|"error", "data": …, "errors": [] }.

Опционально: настоящий HTTP 500 для ошибок (nginx)

По умолчанию bitrix-env включает fastcgi_intercept_errors on; и error_page 500 …, поэтому любой ответ с кодом 5xx от PHP nginx подменяет своей HTML-страницей. Если для /api/ вам нужен именно HTTP 500 с JSON-телом — на стороне nginx отключите перехват для этого префикса (требуется доступ к конфигу сервера):

location /api/ {
    fastcgi_intercept_errors off;   # или proxy_intercept_errors off; — в зависимости от схемы
    # ... остальная проксирующая/fastcgi-конфигурация как в основном location ...
}

Без этой правки HTTP-код 500 и запись в Журнал событий остаются корректными, но тело 500-ответа заменяется HTML-страницей nginx. Ответы 4xx (в т.ч. ApiException) не перехватываются и доходят с JSON всегда.

API Endpoints

ANY означает, что маршрут принимает любой HTTP-метод (GET, POST и т.д.).

Общий параметр для всех эндпоинтов

Параметр Тип Описание
siteId string ID сайта Bitrix. По умолчанию: SITE_ID текущего сайта.

Инфоблоки — /api/iblock

Поведение по умолчанию (списки и детальные)

Базовый фильтр. Ко всем выборкам элементов/разделов инфоблока всегда применяется базовый фильтр: ACTIVE=Y, IBLOCK_ACTIVE=Y, CHECK_PERMISSIONS=Y, MIN_PERMISSION=PUBLIC_READ, привязка к инфоблоку (IBLOCK_ID) и к сайту (IBLOCK_LID=siteId). Переданный параметр filter только добавляет условия к базовому и не может переопределить эти ключи — проверка прав, активность и привязка к инфоблоку/сайту сохраняются всегда (это защита от обхода прав через тело запроса).

Управляющие ключи фильтра игнорируются. В фильтре Битрикс есть ключи, которые не добавляют условие, а меняют способ сборки запроса, поэтому порядок слияния от них не защищает. Такие ключи вырезаются из переданного filter на верхнем уровне: LOGIC, CHECK_PERMISSIONS, MIN_PERMISSION, PERMISSIONS_BY, SHOW_HISTORY, SHOW_NEW, SHOW_BP_NEW, CHECK_BP_PERMISSIONS (сравнение не зависит от префикса-оператора: !CHECK_PERMISSIONS тоже вырезается). Без этого {"LOGIC":"OR"} превращал базовые условия в альтернативы (снимая привязку к инфоблоку, сайту и ACTIVE=Y), PERMISSIONS_BY заставлял ядро проверять права от имени другого пользователя, а SHOW_HISTORY/SHOW_NEW отдавали черновики и записи истории документооборота. Во вложенных группах фильтра LOGIC работает как обычно: такая группа присоединяется к запросу через AND и может только сузить выборку.

{ "filter": { "PROPERTY_BRAND": 42, "0": { "LOGIC": "OR", "NAME": "a", "CODE": "b" } } }

Сортировка по умолчанию. Для списков элементов/разделов, если order не передан, применяется {"SORT":"ASC","NAME":"ASC"}; для списка свойств — {"sort":"asc","name":"asc"}. Переданный order заменяет сортировку целиком.

Пагинация по умолчанию. limit=10, offset=0 (для эндпоинтов со списками элементов/разделов).

Кеш и права. Ключ кеша включает отсортированный набор групп текущего пользователя — так же, как в штатных компонентах Битрикс при CACHE_GROUPS=Y. Ответ, сформированный для администратора или закрытой группы, не будет отдан анонимному пользователю.

Для инфоблоков в расширенном режиме прав (RIGHTS_MODE = E) групп недостаточно: там права выдаются ещё и персонально (код доступа U<ID>), по отделу и на «создателя» (CR), поэтому двум пользователям с одинаковыми группами доступны разные наборы элементов. Для таких инфоблоков в ключ кеша добавляется и ID пользователя. В простом режиме (RIGHTS_MODE = S) видимость — функция набора групп, и ключ остаётся групповым, чтобы кеш не дробился по каждому авторизованному пользователю. Для анонимных запросов оба варианта дают один общий кеш.

Точка расширения: IblockService::getAccessCacheId() и IblockProperty::getAccessCacheId() — переопределите, если наследник добавляет в ответ данные, зависящие от пользователя.

propertyCode необязателен. Если propertyCode не передан (и в инфоблоке включены «Свойства инфоблоков»/property features), набор свойств определяется автоматически:

  • для списков элементов — свойства с включённым флагом «Показывать на странице списка элементов» (getListPageShowPropertyCodes);
  • для детальных карточек элемента — свойства с включённым флагом «Показывать на детальной странице элемента» (getDetailPageShowPropertyCodes).

Передайте "propertyCode": ["*"], чтобы получить все свойства, либо явный список кодов — чтобы ограничить выборку.

ANY /api/iblock/{iblockCode}

Информация об инфоблоке.

Контроллер: IblockController::getIblockAction Модель: IblockService Конфиг: IblockDetailConfig

Параметр Тип Описание
siteId string ID сайта
filter array Дополнительный фильтр
select string[] Список полей для выборки

ANY /api/iblock/{iblockCode}/elements

Список элементов инфоблока с пагинацией.

Контроллер: IblockController::getElementListAction Модель: IblockService Конфиг: IblockListElementsConfig

Параметр Тип По умолчанию Описание
siteId string SITE_ID ID сайта
filter array [] Фильтр в формате Bitrix ({"ACTIVE": "Y"})
select string[] [] Список полей
order array {"SORT":"ASC","NAME":"ASC"} Сортировка. Если не передать — применяется значение по умолчанию
limit int 10 Кол-во элементов на странице
offset int 0 Смещение
propertyCode string[] (см. «Поведение по умолчанию») Символьные коды свойств. Необязателен: без него — свойства с флагом «Показывать на странице списка элементов»
SHOW_TOTAL 'Y'|'N' 'Y' Включить total в ответ
SHOW_IPROPERTY_VALUES 'Y'|'N' 'N' Выводить SEO-поля (iPropertyValues)
HIDE_DISPLAY_VALUES 'Y'|'N' 'N' Скрыть DISPLAY_VALUES у свойств (уменьшает объём ответа)
SHOW_PREFIX_FIELD_DATA 'Y'|'N' 'N' Показывать поля с префиксом ~
GET_PROPERTIES_PROPERTY object {} Свойства связанных элементов: {"VENDORS": ["LOGO"]}
activeDateFormat string null Формат даты ACTIVE_FROM (напр. d.m.Y)
activeToDateFormat string null Формат даты ACTIVE_TO
parentSectionId int null Фильтр по ID родительского раздела
parentSectionCode string null Фильтр по символьному коду родительского раздела

Случайная сортировка (RAND)

При передаче "order": {"RAND": "ASC"} ответ кешируется на 30 секунд вместо стандартных ~24 часов. Это позволяет возвращать разные случайные наборы элементов, не нагружая базу данных: максимум 1 запрос к БД в 30 секунд на каждую уникальную комбинацию параметров.

{ "order": { "RAND": "ASC" }, "limit": 5 }

ANY /api/iblock/{iblockCode}/element/code/{elementCode}

Детальная карточка элемента инфоблока по символьному коду.

Контроллер: IblockController::getElementDetailByCodeAction Модель: IblockService Конфиг: IblockDetailElementConfig

Параметр Тип Описание
siteId string ID сайта
filter array Дополнительный фильтр
select string[] Список полей
propertyCode string[] Символьные коды свойств. Необязателен: без него — свойства с флагом «Показывать на детальной странице элемента»
activeDateFormat string Формат даты ACTIVE_FROM
activeToDateFormat string Формат даты ACTIVE_TO
HIDE_DISPLAY_VALUES 'Y'|'N' Скрыть DISPLAY_VALUES у свойств
SHOW_PREFIX_FIELD_DATA 'Y'|'N' Показывать поля с ~
GET_PROPERTIES_PROPERTY object Свойства связанных элементов
parentSectionId int ID родительского раздела
parentSectionCode string Символьный код родительского раздела

ANY /api/iblock/{iblockCode}/element/id/{elementId}

Детальная карточка элемента инфоблока по ID.

Контроллер: IblockController::getElementDetailByIdAction Конфиг: IblockDetailElementConfig — параметры идентичны /element/code/{elementCode}.

ANY /api/iblock/{iblockCode}/sections

Список разделов инфоблока с пагинацией.

Контроллер: IblockController::getSectionListAction Модель: IblockService Конфиг: IblockListSectionsConfig

Параметр Тип По умолчанию Описание
siteId string SITE_ID ID сайта
filter array [] Фильтр
select string[] [] Список полей
order array null Сортировка
limit int 10 Кол-во на странице
offset int 0 Смещение
bIncCnt bool|array null Параметр bIncCnt для CIBlockSection::GetList()
SHOW_TOTAL 'Y'|'N' 'Y' Включить total в ответ
parentSectionId int null ID родительского раздела
parentSectionCode string null Символьный код родительского раздела

ANY /api/iblock/{iblockCode}/section/code/{sectionCode}

Детальная информация о разделе инфоблока по символьному коду.

Контроллер: IblockController::getSectionDetailByCodeAction Модель: IblockService Конфиг: IblockDetailSectionConfig

Параметр Тип Описание
siteId string ID сайта
filter array Дополнительный фильтр
select string[] Список полей
parentSectionId int ID родительского раздела
parentSectionCode string Символьный код родительского раздела

ANY /api/iblock/{iblockCode}/section/id/{sectionId}

Детальная информация о разделе инфоблока по ID.

Контроллер: IblockController::getSectionDetailByIdAction Конфиг: IblockDetailSectionConfig — параметры идентичны /section/code/{sectionCode}.

Хлебные крошки (BREADCRUMBS) в детальных ответах

Детальные эндпоинты инфоблока добавляют в ответ готовую цепочку навигации — ключ BREADCRUMBS верхнего уровня. Параметрами не управляется, формируется всегда и кешируется вместе с ответом.

Где есть: /element/code/{elementCode}, /element/id/{elementId}, /section/code/{sectionCode}, /section/id/{sectionId}. Где нет: в списках (/elements, /sections) и в /api/pages/{code}PageService цепочку не строит (если в ответе страницы встречается BREADCRUMBS, это обычное свойство инфоблока внутри PROPERTIES).

Формат — массив звеньев в порядке от корня к текущей странице:

{
  "BREADCRUMBS": [
    { "TEXT": "Каталог", "HREF": "/catalog/" },
    { "TEXT": "Ноутбуки", "HREF": "/catalog/noutbuki/" },
    { "TEXT": "ThinkPad X1" }
  ]
}

Детальная элемента (lib/Model/Iblock/IblockService.php:543):

  1. Инфоблок — TEXT = название инфоблока, HREF = LIST_PAGE_URL элемента. Звено добавляется, только если инфоблок определён.
  2. Цепочка разделов от корня до раздела элемента (CIBlockSection::GetNavChain по IBLOCK_SECTION_ID) — TEXT = SEO-заголовок раздела IPROPERTY_VALUES.SECTION_PAGE_TITLE, при пустом — NAME; HREF = SECTION_PAGE_URL. Если элемент вне разделов, звеньев нет.
  3. Сам элемент — TEXT = IPROPERTY_VALUES.ELEMENT_PAGE_TITLE, при пустом — NAME. Ключа HREF у последнего звена нет (не пустая строка, а отсутствующий ключ).

Попутно детальная элемента отдаёт SECTION_URL — URL последнего раздела цепочки (пустая строка, если элемент вне разделов).

Детальная раздела (lib/Model/Iblock/IblockService.php:865):

  1. Инфоблок — TEXT = название инфоблока, HREF = LIST_PAGE_URL раздела.
  2. Цепочка навигации, включая сам раздел: TEXT = IPROPERTY_VALUES.SECTION_PAGE_TITLE или NAME; HREF = SECTION_PAGE_URL, а у звена самого текущего раздела HREFпустая строка (ключ присутствует).

ANY /api/iblock/{iblockCode}/filter

Данные для построения фильтра по элементам инфоблока: значения свойств, у которых в настройках инфоблока включено «Показывать в умном фильтре». Выводятся только значения, реально использованные в доступных элементах.

Контроллер: IblockController::getElementFilterAction Модель: IblockProperty Конфиг: IblockFilterConfig

Параметр Тип Описание
siteId string ID сайта
propertyCode string[] Символьные коды свойств для фильтра
filter array Дополнительный фильтр. Сужает набор значений: значения собираются только по элементам, попадающим под фильтр
select string[] Список полей
parentSectionId int ID раздела
parentSectionCode string Символьный код раздела

Сужение значений переданным filter

Значения каждого свойства собираются по элементам, отобранным с учётом filter. Условие по самому свойству (PROPERTY_<КОД>) при сборке его значений из фильтра исключается — иначе свойство сужало бы собственный список и в фильтре оставался бы только уже выбранный вариант. Переданный filter входит в ключ кеша.

Исключение работает по точному совпадению ключа: PROPERTY_COLOR из сборки значений свойства COLOR убирается, а ключ с префиксом-оператором (>=PROPERTY_PRICE, !PROPERTY_COLOR) — нет, такое условие сузит список значений своего же свойства. Для фасетов передавайте условия по свойству без операторов.

{ "filter": { "PROPERTY_BRAND": 42 } }

Проверка прав. Значения собираются с CHECK_PERMISSIONS=Y и MIN_PERMISSION=PUBLIC_READ для всех типов свойств — значения из недоступных элементов в фильтр не попадают. Это же относится к вторичным выборкам: названия связанных элементов и разделов (свойства E и G) берутся с проверкой прав на инфоблок-справочник, поэтому из закрытого инфоблока названия не утекают.

Кеш. Ответ кешируется на сутки с тегом инфоблока (iblock_id_{ID}), а для свойств-справочников дополнительно с тегом HL-блока (hlblock_id_{ID}) — правка записей справочника в админке сбрасывает кеш фильтра.

Поддерживаемые типы свойств: L (список), E (привязка к элементу), G (привязка к разделу), S (строка, в том числе directory — справочник HL-блока). Свойство, у которого в выборке нет значений, в ответ не попадает. Числовые свойства (N) и любые пользовательские типы сторонних модулей модуль из коробки не обрабатывает — они добавляются на проекте, см. «Расширение через наследование (IblockProperty)».

Формат items: для списка (L) value — ID значения перечисления (ENUM_ID), text — его название; для привязок (E, G) — ID элемента/раздела и его NAME; для справочника (directory) — UF_XML_ID и название значения справочника.

[
  {
    "id": "105",
    "propertyType": "L",
    "code": "COLOR",
    "name": "Цвет",
    "multiple": "N",
    "displayType": "F",
    "items": [
      { "id": "12", "value": "12", "text": "Красный" },
      { "id": "13", "value": "13", "text": "Синий" }
    ]
  }
]

ANY /api/iblock/{iblockCode}/properties

Все свойства инфоблока.

Контроллер: IblockController::getIblockPropertiesAction Конфиг: IblockListConfig

Параметр Тип Описание
siteId string ID сайта
filter array Фильтр
select string[] Список полей
order array Сортировка

Расширение через наследование (IblockService)

Кешируемые выборки инфоблоков имеют точки расширения — проект наследует IblockService и переопределяет только нужный хук, не копируя тела методов. Наследник подключается через getModelClass() своего контроллера (аналогично SitemapController, см. раздел «Карта сайта»).

Хуки payload — вызываются внутри кешируемой области, перед сохранением в кеш (поэлементные — в цикле выборки); всё добавленное попадает в кеш (по умолчанию ~24 часа, тег iblock_id_{ID}), поэтому не полагайтесь в них на per-request данные (текущий пользователь, время и т.п.):

Порядок параметров единый: данные → сырые поля → сырые свойства → объект выборки → конфиг (конфиг всегда последним, как в остальных методах сервиса).

Метод Область Эндпоинт
appendElementListData(array $data, $config): array весь payload (items + meta) /api/iblock/{code}/elements
appendElementListItemData(array $arItem, array $rawFields, array $rawProperties, $obj, $config): array один элемент списка /api/iblock/{code}/elements
appendElementDetailData(array $data, array $rawFields, array $rawProperties, $obj, $config): array детальная карточка /api/iblock/{code}/element/code|id/{...}
appendSectionListData(array $data, $config): array весь payload (items + meta) /api/iblock/{code}/sections
appendSectionListItemData(array $arSection, array $rawFields, $config): array один раздел списка /api/iblock/{code}/sections
appendSectionDetailData(array $data, array $rawFields, $obj, $config): array детальная раздела /api/iblock/{code}/section/code|id/{...}

Дополнительные аргументы хуков:

  • $rawFields — снимок полей сразу после выборки (GetFields()/GetNext()), до преобразований для фронта: с ~-полями и системными полями (EXTERNAL_ID, IBLOCK_TYPE_ID и т.п.), которые из итогового $data вырезаются. Для разделов включает UF-поля.
  • $rawProperties — свойства элемента из $obj->GetProperties() до преобразований (convertPropertiesDataForFront). Заполнены, только если свойства запрошены (propertyCode передан или коды определены через property features); иначе пустой массив — при необходимости вызовите $obj->GetProperties() сами.
  • $obj — объект строки выборки (_CIBElement), где есть: доступны его методы без повторного запроса к БД.

Хуки ключа кешаgetElementListCacheKeyParts(), getElementDetailCacheKeyParts(), getSectionListCacheKeyParts(), getSectionDetailCacheKeyParts() (сигнатура (array $parts, $config): array). Если добавленные данные зависят от входных параметров — добавьте эти параметры к $parts, иначе разные входы склеятся в одну кеш-запись. Базовые части (включая static::class — он разводит кеш наследника и базового класса) не удаляйте.

Если данные для хука берутся из другого инфоблока — зарегистрируйте его тег, тег-кеш в момент вызова открыт: Application::getInstance()->getTaggedCache()->registerTag('iblock_id_' . $id).

class ProjectIblockService extends \Uplab\Api\Model\Iblock\IblockService
{
    protected function appendSectionDetailData(array $data, array $rawFields, mixed $obj, mixed $iblockConfig): array
    {
        // $rawFields — исходные поля выборки (включая '~'-поля), $obj — объект строки выборки
        $data['MY_PARAM'] = $this->calcBusinessLogic((int)$data['ID'], $rawFields); // попадёт в кеш
        return $data;
    }
}

class ProjectIblockController extends \Uplab\Api\Controller\IblockController
{
    protected function getModelClass()
    {
        return new ProjectIblockService();
    }
}

Расширение через наследование (IblockProperty)

Значения умного фильтра (/api/iblock/{code}/filter) собираются по карте «тип свойства → метод-резолвер». Проект наследует IblockProperty, дописывает карту и реализует только свой метод — остальные типы продолжают работать без копирования кода.

Подключение наследника — через IblockService::getPropertyModel(), экшен переопределять не нужно. Фабрика без параметров (как getModelClass() в контроллерах): инфоблок, сайт и раздел модели задаёт сам сервис сеттерами, поэтому сигнатура не меняется при появлении новых данных.

class ProjectIblockService extends \Uplab\Api\Model\Iblock\IblockService
{
    /**
     * @return \Uplab\Api\Model\Iblock\IblockProperty
     */
    protected function getPropertyModel()
    {
        return new ProjectIblockProperty();
    }
}

class ProjectIblockController extends \Uplab\Api\Controller\IblockController
{
    protected function getModelClass()
    {
        return new ProjectIblockService();
    }
}
protected function getValueResolvers(): array
{
    return [
        'L' => 'getEnumValues',
        'S' => 'getStringValues',
        'E' => 'getElementLinkValues',
        'G' => 'getSectionLinkValues',
    ];
}

Список типов, которые вообще попадают в фильтр, выводится из этой же карты — отдельного перечня типов, способного с ней разойтись, больше нет. Модуль поддерживает только штатные типы Bitrix; числовые свойства (N) и типы сторонних модулей — задача проекта (готовый пример ниже).

Свой USER_TYPE. Пользовательский тип свойства в Bitrix живёт поверх базового (S, N, …), поэтому переопределяйте резолвер соответствующего базового типа и опирайтесь на parent:: для штатного поведения:

class ProjectIblockProperty extends \Uplab\Api\Model\Iblock\IblockProperty
{
    protected function getStringValues(array $arProperty, array $customFilter): array
    {
        if (($arProperty['USER_TYPE'] ?? '') === 'my_type') {
            // $customFilter — фильтр запроса без условия по самому свойству
            return $this->buildMyTypeItems($arProperty, $customFilter);
        }

        return parent::getStringValues($arProperty, $customFilter);
    }
}

Свой базовый тип (которого нет в карте) регистрируется добавлением ключа: return parent::getValueResolvers() + ['F' => 'getFileValues'];.

Сигнатура резолвера: (array $arProperty, array $customFilter): array. Возвращает список значений [['id' => …, 'value' => …, 'text' => …], …]; пустой массив означает «свойство в фильтр не попадает». Исключение внутри резолвера не роняет весь фильтр: оно пишется в Журнал событий (тип UPLAB_API_FILTER_ERROR), свойство отдаёт пустой набор.

Метод Назначение
getValueResolvers() Карта «PROPERTY_TYPE → метод». Задаёт и список поддерживаемых типов
getEnumValues() / getStringValues() / getElementLinkValues() / getSectionLinkValues() Резолверы штатных типов (L, S, E, G)
getDirectoryValues() / getPlainStringValues() Ветки строкового типа: справочник HL-блока и значения свойства как есть
buildPropertyFilter() Фильтр запроса без условия по самому свойству
buildElementsFilter() Базовый (защитный) фильтр выборки: ACTIVE, IBLOCK_ID, CHECK_PERMISSIONS, MIN_PERMISSION, раздел
getEnumPropertyItems() / getGroupPropertyItems() Значения свойства, реально использованные в доступных элементах
logPropertyError() Запись ошибки резолвера в Журнал событий и уведомление администратору

Настройка под конкретное свойство конкретного инфоблока

Сортировку, формат значений и фильтр можно менять точечно — без копирования тела резолвера. Все хуки получают $arProperty (там CODE, ID, USER_TYPE, IBLOCK_ID), а инфоблок доступен через $this->getIblockId().

Хук Что задаёт По умолчанию
getEnumOrder($arProperty) Сортировка значений перечисления (L) {"SORT":"ASC","VALUE":"ASC"}
getSectionsOrder($arProperty) Сортировка разделов (G) {"SORT":"ASC","NAME":"ASC"}
getElementsOrder($arProperty) Сортировка элементов (E) {"SORT":"ASC","NAME":"ASC"}
getDirectoryOrder($arProperty) Сортировка записей справочника [] (без ORDER BY)
getSectionsSelect($arProperty) Поля выборки разделов (G) ['ID','NAME','IBLOCK_ID','CODE']
getElementsSelect($arProperty) Поля выборки элементов (E), можно добавить PROPERTY_<КОД> ['ID','NAME']
getLinkedSectionsFilter($iblockId, $ids, $arProperty) Фильтр выборки связанных разделов (G) ID, ACTIVE=Y, IBLOCK_ID, CHECK_PERMISSIONS=Y, MIN_PERMISSION=PUBLIC_READ
getLinkedElementsFilter($iblockId, $ids, $arProperty) Фильтр выборки связанных элементов (E) то же
buildEnumItems($rows, $arProperty) Формат значений перечисления id/value = ENUM_ID, text = VALUE
buildSectionItems($rows, $arProperty) Формат значений-разделов (G и проектные типы с разделами) id/value = ID, text = NAME
buildElementItems($rows, $arProperty) Формат значений-элементов (E) id/value = ID, text = NAME
buildDirectoryItems($rows, $nameFields, $arProperty) Формат значений справочника value = UF_XML_ID, text = локализованное имя
buildPlainStringItems($arProperty, $rows) Формат значений строкового свойства value = text = значение свойства
class ProjectIblockProperty extends \Uplab\Api\Model\Iblock\IblockProperty
{
    // Сортировка — только для COLOR в инфоблоке 17
    protected function getEnumOrder(array $arProperty): array
    {
        if ($this->getIblockId() === 17 && $arProperty['CODE'] === 'COLOR') {
            return ['VALUE' => 'DESC'];
        }

        return parent::getEnumOrder($arProperty);
    }

    // Формат — добавляем своё поле, выборку не копируем
    protected function buildEnumItems(array $rows, array $arProperty): array
    {
        $items = parent::buildEnumItems($rows, $arProperty);
        if ($arProperty['CODE'] === 'COLOR') {
            foreach ($items as &$item) {
                $item['hex'] = $this->resolveHex($item['value']);
            }
        }

        return $items;
    }
}

Фильтр. buildElementsFilter() вызывается из общих методов подсчёта значений и мета-данных свойства параметром не получает, поэтому текущее свойство доступно через getCurrentProperty() — он заполнен, пока работает резолвер, и сбрасывается после (в том числе при исключении):

protected function buildElementsFilter(array $additionalFilter = []): array
{
    $filter = parent::buildElementsFilter($additionalFilter);
    if (($this->getCurrentProperty()['CODE'] ?? '') === 'COLOR') {
        $filter['!PROPERTY_HIDE_VALUE'] = false;
    }

    return $filter;
}

Защитные ключи (CHECK_PERMISSIONS, MIN_PERMISSION, ACTIVE, IBLOCK_ID) базовая реализация проставляет последними — переопределение их не отменяет, если вы вызываете parent:: и добавляете условия к результату.

Обращения к API Bitrix вынесены в отдельные обёртки (fetchPropertyValueRows(), fetchPropertyValueRowsWithRaw(), fetchSections(), fetchElements(), fetchEnumValues(), fetchHlblockFields(), fetchDirectoryItems()). Обёртки получают готовые фильтр, сортировку и поля выборки — сами ничего не решают; переопределять их в проекте обычно не нужно, они существуют, чтобы логику можно было покрыть юнит-тестами без ядра Bitrix (см. «Юнит-тесты»).

Если значения берутся из другого инфоблока — зарегистрируйте его тег кеша: $this->addIblockTags($iblockId). Для сущностей, у которых тег не сводится к iblock_id_{ID} (например записи HL-блока), есть $this->addCacheTag($tag) — так модуль регистрирует hlblock_id_{ID} для свойств-справочников.

Пример: свои типы свойств (модуль asd.iblock)

Типы SASDCheckboxNum и SASDSection принадлежат сторонему модулю asd.iblock, поэтому uplab.api их не обрабатывает — проект добавляет их сам. Пример полный, ничего кроме него писать не нужно:

namespace Project\Api;

use Uplab\Api\Model\Iblock\IblockProperty;

class ProjectIblockProperty extends IblockProperty
{
    protected function getValueResolvers(): array
    {
        // Пользовательский тип живёт поверх базового, поэтому регистрируем базовый тип «число»
        return parent::getValueResolvers() + ['N' => 'getAsdNumberValues'];
    }

    protected function getAsdNumberValues(array $arProperty, array $customFilter): array
    {
        return match ($arProperty['USER_TYPE'] ?? '') {
            'SASDCheckboxNum' => $this->buildAsdCheckboxItems($arProperty),
            'SASDSection'     => $this->getAsdSectionValues($arProperty, $customFilter),
            default           => [],
        };
    }

    /** Значения берутся из настроек свойства — запрос к БД не нужен. */
    protected function buildAsdCheckboxItems(array $arProperty): array
    {
        $items = [];
        foreach ((array)($arProperty['USER_TYPE_SETTINGS']['VIEW'] ?? []) as $key => $label) {
            $items[] = [
                'id'    => $arProperty['CODE'] . $key,
                'value' => $key,
                'text'  => $label,
            ];
        }

        return $items;
    }

    protected function getAsdSectionValues(array $arProperty, array $customFilter): array
    {
        // Штатный метод модуля: отбирает реально использованные значения свойства
        // с базовым (защитным) фильтром, проверкой прав и переданным filter
        $groupItems = $this->getGroupPropertyItems($arProperty['CODE'], $customFilter);
        if (!$groupItems) {
            return [];
        }

        // Штатный формат значений-разделов — id/value/text
        return $this->buildSectionItems($this->fetchAsdSections(array_keys($groupItems)), $arProperty);
    }

    protected function fetchAsdSections(array $ids): array
    {
        $res = \Bitrix\Iblock\SectionTable::getList([
            'filter' => [
                'GLOBAL_ACTIVE' => 'Y',
                'IBLOCK_ID'     => $this->getIblockId(),
                'ID'            => $ids,
            ],
        ]);

        $rows = [];
        while ($row = $res->fetch()) {
            $rows[] = $row;
        }

        return $rows;
    }
}

Подключение — через getPropertyModel() (см. выше). Штатные типы (L, S, E, G) продолжают работать: их код не копируется.

Что здесь переиспользуется из модуля и не требует своей реализации: getGroupPropertyItems() (отбор использованных значений с базовым фильтром, проверкой прав и учётом переданного filter), buildSectionItems() (формат значений), getIblockId(), а также перехват ошибок — исключение внутри резолвера попадёт в Журнал событий, остальные свойства фильтра не пострадают.

Этот же класс продублирован в юнит-тесте tests/Unit/Iblock/ProjectCustomTypeExampleTest.php — если пример разойдётся с кодом модуля, тесты упадут.

Пример: строковое свойство со своим USER_TYPE

Свойство типа S с пользовательским типом (в примере — проектный выбор цвета) сейчас попадает в штатную ветку простых строк и отдаёт «сырые» значения. Чтобы подменить только его, переопределите резолвер строкового типа и оставьте остальные строковые свойства базовой реализации:

protected function getStringValues(array $arProperty, array $customFilter): array
{
    if (($arProperty['USER_TYPE'] ?? '') !== 'ProjectColorPicker') {
        return parent::getStringValues($arProperty, $customFilter);
    }

    $labels = $this->loadColorLabels();   // ваш справочник: '#FF0000' => 'Красный'

    $items = [];
    // getGroupPropertyItems() вернёт «значение свойства => количество элементов»,
    // уже с проверкой прав, базовым фильтром и учётом переданного filter
    foreach (array_keys($this->getGroupPropertyItems($arProperty['CODE'], $customFilter)) as $value) {
        $items[] = [
            'id'    => $arProperty['ID'],
            'value' => $value,                    // значение остаётся ключом фильтрации
            'text'  => $labels[$value] ?? $value, // неизвестное значение отдаём как есть
        ];
    }

    return $items;
}

Ключевое: value должен оставаться тем, что реально хранится в свойстве, иначе фронт не сможет фильтровать по нему через filter: {"PROPERTY_COLOR_HEX": "#FF0000"}. Меняйте text, а не value.

Если для своего типа нужен справочник в HL-блоке, посмотрите на getDirectoryValues() — там та же схема: значения свойства → выборка записей справочника → buildDirectoryItems().

Пример: привязка к элементу (E) со своим названием и сортировкой

Для E в значения по умолчанию попадает NAME связанного элемента. Чтобы вывести другое поле (например короткое название из свойства) и отсортировать по нему, нужны три хука — выборка и отбор значений остаются штатными:

// 1. Добавляем нужные поля в выборку связанных элементов
protected function getElementsSelect(array $arProperty): array
{
    if ($arProperty['CODE'] === 'VENDOR') {
        return ['ID', 'NAME', 'PROPERTY_SHORT_TITLE'];
    }

    return parent::getElementsSelect($arProperty);
}

// 2. Своя сортировка
protected function getElementsOrder(array $arProperty): array
{
    if ($arProperty['CODE'] === 'VENDOR') {
        return ['PROPERTY_SHORT_TITLE' => 'ASC'];
    }

    return parent::getElementsOrder($arProperty);
}

// 3. Свой «text» с фолбэком на имя элемента
protected function buildElementItems(array $rows, array $arProperty): array
{
    if ($arProperty['CODE'] !== 'VENDOR') {
        return parent::buildElementItems($rows, $arProperty);
    }

    $items = [];
    foreach ($rows as $row) {
        $items[] = [
            'id'    => $row['ID'],
            'value' => $row['ID'],
            'text'  => (string)($row['PROPERTY_SHORT_TITLE_VALUE'] ?? '') !== ''
                ? $row['PROPERTY_SHORT_TITLE_VALUE']
                : $row['NAME'],
        ];
    }

    return $items;
}

Условие можно сузить до конкретного инфоблока — $this->getIblockId() === 17 && $arProperty['CODE'] === 'VENDOR'.

Что делает модуль сам: отбирает ID связанных элементов, реально использованных в доступных элементах текущего инфоблока (с базовым фильтром, правами и переданным filter), берёт LINK_IBLOCK_ID из настроек свойства, выбирает только активные элементы связанного инфоблока и регистрирует его тег кеша (iblock_id_{LINK_IBLOCK_ID}) — сброс кеша при правке справочника-инфоблока работает без вашего участия.

Разделы (G) настраиваются симметрично: getSectionsSelect(), getSectionsOrder(), buildSectionItems().

Оба примера тоже покрыты тестами в tests/Unit/Iblock/ProjectCustomTypeExampleTest.php.

Highload-блоки — /api/hlblock

Доступ к данным HL-блоков

HL-блок отдаётся публично, пока на нём никому не выдано право чтения: вкладка «Права доступа» в форме HL-блока по умолчанию пуста, и это означает «ограничение чтения не настроено», а не «доступ запрещён».

Чтобы закрыть блок — выдайте на нём штатную операцию hl_element_read нужным группам (Настройки → Highload-блоки → блок → Права доступа). С этого момента все три endpoint-а (/elements, /filter, /fields) читают только обладатели права и администратор, остальные получают 403 Forbidden. Права на запись публичность не отбирают — ограничение включает именно выдача права чтения.

Как снова открыть блок, если право чтения уже выдано. Два способа:

  • выдать чтение группе «Все пользователи» (код доступа G2) — тогда блок доступен и анонимным запросам, а остальные выданные права продолжают работать. Это то, что нужно, если права на блоке заведены осознанно;
  • либо снять с блока все записи с правом чтения — блок вернётся в состояние «ограничение не настроено» и снова станет публичным.

Порядок действий: Настройки → Highload-блоки → нужный блок → вкладка «Права доступа» → добавить строку, выбрать «Все пользователи» и уровень доступа, включающий операцию чтения элементов (hl_element_read; операция входит в штатную задачу модуля highloadblock с буквой R), сохранить. Права на изменение и удаление (hl_element_write, hl_element_delete, задача с буквой W) на публичность не влияют — они операции чтения не содержат, поэтому блок с одними лишь правами на запись остаётся открытым.

Проверка учитывает штатные коды доступа: группы (G<ID>, у анонимного пользователя это G2 — «Все пользователи, в том числе неавторизованные»), AU и U<ID>. Для авторизованных дополнительно работает штатный HighloadBlockRightsTable::getOperationsName() — он раскрывает коды отделов и соцгрупп. Кеш ответов разделяется по группам текущего пользователя.

ANY /api/hlblock/{hlblockCode}/elements

Список элементов highload-блока.

Контроллер: HlblockController::getElementListAction Модель: HlblockService Конфиг: HlblockListConfig

Параметр Тип По умолчанию Описание
siteId string SITE_ID ID сайта
filter array [] Фильтр (только переданный; собственного базового фильтра нет)
select string[] ['*'] Список полей
order array {"ID":"ASC"} Сортировка. Если не передать — применяется значение по умолчанию
limit int 10 Кол-во элементов на странице, максимум 500
offset int 0 Смещение, максимум 100000
SHOW_TOTAL 'Y'|'N' 'Y' Включить total в ответ (в meta.total)

Ответ содержит items и meta (total при SHOW_TOTAL=Y, а также limit и offset); тело сырое, без обёртки.

ANY /api/hlblock/{hlblockCode}/filter

Данные для фильтра элементов highload-блока.

Контроллер: HlblockController::getElementFilterAction Конфиг: HlblockListConfig — параметры идентичны /elements.

ANY /api/hlblock/{hlblockCode}/fields

Список пользовательских полей highload-блока.

Контроллер: HlblockController::getFieldsAction Конфиг: BaseConfig

Параметр Тип Описание
siteId string ID сайта

Статические страницы — /api/pages

ANY /api/pages/{code}

Детальная страница из инфоблока Pages (статические страницы сайта).

Контроллер: PageController::getPageAction Модель: PageService Конфиг: IblockDetailElementConfig — параметры идентичны /api/iblock/{iblockCode}/element/code/{elementCode}.

Поле CONTENT в ответе

К ответу дополнительно подмешивается ключ CONTENT — статический контент этой страницы из HLBlock Content (PageService::getItemData(), lib/Model/Iblock/PageService.php:177). Это те же записи, что отдаёт /api/content/page/{pageCode}: оба эндпоинта вызывают ContentTable::getPageData(), поэтому отдельный запрос за контентом страницы фронтенду не нужен.

  • Отбираются записи с UF_PAGE = CODE найденного элемента, UF_ACTIVE = true и UF_LANG = siteId (условие по языку добавляется, только если siteId передан).
  • Структура — CONTENT[UF_BLOCK][UF_CODE] = UF_VALUE, значение #YEAR# заменяется на текущий год.
  • Если записей контента для страницы нет, CONTENT — пустой объект.
  • Кеш у контента свой: 24 часа, каталог /content/{pageCode}[/{siteId}], тег update_contenttable_{UF_PAGE} — правка контента в админке сбрасывает его независимо от кеша элемента инфоблока.
{
  "NAME": "Вакансии",
  "CODE": "vacancies",
  "PROPERTIES": {
    "SEO_H1": { "VALUE": "Вакансии" }
  },
  "CONTENT": {
    "filter": { "reset": "Сбросить фильтр" },
    "list": {
      "empty.title": "Ничего не найдено",
      "empty.text": "Попробуйте изменить параметры поиска"
    }
  }
}

Веб-формы — /api/forms

GET /api/forms/{code}/fields

Список полей веб-формы.

Контроллер: WebFormController::getWebFormFieldsAction Конфиг: BaseConfig

Параметр Тип Описание
siteId string ID сайта

POST /api/forms/{code}/add

Отправка результата веб-формы.

Контроллер: WebFormController::addNewResultAction Конфиг: BaseConfig + $_REQUEST

Поля формы передаются в теле запроса и соответствуют полям Bitrix-формы. Перед сохранением вызывается событие uplab.api::OnBeforeCheckForm.

ANY /api/forms/{code}

Структура веб-формы: все вопросы и варианты ответов.

Контроллер: WebFormController::getWebFormAction Конфиг: BaseConfig

Параметр Тип Описание
siteId string ID сайта

Статический контент — /api/content

ANY /api/content/page/{pageCode}

Статический контент страницы из HLBlock Content (таблица b_hlbd_content).

Возвращает объект вида { "block": { "code": "value" } }. Значение #YEAR# автоматически заменяется на текущий год.

Для страниц инфоблока Pages отдельный запрос не нужен: те же данные приходят в ключе CONTENT ответа /api/pages/{code}.

Контроллер: ContentPageController::getAction Конфиг: BaseConfig

Параметр Тип Описание
siteId string ID сайта. Влияет на ключ кеша.

POST /api/content/admin/{pageCode}

Список записей контента для административного интерфейса. Требует авторизации и прав hl_element_read на HLBlock.

Контроллер: ContentController::getListAction

Query-параметр Тип По умолчанию Описание
page int 1 Номер страницы
pageSize int 20 Размер страницы

DELETE /api/content/admin/{elementId}

Удалить запись контента. Требует авторизации, прав hl_element_delete на HLBlock и валидного CSRF-токена (sessid).

Контроллер: ContentController::deleteElementAction

CSRF. Мутирующее действие: требуется параметр sessid (например ?sessid=<bitrix_sessid>). Без валидного токена — Доступ запрещён.

POST /api/content/bulk

Групповые действия над записями контента. Требует авторизации, соответствующих прав на HLBlock и валидного CSRF-токена (sessid).

Контроллер: ContentController::bulkAction

Параметр Тип Описание
action 'activate'|'deactivate'|'delete' Действие
ids[] int[] Массив ID элементов
sessid string CSRF-токен Bitrix (bitrix_sessid). Обязателен.
  • activate / deactivate — требуют прав hl_element_write
  • delete — требует прав hl_element_delete

CSRF. Мутирующее действие: без валидного sessid запрос отклоняется (Доступ запрещён).

Меню — /api/menus

ANY /api/menus/{menuCode}

Иерархическое меню из инфоблока в формате для фронтенда.

Контроллер: MenuController::getAction Модель: MenuService Конфиг: MenuConfig

Параметр Тип Описание
siteId string ID сайта
iblockType string Тип инфоблока Bitrix
filter array Дополнительный фильтр
select string[] Список полей
order array Сортировка

Поиск — /api/search

ANY /api/search

Полнотекстовый поиск через Bitrix CSearch. Поддерживает морфологию и повторный поиск без стемминга при пустом результате.

Контроллер: SearchController::getAction Модель: Search

Параметры передаются напрямую как JSON body (без Config-объекта):

Параметр Тип По умолчанию Описание
search string '' Поисковый запрос
siteId string ID сайта для фильтрации результатов
sort 'rank'|'date' 'rank' Тип сортировки
type 'full'|'short' 'full' full — страница поиска, short — шапка сайта (приоритет TITLE_RANK)
limit int 10 Кол-во результатов, 1…500
offset int 0 Смещение, 0…100000

Текст при пустом результате (message)

Если ничего не найдено, ответ содержит message.title и message.text. Источник текста (по приоритету):

  1. Статический контент — HLBlock Content: запись с UF_PAGE = 'search', UF_BLOCK = 'empty', UF_CODE = 'title' и UF_CODE = 'text' (редактируется в админке, раздел контента; см. /api/content/page/search).
  2. Языковые сообщения модуля (фолбэк, если контент пуст) — UPLAB_API.SEARCH_EMPTY_TITLE и UPLAB_API.SEARCH_EMPTY_TEXT в lang/ru/include.php / lang/en/include.php.

Чтобы задать свой текст: либо добавьте записи в контент (search/empty/title|text), либо переопределите указанные lang-сообщения.

Карта сайта — /api/sitemap

ANY /api/sitemap

Данные для генерации sitemap. Кешируется на 24 часа в разрезе сайта.

Контроллер: SitemapController::getAction Модель: Sitemap (реализует SitemapInterface)

Параметр Тип Описание
siteId string ID сайта. Влияет на ключ кеша и фильтрацию (IBLOCK_LID).

Расширение через наследование

Модель разбита на точечно переопределяемые хуки. Достаточно создать наследника Sitemap и зарегистрировать его через SitemapController::getModelClass().

class ProjectSitemapController extends SitemapController
{
    protected function getModelClass()
    {
        return new ProjectSitemap();
    }
}

Свойства наследника:

Свойство Тип Описание
$domain string Домен перед каждым URL (по умолчанию пустая строка — относительные URL).
$cacheTime int Время жизни кеша в секундах (по умолчанию 86400).
$pagesIblockElementsCodes string[] Коды инфоблоков, детальные страницы элементов которых попадают в sitemap.
$pagesIblockSectionsCodes string[] Коды инфоблоков, страницы разделов которых попадают в sitemap.

Хуки для точечного переопределения:

Метод Описание
getStaticPagesIblockCode() Код инфоблока статических страниц (по умолчанию 'pages').
getStaticPagesOrder() Сортировка статических страниц.
getStaticPagesFilter(int $iblockId) Фильтр статических страниц.
getStaticPagesSelect() Поля выборки статических страниц.
buildStaticPageItem(array $el) Трансформация элемента → {loc, lastmod}. null = пропустить.
getElementPagesOrder() Сортировка элементов инфоблоков.
getElementPagesFilter() Фильтр элементов инфоблоков.
buildElementPageItem(array $el) Трансформация элемента → {loc, lastmod}. null = пропустить.
getSectionPagesOrder() Сортировка разделов инфоблоков.
getSectionPagesFilter() Фильтр разделов инфоблоков.
buildSectionPageItem(array $sect) Трансформация раздела → {loc, lastmod}. null = пропустить.
getFormatDate(int $date) Формат даты (по умолчанию ISO 8601).
getPages() Полный сбор страниц из всех источников.

Система событий

Все события регистрируются на модуль uplab.api.

Событие Параметры (ExecuteModuleEventEx) Описание
OnBeforeCheckForm BeforeCheckForm $event Перед сохранением результата веб-формы (POST /api/forms/{code}/add). Возврат EventResult::ERROR отклоняет форму.
OnBeforeHlblockConvertFieldValue array $fields, array &$dataItem Для каждого элемента HL-блока перед конвертацией значений полей (GET /api/hlblock/{code}/elements).
OnAfterHlblockConvertFieldValue array $fields, array &$dataItem Для каждого элемента HL-блока после конвертации значений полей.
OnIblockConvertSectionDisplayValue array $sectionUfField, array &$arDisplayValue Для каждого UF-поля раздела инфоблока после сборки его отображаемого значения (эндпоинты разделов ИБ).
OnIblockConvertPropertyDisplayValue array $data, array &$arDisplayValue Для каждого свойства элемента инфоблока после сборки его отображаемого значения (эндпоинты элементов ИБ).
$eventManager->addEventHandler('uplab.api', 'OnBeforeCheckForm', [static::class, 'OnBeforeCheckForm']);

public static function OnBeforeCheckForm(\Uplab\Api\EventResult\BeforeCheckForm $event): \Bitrix\Main\EventResult
{
    // $event->request — данные из $_REQUEST
    if (empty($event->request['token'])) {
        return new \Bitrix\Main\EventResult(
            \Bitrix\Main\EventResult::ERROR,
            parameters: ['message' => 'Token required'],
            moduleId: 'uplab.api'
        );
    }
    return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS);
}

Конвертация значений полей HL-блока

При выдаче элементов HL-блока (GET /api/hlblock/{code}/elements) сервис преобразует значения пользовательских полей для фронтенда: поля-привязки (iblock_element, hlblock) и поля типа file дополняются читаемыми данными в подмассиве INFO (исходное значение поля сохраняется). Для file в INFO кладётся полный массив файла из CFile::GetFileArray() (SRC, FILE_NAME, FILE_SIZE, CONTENT_TYPE, размеры и т.д.): один массив для одиночного поля, массив таких массивов — для множественного.

События OnBeforeHlblockConvertFieldValue / OnAfterHlblockConvertFieldValue позволяют вмешаться в этот процесс. Оба вызываются для каждого элемента выборки и получают одинаковый набор параметров:

  • $fields — массив мета-данных всех пользовательских полей HL-блока;
  • &$dataItem — данные одного элемента, передаются по ссылке — обработчик модифицирует их напрямую.

Результат конвертации кешируется вместе с ответом (по умолчанию 24 часа, тег HL-блока). Изменения, внесённые обработчиком, тоже попадают в кеш — не полагайтесь в них на per-request данные.

$eventManager->addEventHandler('uplab.api', 'OnAfterHlblockConvertFieldValue', [static::class, 'OnAfterHlblockConvertFieldValue']);

public static function OnAfterHlblockConvertFieldValue(array $fields, array &$dataItem): void
{
    // Например, добавить вычисляемое поле к каждому элементу
    $dataItem['INFO']['UF_FULL_URL'] = '/catalog/' . ($dataItem['UF_CODE'] ?? '');
}

Конвертация отображаемых значений инфоблока

При выдаче элементов и разделов инфоблока сервис собирает «человекочитаемое» представление каждого свойства/поля в DISPLAY_VALUE. Два события позволяют вмешаться в этот процесс — оба вызываются после штатной конвертации, поэлементно (на каждое свойство/поле), и принимают отображаемое значение по ссылке:

  • OnIblockConvertPropertyDisplayValue — для свойств элемента ИБ. Параметры: array $data (мета-данные свойства: PROPERTY_TYPE, USER_TYPE, MULTIPLE, VALUE и т.д.) и array &$arDisplayValue (собранное отображаемое значение).
  • OnIblockConvertSectionDisplayValue — для UF-полей раздела ИБ. Параметры: array $sectionUfField (мета-данные и значение поля) и array &$arDisplayValue.
$eventManager->addEventHandler('uplab.api', 'OnIblockConvertPropertyDisplayValue', [static::class, 'OnIblockConvertPropertyDisplayValue']);

public static function OnIblockConvertPropertyDisplayValue(array $data, array &$arDisplayValue): void
{
    // Например, для своего пользовательского типа свойства сформировать своё отображение
    if (($data['USER_TYPE'] ?? '') === 'my_custom_type') {
        $arDisplayValue = array_map('mb_strtoupper', $arDisplayValue);
    }
}