mfd / typo3-bunny-cdn
Bunny CDN cache integration for TYPO3
Package info
github.com/marketing-factory/bunny-cdn
Type:typo3-cms-extension
pkg:composer/mfd/typo3-bunny-cdn
Requires
- php: ^8.2
- typo3/cms-core: ^14.0
- typo3/cms-frontend: ^14.0
Requires (Dev)
- guzzlehttp/psr7: ^2.10
- phpunit/phpunit: ^11.0
- ssch/typo3-rector: ^3.15
- typo3/coding-standards: ^0.9.0
- typo3/testing-framework: ^9.3
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-09 15:18:05 UTC
README
Active Bunny CDN cache invalidation for TYPO3 13. Purges
Bunny's edge cache whenever an editor changes content, by page URL and by
cache tag, and tags outgoing responses with Bunny's CDN-Tag header so those
tag-based purges actually match something. Also exposes whether Bunny is
active for the current site/request to TypoScript, Fluid, and PHP.
How it works
Purges Bunny's edge cache by reusing the cache tags TYPO3 already computes
for every page render, both to tag outgoing responses (CDN-Tag header) and
to trigger purges when TYPO3 flushes its own page cache. See
Documentation/ for architecture, editor/admin
usage, and design decisions.
Installation
composer require mfd/typo3-bunny-cdn
Configuration
Enable/disable (global switch)
Extension Configuration enabled (Admin Tools > Settings > Extension
Configuration > bunny_cdn), on by default. Turning it off skips all purge
requests — CdnTagHeader still sets the CDN-Tag response header regardless,
since that's harmless without anything ever purging by it.
In this project, config/system/additional.php forces it off outside the
Production application context, so purge requests are never sent from
Development/Testing:
if ((string)Environment::getContext() !== 'Production') { $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['bunny_cdn']['enabled'] = false; }
API Access Key (shared across all sites)
Set via the regular TYPO3 Extension Configuration: Admin Tools > Settings >
Extension Configuration > bunny_cdn, or directly in LocalConfiguration.php /
AdditionalConfiguration.php:
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['bunny_cdn']['apiKey'] = 'your-access-key';
To source it from an environment variable instead (e.g. so it never touches
LocalConfiguration.php), set it in config/system/additional.php — it's
read after the backend-configured value and overrides it:
if (isset($_ENV['BUNNY_API_KEY'])) { $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['bunny_cdn']['apiKey'] = $_ENV['BUNNY_API_KEY']; }
If empty, the extension no-ops — no purge requests are sent.
Pull Zone ID (per site)
Each TYPO3 site needs its Bunny Pull Zone ID configured, since different
sites are typically served by different Pull Zones. Set it on the "Bunny
CDN" tab of the site configuration (bunny_pull_zone_id), or directly in
config/sites/<site>/config.yaml:
bunny_pull_zone_id: 12345
A site without a Pull Zone ID configured is skipped for purging.
Async retry on rate limiting (opt-in)
Extension Configuration asyncRetryEnabled, off by default. Bunny's
purge API rate-limits (429); by default a rate-limited purge is just
logged and dropped, same as any other failure.
Turning this on instead schedules it as a
Symfony Messenger
message, delayed 5 seconds (DelayStamp), and retries it off the request
path. It's opt-in because that retry only ever happens if something is
actually consuming the queue — a messenger:consume doctrine worker process
(cron, systemd timer, supervisor, ...). If your hosting doesn't run one,
leave this off: the message would just sit in the sys_messenger_messages
table forever.
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['bunny_cdn']['asyncRetryEnabled'] = true;
vendor/bin/typo3 messenger:consume doctrine
A retry that's rate-limited again schedules another delayed retry the same
way, 5 seconds out each time — there's no retry cap, so a Bunny outage that
keeps answering 429 keeps this going indefinitely (at one request every 5s)
until it succeeds or asyncRetryEnabled is turned off.
Checking activation state
Mfd\BunnyCdn\Service\BunnyCdnService::isActiveForSite() and the
bunnyCdn Context aspect it feeds both answer two different questions:
active— config: is Bunny CDN enabled and fully configured (API key + Pull Zone ID) for the current site.viaBunny— observed: did this specific request actually arrive through Bunny's edge (detected via theCDN-ServerIdheader Bunny adds to every proxied request). Useful e.g. behind a staging domain that bypasses the CDN even though it'sactive.
Both, plus a handful of other CDN-* request headers Bunny sends
(CDN-RequestId, CDN-RequestCountryCode, CDN-RequestStateCode,
CDN-MobileDevice, CDN-ConnectionId, CDN-Host, CDN-ServerZone), are
set on the bunnyCdn Context aspect
by Mfd\BunnyCdn\Middleware\BunnyCdnAspectMiddleware, so they're reachable
from anywhere:
PHP:
$context->getPropertyFromAspect('bunnyCdn', 'active'); $context->getPropertyFromAspect('bunnyCdn', 'requestCountryCode');
TypoScript, via the context data type:
lib.bunnyCdnActive = TEXT
lib.bunnyCdnActive.data = context:bunnyCdn:active
Fluid, via the two dedicated ViewHelpers (only active/viaBunny get
one — pull anything else through the TypoScript bridge above with
<f:cObject typoscriptObjectPath="lib.bunnyCdnActive" />):
<html xmlns:bunny="http://typo3.org/ns/Mfd/BunnyCdn/ViewHelpers"> <f:if condition="{bunny:isActive()}">CDN configured for this site.</f:if> <f:if condition="{bunny:isViaBunny()}">This request came through Bunny.</f:if>
Testing
Unit tests use typo3/testing-framework.
composer test:unit
or directly:
phpunit -c Build/UnitTests.xml