netresearch / nr-temporal-cache
Automatic TYPO3 cache invalidation for time-based content (starttime/endtime), addressing Forge #14277. Three scoping strategies (global, per-page, per-content) and three timing strategies (dynamic, scheduler, hybrid). The default global scoping expires all page caches on every transition - read the
Package info
github.com/netresearch/t3x-nr-temporal-cache
Type:typo3-cms-extension
pkg:composer/netresearch/nr-temporal-cache
Requires
- php: ^8.1
- typo3/cms-core: ^12.4 || ^13.0 || ^14.0
- typo3/cms-reports: ^12.4 || ^13.0 || ^14.0
- typo3/cms-scheduler: ^12.4 || ^13.0 || ^14.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.40
- netresearch/typo3-ci-workflows: ^1.3
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^1.10 || ^2.0
- phpunit/phpcov: ^9.0 || ^11.0 || ^12.0 || ^13.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0 || ^13.0
- saschaegerer/phpstan-typo3: ^1.0 || ^2.0 || ^3.0
- typo3/coding-standards: ^0.8 || ^0.9
- typo3/testing-framework: ^8.0 || ^9.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-05 06:12:02 UTC
README
Automatic cache invalidation for time-based content, developed by Netresearch DTT GmbH.
Addresses TYPO3 Forge Issue #14277: "Start/Stop time for pages is ignored in standard menu objects", reported in 2004 and still open.
Status:
ext_emconf.phpdeclares version 1.0.0, statestable. The API listed in Documentation/Api follows Semantic Versioning from this release onwards.
The Problem (20+ Years Old)
TYPO3's cache system is event-driven (invalidates when data changes) but doesn't handle temporal dependencies (when time passes):
- ❌ Pages with
starttimedon't appear in menus when scheduled time arrives - ❌ Pages with
endtimeremain visible in menus after expiration - ❌ Content elements with
starttime/endtimedon't update automatically - ❌ Sitemaps, search results, and listings show stale temporal content
- ⚠️ Requires manual cache clearing for every time-based transition
The Solution
This extension provides automatic temporal cache management with configurable scoping and timing strategies.
How It Works
Timeline:
09:00 → Cache generated, expires at 10:00 (next starttime)
10:00 → Cache regenerates, content now visible, expires at 11:00
11:00 → Cache regenerates, page appears in menu, expires at 12:00
12:00 → Cache regenerates, expired content hidden
✅ Fully automatic, no manual intervention
What Gets Fixed
Dynamic timing caps the page cache lifetime at the next transition; scheduler and hybrid timing flush cache tags once a transition has passed. Either way the cached output is regenerated:
- ✅ Menus (HMENU) - Pages appear/disappear based on starttime/endtime
- ✅ Content Elements - Scheduled content blocks update automatically
- ✅ Sitemaps - XML sitemaps reflect current page visibility
- ✅ Search Results - Cached search listings stay current
- ✅ Plugin Output - Any cached plugin with temporal records
- ✅ Custom Records - Tables registered through
TemporalMonitorRegistry;pagesandtt_contentare monitored by default
Features
Three Scoping Strategies
Choose how cache invalidation is scoped:
-
Global Scoping (default)
- Under dynamic timing, every rendered page expires at the next transition anywhere on the site
- Zero configuration, works everywhere
- Best for: sites with minimal temporal content
-
Per-Page Scoping (Targeted invalidation)
- Flushes
pageId_<uid>for the page a transition belongs to - Under dynamic timing, a rendered page's lifetime considers page transitions site-wide (menus) plus content transitions on that page
- Best for: most sites
- Flushes
-
Per-Content Scoping (Reference-aware)
- Resolves every page a content element appears on via
sys_refindexand flushes those page caches - Falls back to the element's parent page when
scoping.use_refindexis off or the reference index yields nothing - Best for: sites with extensive temporal content shared across pages
- Resolves every page a content element appears on via
Three Timing Strategies
Choose when to check for temporal transitions:
-
Dynamic Timing (Event-based)
- Caps the cache lifetime on every page cache generation, via
ModifyCacheLifetimeForPageEvent - Immediate response to transitions
- Best for: real-time requirements
- Caps the cache lifetime on every page cache generation, via
-
Scheduler Timing (Background processing)
- Sets no cache lifetime at all, so page rendering runs no temporal queries
- Transitions are processed by the scheduler task, which flushes the cache tags the scoping strategy selects
- Best for: high-traffic sites
-
Hybrid Timing (Both)
- Separate timing per content type (
timing.hybrid.pages,timing.hybrid.content) - Example: dynamic for pages, scheduler for content
- Best for: complex requirements
- Separate timing per content type (
Time Harmonization
Reduce cache churn by rounding transition times to fixed slots:
- Configure time slots (e.g., 00:00, 06:00, 12:00, 18:00)
- Transitions at 00:05, 00:15 and 00:45 all round to 00:00
- The tolerance is the maximum shift: a timestamp further from the nearest slot than the tolerance is left unchanged
- Rounding is applied by
temporalcache:harmonizeand by the backend module's bulk harmonization, not to records as they are saved
Backend Module
Visual management interface at Admin Tools → Temporal Cache:
-
Dashboard
- Statistics: total, active and scheduled temporal content, transitions in the next 30 days
- Timeline of the next seven days of transitions, grouped by day
- Current configuration summary and derived KPIs
-
Content
- Paginated list of temporal pages and content elements
- Filters: all, pages, content, active, scheduled, expired, harmonizable
- Per-record harmonization suggestions
- Bulk harmonization of selected records, after a confirmation dialog
-
Configuration Wizard
- Analysis of the current content and configuration, with recommendations
- Three presets:
simple(global/dynamic),balanced(per-page/hybrid/harmonization),aggressive(per-content/scheduler/harmonization) - The wizard shows the values; they are entered in Extension Configuration
Installation
Composer
composer require netresearch/nr-temporal-cache:^1.0
No stability flag is needed: v1.0.0 carries no pre-release suffix, and ext_emconf.php declares state stable.
TER (TYPO3 Extension Repository)
The extension key nr_temporal_cache is registered in TER; version 1.0.0 is published there.
Manual
- Download from GitHub
- Extract to
typo3conf/ext/nr_temporal_cache/ - Activate in Extension Manager
Requirements
From composer.json and ext_emconf.php:
- TYPO3
^12.4 || ^13.0 || ^14.0(ext_emconf.php: 12.4.0-14.99.99) - PHP
^8.1(ext_emconf.php: 8.1.0-8.5.99) typo3/cms-scheduler— required, installed with the extension; the scheduler task backs the scheduler and hybrid timing strategiestypo3/cms-reports— required, installed with the extension; adds a Temporal Cache entry to the Reports module
Post-Installation
- Apply the database schema. The extension ships its indexes in
ext_tables.sql—idx_temporalcache_starttimeandidx_temporalcache_endtimeonpagesandtt_content— so they are created by the schema migrator, not by hand:
vendor/bin/typo3 extension:setup
Admin Tools → Maintenance → Analyze Database Structure does the same. vendor/bin/typo3 temporalcache:verify reports whether the indexes are in place.
- Configure the extension (optional - defaults work for most sites):
- Admin Tools → Settings → Extension Configuration →
nr_temporal_cache
- Admin Tools → Settings → Extension Configuration →
Quick Start
CLI Commands Quick Reference
For administrators and DevOps:
# Verify database indexes and configuration vendor/bin/typo3 temporalcache:verify # Analyze temporal content and statistics vendor/bin/typo3 temporalcache:analyze --days=30 # List all temporal content vendor/bin/typo3 temporalcache:list --upcoming # Apply harmonization (with dry-run first) vendor/bin/typo3 temporalcache:harmonize --dry-run
See the command-line interface chapter for all four commands and their options.
Reports Module
Monitor system health via the TYPO3 backend:
- Navigate to System → Reports → Status Report
- Scroll to the Temporal Cache entries
- Review health indicators and recommendations
See the Reports module chapter for details.
Default Configuration (Zero Config)
Extension works out of the box with the defaults from ext_conf_template.txt:
- Scoping:
global(site-wide) - Timing:
dynamic(event-based) - Harmonization: disabled
This provides automatic temporal cache management with no configuration.
Recommended Configuration: "Balanced" Preset
scoping.strategy = per-page
timing.strategy = hybrid
harmonization.enabled = 1
harmonization.slots = 00:00,06:00,12:00,18:00
Trade-off: content-driven cache churn is limited to the page a content element lives on, while page transitions still expire every page so menus stay correct.
Recommended Configuration: "Aggressive" Preset
scoping.strategy = per-content
scoping.use_refindex = 1
timing.strategy = scheduler
harmonization.enabled = 1
harmonization.slots = 00:00,04:00,08:00,12:00,16:00,20:00
Trade-off: page rendering runs no temporal queries at all, and invalidation is limited to the pages a transitioned element actually appears on — at the cost of depending on a background task and an up-to-date reference index.
Scheduler Task (For Scheduler and Hybrid Timing)
Netresearch\TemporalCache\Task\TemporalCacheSchedulerTask collects every transition that passed since its last run and hands each one to the timing strategy, which flushes the cache tags the scoping strategy selects. It records its last run in the TYPO3 registry (tx_temporalcache/scheduler_last_run), so the interval between runs determines how quickly a transition takes effect. Dynamic timing does not need the task.
Configuration Options
The twelve settings from ext_conf_template.txt, with their defaults:
Scoping Strategy (scoping.strategy, default global)
global- site-wide: under dynamic timing every page expires at the next transition anywhereper-page- the affected pageper-content- every page the affected content appears on
Use Refindex (scoping.use_refindex, default 1)
- Read by the per-content strategy; with it off, a content transition falls back to the element's parent page
Timing Strategy (timing.strategy, default dynamic)
dynamic,scheduler,hybrid
Hybrid Strategy - Pages (timing.hybrid.pages, default dynamic)
- Rule for records in the
pagestable
Hybrid Strategy - Content (timing.hybrid.content, default scheduler)
- Rule for content records. With
hybridtiming the page cache lifetime is the earliest across both rules
Enable Harmonization (harmonization.enabled, default 0)
- Round transitions to fixed time slots
Time Slots (harmonization.slots, default 00:00,06:00,12:00,18:00)
- Comma-separated slots on a 24-hour clock;
HH:MMandH:MMare both accepted
Tolerance (harmonization.tolerance, default 3600)
- Maximum shift harmonization may apply, in seconds. A transition further from its nearest slot is left untouched;
0disables harmonization entirely
Auto-Round Reporting Flag (harmonization.auto_round, default 0)
- Records the intended policy for the status report and the
analyze/verifycommands. It has no save-time effect — harmonization is applied on demand from the backend module ortemporalcache:harmonize
Default Max Lifetime (advanced.default_max_lifetime, default 86400)
- Cache lifetime when no transition is pending, and the cap when TypoScript
config.cache_periodis not set
Debug Logging (advanced.debug_logging, default 0)
- Log temporal cache decisions
See the configuration reference for detailed explanations and examples.
Performance Summary
Behaviour by Configuration
| Scoping Strategy | Cache tags flushed on a transition | Timing Strategy | Per-page render cost |
|---|---|---|---|
| Global | pages |
Dynamic | 2 MIN() queries per monitored table - 4 with the default pages + tt_content - held in a request-level cache |
| Per-Page | pageId_<uid> of the affected page |
Dynamic | 4 queries by default: page transitions site-wide plus content transitions on the rendered page |
| Per-Content | pageId_<uid> for every page the element appears on |
Dynamic | Same as global - the lifetime lookup is the site-wide one |
| Any scoping | Same as above | Scheduler | No queries - the listener sets no lifetime |
| Any scoping | Same as above | Hybrid | Per content type, according to timing.hybrid.* |
How scoping and timing interact:
- Dynamic timing caps each rendered page's cache lifetime to its next relevant transition, as determined by the scoping strategy:
global— the next transition anywhere (every page expires together).per-page— the next page transition site-wide (menus) plus content transitions on that page only. This is where per-page scoping reduces content-driven cache churn.per-content— conservatively uses the site-wide transition for the lifetime (content can be embedded into arbitrary pages via references, so a per-page lifetime could serve stale embedded content). Its precise, per-content cache invalidation is applied to the flush tags under scheduler/hybrid timing.- Scheduler/Hybrid timing actively flush the cache tags chosen by the scoping strategy when a transition passes, giving
per-contentits full precision.Rule of thumb:
per-page+dynamicis great for content-heavy pages; large sites with cross-page embedded content should useper-content+scheduler.
Decision Guide
✅ Safe for:
- Sites with minimal temporal content (global scoping, dynamic timing - the defaults)
- Most sites (per-page scoping)
- Sites with extensive temporal content shared across pages (per-content scoping with scheduler timing and harmonization)
⚠️ Evaluate Carefully:
- Dynamic timing queries on every uncached page render - scheduler timing removes them
- Multi-language sites: transitions are resolved per workspace and language, so a request-level result is not reused across languages
- CDN/reverse proxy setups: the extension caps TYPO3's page cache lifetime only, upstream TTLs are unaffected
See Performance Considerations for detailed analysis and mitigation strategies.
Rollout
The extension works immediately after installation with the default configuration.
- Install it (see Installation)
- Apply the database schema update
- Run
vendor/bin/typo3 temporalcache:verify - Adjust the strategies in Extension Configuration (optional)
- Test in a staging environment
- Deploy to production
See the installation guide and the configuration reference for details.
Documentation
The manual is published at docs.typo3.org. Its source lives in Documentation/ and builds locally with composer docs:render:
- Introduction - Problem background
- Performance Considerations - Performance impact and mitigation
- Installation - Setup guide
- Configuration - Complete configuration reference
- Backend Module - Backend module user guide
- Command-line interface - CLI command reference
- Reports Module - TYPO3 Reports integration
- Architecture - Technical details
- Public API - What Semantic Versioning covers, and what is internal
- Phases - Approach, limits and a core solution
Compatibility
Declared support comes from composer.json and ext_emconf.php; the tested column is the matrix in .github/workflows/ci.yml.
| TYPO3 constraint | Declared PHP | PHP versions tested in CI |
|---|---|---|
^12.4 |
^8.1 |
8.1, 8.2, 8.3, 8.4 |
^13.0 |
^8.1 |
8.2, 8.3, 8.4, 8.5 |
^14.0 |
^8.1 |
8.3, 8.4, 8.5 |
What a core solution would need
Phase 1: Extension with Strategies (Current)
- ✅ Dynamic cache lifetime via PSR-14 event
- ✅ Three scoping strategies (global, per-page, per-content), page-aware under dynamic timing
- ✅ Three timing strategies (dynamic, scheduler, hybrid)
- ✅ Time harmonization for reduced cache churn
- ✅ Backend module for visual management
- Status: implemented as v1.0.0, state
stable; published to TER and Packagist
Phase 2: Absolute Expiration API (Proposed for TYPO3 Core)
- Extend
CacheTagso a tag can carry an absolute expiration timestamp - System-wide temporal cache awareness, without an extension
Phase 3: Automatic Temporal Detection (Proposed for TYPO3 Core)
- Zero-configuration temporal caching
- Automatic detection of starttime/endtime dependencies
- Uses the Phase 2 API transparently
None of this describes committed work in TYPO3 core: there is no accepted RFC, no target version and no timeline. The intent is to deprecate this extension if core ever covers it. See Phases.
Testing
# Unit tests composer ci:test:php:unit # Functional + integration tests composer ci:test:php:functional # Coverage report for the unit suite (HTML in .Build/coverage, clover in .Build/logs) composer ci:test:php:coverage # Check that report against the 69% threshold composer ci:test:php:coverage:check # Static analysis, code style and Rector composer ci:test:php:phpstan composer ci:test:php:cgl # check only composer ci:cgl # auto-fix composer ci:test:php:rector # dry-run
Test Suites
- Unit:
Tests/Unit, 27 test classes with stubbed/mocked dependencies (Build/phpunit/UnitTests.xml) - Functional:
Build/phpunit/FunctionalTests.xmlrunsTests/FunctionalandTests/Integration, 11 test classes against a real database (event listener, scheduler task, scoping/timing strategies, harmonization persistence, backend controller) - Coverage gate: CI runs both suites with coverage and uploads them to Codecov, which reports every pull request against the 69% project target in
codecov.yml.composer ci:test:php:coverage:checkis the local equivalent, measured on the unit suite alone.
Contributing
Contributions welcome. The conventions this repository enforces are in AGENTS.md.
- Fork the repository
- Create feature branch:
git checkout -b feature/my-feature - Commit with a Conventional Commit subject, signed and signed off:
git commit -S --signoff -m 'feat: add my feature' - Push to branch:
git push origin feature/my-feature - Submit pull request
CI runs code style, PHPStan and Rector, plus the unit and functional suites across the version matrix above.
Support & Issues
- Issues: GitHub Issues
- Forge: TYPO3 Forge #14277
- Documentation:
Documentation/in this repository
License
GPL-2.0-or-later - See LICENSE file
Credits
Developed by: Netresearch DTT GmbH
Addresses: TYPO3 Forge Issue #14277, reported 2004-08-20 and still open
Related Issues (both closed):
- #16815 - Sitemap ignoring "Start" and "End" flags
- #98964 - Menu object caching creates too many records resulting in huge cache_hash table
Made with ❤️ for the TYPO3 Community