Search by

mfd / typo3-bunny-cdn

cspoo

Bunny CDN cache integration for TYPO3

Package info

github.com/marketing-factory/bunny-cdn

Type:typo3-cms-extension

pkg:composer/mfd/typo3-bunny-cdn

Statistics

Installs: 1 133

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-master 2026-09-09 15:11 UTC

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 the CDN-ServerId header Bunny adds to every proxied request). Useful e.g. behind a staging domain that bypasses the CDN even though it's active.

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