Configuring Caching for Custom 1C-Bitrix Components
A component without caching is a database query on every page view. With 1,000 page views per day that means 1,000 queries; at 10,000 it becomes a real load problem. Caching in Bitrix is not a magic "enable" button — it is a set of concrete decisions: what to cache, for how long, under what key, and how to invalidate when data changes.
Bitrix Caching Mechanism
Bitrix uses file-based caching by default. Cache is stored in /bitrix/cache/ (or /upload/cache/ depending on configuration). Each cache file is a serialized $arResult of a component. On a cache hit, template.php is called with the cached data — no database queries at all.
Two levels of caching:
-
Result cache (
StartResultCache/EndResultCache) —$arResultis cached - HTML cache (composite cache, a separate mechanism) — the final HTML is cached
For custom components, the first level is used.
Basic Usage of StartResultCache
// component.php
$cacheId = serialize([
$arParams['IBLOCK_ID'],
$arParams['COUNT'],
$arParams['SECTION_ID'],
LANGUAGE_ID,
SITE_ID,
]);
$cacheDir = '/custom/my.component/' . $arParams['IBLOCK_ID'] . '/';
if ($this->StartResultCache($arParams['CACHE_TIME'], $cacheId, $cacheDir)) {
// This block executes only when there is no cache
$this->arResult = $this->getData();
$this->IncludeComponentTemplate();
$this->EndResultCache();
}
Parameters of StartResultCache($cacheTime, $cacheId, $cacheDir):
-
$cacheTime— TTL in seconds.falseor0disables caching.-1means infinite cache (until manually invalidated). -
$cacheId— unique identifier for this particular set of parameters. Must differ for different component parameters — otherwise all variants will show the same data. -
$cacheDir— folder inside/bitrix/cache/. Needed for bulk invalidation of a single component's cache.
Forming cacheId Correctly
A common first-time mistake: $cacheId does not account for all influencing parameters. As a result, the page showing the "Electronics" section displays the cache from the "Clothing" section.
What must always be included in cacheId:
$cacheId = serialize([
// Component parameters that affect the result
$arParams['IBLOCK_ID'],
$arParams['SECTION_ID'],
$arParams['COUNT'],
$arParams['ELEMENT_SORT_FIELD'],
// Context
LANGUAGE_ID, // multilingual support
SITE_ID, // multi-site support
// If content depends on user group
// serialize(CSaleUser::GetUserGroups()), -- only when needed
]);
Do not include in cacheId anything that does not affect the data — otherwise the cache will never be reused. For example, the block's CSS class ($arParams['CSS_CLASS']) is a display parameter, not a data parameter. It is applied in template.php directly from $arParams, not through $arResult.
Cache with User Group Awareness
If a component shows different content to authenticated users and guests (for example, B2B prices differ from retail prices), the cache must be separate:
// Option 1: disable cache for authenticated users
global $USER;
$cacheTime = $USER->IsAuthorized() ? false : $arParams['CACHE_TIME'];
// Option 2: enable per-group separation (heavier)
$arParams['CACHE_GROUPS'] = 'Y'; // this parameter enables per-group separation in Bitrix
The CACHE_GROUPS => 'Y' parameter in standard Bitrix components automatically adds user groups to cacheId. In a custom component this must be done manually:
if ($arParams['CACHE_GROUPS'] === 'Y') {
$userGroups = CSaleUser::GetUserGroups();
sort($userGroups);
$cacheId = serialize([$baseParams, $userGroups]);
}
Cache Invalidation on Data Update
TTL cache (time-based cache) is the simplest solution but imprecise: an element updated at 10:00 will only become visible after 1 hour (if CACHE_TIME = 3600). For time-sensitive content, event-based invalidation is required.
Infoblock event handler:
// In /local/php_interface/init.php
AddEventHandler('iblock', 'OnAfterIBlockElementUpdate', 'clearMyComponentCache');
AddEventHandler('iblock', 'OnAfterIBlockElementAdd', 'clearMyComponentCache');
AddEventHandler('iblock', 'OnAfterIBlockElementDelete', 'clearMyComponentCache');
function clearMyComponentCache($arFields) {
// Only clear cache for the relevant infoblock
if (!in_array($arFields['IBLOCK_ID'], [IBLOCK_CATALOG_ID, IBLOCK_NEWS_ID])) {
return;
}
// Clear the entire cache folder for the component
BXClearCache(true, '/custom/my.component/' . $arFields['IBLOCK_ID'] . '/');
}
BXClearCache(true, $path) deletes all files in the specified cache folder. Fast and without ORM.
Tagged Cache: Precise Invalidation
If a component displays data from multiple infoblocks, clearing the entire cache whenever any of them changes is wasteful. Tagged cache handles this more precisely:
use Bitrix\Main\Data\TaggedCache;
$taggedCache = new TaggedCache();
$taggedCache->startTagCache('/custom/my.component/');
if ($this->StartResultCache($cacheTime, $cacheId, $cacheDir)) {
$taggedCache->registerTag('iblock_id_' . $arParams['IBLOCK_ID']);
$taggedCache->registerTag('iblock_element_' . $elementId); // when caching a specific element
$this->arResult = $this->getData();
$this->IncludeComponentTemplate();
$taggedCache->endTagCache();
$this->EndResultCache();
} else {
$taggedCache->abortTagCache();
}
Invalidation by tag when a specific element is updated:
// In the event handler
$taggedCache = new TaggedCache();
$taggedCache->clearByTag('iblock_element_' . $arFields['ID']);
$taggedCache->clearByTag('iblock_id_' . $arFields['IBLOCK_ID']);
Tagged cache is more precise but requires more code. It is justified for high-load projects with frequent data updates.
Debugging the Cache
The cache can be disabled for a specific component during development:
// In component.php while debugging
$cacheTime = defined('DEVELOPER_MODE') && DEVELOPER_MODE ? false : $arParams['CACHE_TIME'];
DEVELOPER_MODE is defined in /bitrix/.settings.php or in project constants. When set to false, cache is bypassed and data is always fetched from the database.
Inspecting the cache: files in /bitrix/cache/ are accessible directly — they are PHP files with serialize() data. The file creation timestamp is the time of the last cache warm-up.
Cache and AJAX Requests
For components with AJAX-based updates (loading products during pagination), cache is applied to each request individually. The cacheId for an AJAX request includes the request parameters:
$page = max(1, (int)$_REQUEST['page']);
$cacheId = serialize([$baseParams, $page]);
Each pagination page is cached separately — this is the correct approach.
Setup Timelines
| Task | Timeline |
|---|---|
| Basic caching for a component | 2–4 hours |
| + Event-based invalidation | 4–8 hours |
| + Tagged cache | 1–2 days |
| Audit of existing components + fixes | 1–3 days |
Caching is one of the few optimizations that delivers an immediate and measurable result. A properly configured cache reduces page generation time from 500 ms to 20–50 ms without changing any application logic.

