Developing Custom Pages on D7 for 1C-Bitrix
A custom page in the Bitrix admin area is not simply an HTML file dropped into /bitrix/admin/. It is a controller that checks permissions, accepts parameters, executes business logic, and renders the result through a template. D7 provides a \Bitrix\Main\Engine\Controller mechanism with routing, CSRF checking, and session management — no need to build it from scratch.
Two Approaches to Custom Pages
Classic admin file (/bitrix/admin/my_page.php) — the simplest option, suitable for auxiliary tools:
<?php
require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_before.php");
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
Loader::includeModule('my.module');
// Permission check
if (!$APPLICATION->IsAdminPage() || !\CMain::GetUserRight("main") >= "S") {
$APPLICATION->AuthForm("Access denied");
}
$APPLICATION->SetTitle("My Tool");
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_admin_after.php");
// Page content output
?>
<form method="post" action="<?= $APPLICATION->GetCurPage() ?>">
<?= bitrix_sessid_post() ?>
<!-- form fields -->
<input type="submit" value="Execute">
</form>
<?php
require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/epilog_admin.php");
MVC via Controller — the correct approach for pages with logic:
namespace MyProject\Controllers;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
class ReportController extends Controller
{
public function configureActions(): array
{
return [
'getReport' => [
'prefilters' => [
new ActionFilter\Authentication(),
new ActionFilter\HttpMethod(['GET', 'POST']),
new ActionFilter\Csrf(),
],
],
];
}
public function getReportAction(int $periodDays = 30): array
{
// permissions are checked in prefilters
$data = \MyProject\Services\ReportService::build($periodDays);
return ['data' => $data, 'total' => count($data)];
}
}
Routing in Bitrix
Controllers require route registration. In /local/php_interface/init.php or in a module method:
\Bitrix\Main\Routing\Router::getInstance()->add(
'/local/my-tool/report/',
['controller' => '\MyProject\Controllers\ReportController', 'action' => 'getReport'],
'GET'
);
Alternative — UrlManager in the module config or the /local/routes.php file (Bitrix 22+, must be enabled in core settings).
Custom Public Page
For public pages, the standard component mechanism applies. But sometimes a page is needed outside the component system: a utility, a payment gateway callback, a webhook. In that case, a page file is created with the core bootstrapped:
<?php
define('STOP_STATISTICS', true); // do not write statistics
define('NO_AGENT_STATISTIC', true); // do not run agents
define('NOT_CHECK_PERMISSIONS', true); // disable Bitrix permission check
define('NO_LANG_FILES', false);
require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
// all Bitrix classes are now available
// All logic via D7
$request = \Bitrix\Main\Application::getInstance()->getContext()->getRequest();
if (!$request->isPost()) {
http_response_code(405);
exit;
}
// ... processing
D7 Pagination
For pages with data lists:
use Bitrix\Main\UI\PageNavigation;
$nav = new PageNavigation('page');
$nav->allowAllRecords(false)
->setPageSize(20)
->initFromUri();
$result = \MyProject\Storage\OrderLogTable::getList([
'select' => ['*'],
'order' => ['ID' => 'DESC'],
'count_total' => true,
'offset' => $nav->getOffset(),
'limit' => $nav->getLimit(),
]);
$nav->setRecordCount($result->getCount());
// Render pagination via $nav->getPageNavString(...)
Access Control on Custom Pages
Permission check based on user groups:
global $USER;
if (!$USER->IsAdmin() && !$USER->IsInGroup([MANAGER_GROUP_ID])) {
LocalRedirect('/access-denied/');
}
For module pages — via the Bitrix access rights API:
if (\CModule::IncludeModule('my.module')) {
$rights = \CMyModule::GetCurrentUserPermissions();
if ($rights < MY_MODULE_PERMISSION_WRITE) {
ShowError('Insufficient permissions');
exit;
}
}
Integration with Bitrix UI: Grids and Filters
For admin pages with lists — ready-made UI: CAdminList, CAdminFilter. This ensures visual consistency with the core:
$adminList = new CAdminList('my_list', $by, $order);
$adminList->AddColumn(['id' => 'ID', 'content' => 'ID', 'sort' => 'ID']);
$adminList->AddColumn(['id' => 'NAME', 'content' => 'Name', 'sort' => 'NAME']);
while ($row = $result->fetch()) {
$adminRow = $adminList->AddRow($row['ID'], $row);
$adminRow->AddActions([
['text' => 'Edit', 'href' => '/bitrix/admin/my_edit.php?ID=' . $row['ID']],
['text' => 'Delete', 'action' => "if(confirm('Delete?')) window.location='...';"],
]);
}
$adminList->DisplayList();
Timeline
| Task | Timeline |
|---|---|
| Simple admin page (form + data display) | 2–3 days |
| Page with list, pagination, filter, and actions | 4–7 days |
| Full CRUD interface for a custom entity with permissions and logs | 1.5–2.5 weeks |
A custom D7 page is not a replacement for the component system. It is a tool for tasks that do not fit the standard model: admin tools, import interfaces, dashboards with aggregated data. When written correctly, they do not conflict with the core during updates.







