| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| README.md | 2026-04-20 | 22.6 kB | |
| v2.4.0 source code.tar.gz | 2026-04-20 | 186.7 kB | |
| v2.4.0 source code.zip | 2026-04-20 | 226.3 kB | |
| Totals: 3 Items | 435.7 kB | 0 | |
2.4.0 (2026-04-17)
Timber 2.4.0 is a release focused on improved template composition, a smarter terms API, and a number of important bug fixes — including a nasty cache-poisoning bug affecting Gutenberg dynamic blocks. No breaking changes.
[!IMPORTANT] PHP Version Requirement
The minimum required PHP version for Timber has been bumped to PHP 8.2. Please ensure your server meets this requirement before upgrading.
✨ New Features
Twig Block Rendering
The headline feature of 2.4.0: Timber now supports rendering individual Twig blocks in isolation, without rendering the entire template. This unlocks proper component-based architectures and makes partial rendering (think HTMX, AJAX, server-sent fragments) a first-class citizen.
Details and code examples
Two new PHP methods are available: :::php // Return a block's output as a string $html = Timber::compile_twig_block('error', 'components/alerts.twig', [ 'message' => 'An error occurred', ]); // Render a block directly Timber::render_twig_block('success', 'components/alerts.twig', [ 'message' => 'Operation successful!', ]); And a matching Twig function for use inside templates: :::twig {{ render_twig_block('error', 'components/alerts.twig', { message: 'Please correct the errors below.' }) }} If the named block doesn't exist in the template, Timber gracefully falls back to rendering the full template. Full caching support is included. This feature is based on original work by [@JacobPrice](https://github.com/JacobPrice). ([#3236](https://github.com/timber/timber/issues/3236))PostQuery::terms() — Terms Scoped to a Query
Until now, calling Timber::get_terms() with a taxonomy returned all terms in that taxonomy — even those not used by any post in your current query. Building faceted filters or tag clouds scoped to a specific result set required multiple manual queries and a lot of boilerplate.
PostQuery now has a terms() method that returns only the terms actually attached to the posts in the collection.
Details and code examples
:::php $posts = Timber::get_posts([ 'post_type' => 'projects', 'category_name' => 'featured', ]); // All terms across all taxonomies (flat list) $all_terms = $posts->terms(); // Only categories used by these posts $categories = $posts->terms('category'); // Terms grouped by taxonomy — perfect for filter UIs $filters = $posts->terms(['category', 'post_tag'], ['merge' => false]); :::twig {# Flat list #} {% for term in posts.terms('category') %} {{ term.name }} {% endfor %} {# Grouped by taxonomy #} {% set filters = posts.terms('all', {merge: false}) %} {% for taxonomy, terms in filters %}{{ taxonomy }}
{% for term in terms %}{{ term.name }}{% endfor %} {% endfor %} Internally, this fires a single `WP_Term_Query` scoped to the IDs already in memory — replacing what used to be multiple loops and database queries. ([#3234](https://github.com/timber/timber/issues/3234))Timber::get_terms() — New merge Option
Complementing PostQuery::terms(), the merge grouping option is now exposed directly on Timber::get_terms(). Previously, the ability to group terms by taxonomy was tucked away inside Post::terms(). It's now available as a first-class option.
Details and code examples
:::php // Group terms by taxonomy $terms_by_tax = Timber::get_terms([ 'taxonomy' => ['category', 'post_tag'], 'object_ids' => [123, 456], ], ['merge' => false]); // Returns: // [ // 'category' => [Term, Term, ...], // 'post_tag' => [Term, Term, ...], // ] Existing `Post::terms()` calls are completely unaffected. ([#3213](https://github.com/timber/timber/issues/3213))Post::ancestors() Method
Posts now expose an ancestors() method, returning the chain of ancestor posts as an array of Timber\Post objects. Useful for breadcrumbs, nested page hierarchies, or any parent-traversal logic.
Details and code examples
:::php $post = Timber::get_post(); $ancestors = $post->ancestors(); :::twig {% for ancestor in post.ancestors %} {{ ancestor.title }} {% endfor %} ([#3158](https://github.com/timber/timber/issues/3158))🐛 Bug Fixes
Content Cache Poisoning with Gutenberg Dynamic Blocks
This was a subtle but painful bug: if $post->excerpt() (or any code path invoking excerpt()) was called before $post->content() on the same object — for example inside a custom Schema/JSON-LD method in <head> — all Gutenberg dynamic blocks (blocks with a render_callback, such as core/latest-posts or any custom block) would be silently stripped from the full content output.
The root cause was that Post::content() cached the block-stripped version used by the excerpt into $this->___content, and subsequent calls to content() hit that cache regardless of the $remove_blocks flag.
The fix ensures block-stripped content is never written to the cache. (#3208)
ACF Date Fields Now Respect WordPress Timezone
When ACF date/datetime fields were transformed to DateTimeImmutable objects via the timber/meta/transform_value filter, the resulting objects had no timezone applied — because ACF saves dates without timezone info. Timber now passes wp_timezone() when creating these objects, so the timezone from your WordPress settings is correctly applied. (#3163)
Image Letterboxing: Transparent Background No Longer Converts to Black
Images with transparency that were letterboxed had their transparent areas filled with black instead of being preserved. This has been fixed — transparent backgrounds are now handled correctly during letterbox image processing. (#3201, #3233)
Image Resize: Invalid Input No Longer Causes a Fatal Error
Passing invalid input to image resize operations previously triggered a fatal PHP error. Timber now handles invalid input gracefully without crashing. (#3235)
Performance: Image Dimensions Read from Attachment Metadata
Previously, Timber always determined image width and height by calling PHP's filesize() directly on the file — even when WordPress attachment metadata (stored in the database) already contained that information. Timber now reads from metadata first when an attachment ID is available. This means fewer filesystem I/O operations, reduced server load, and — when using an object cache — the potential to skip the filesystem entirely. Sites on CDNs particularly benefit. (#3232)
Transient Cache Expiration Bug Fixed
A bug in transient cache handling caused incorrect expiration behavior. (#3220)
Multisite: Timber\Site::switch_to_blog() Context Stacking Fixed
In multisite environments, Timber's Site class was calling restore_current_blog() unconditionally — even when a switch_to_blog() had not been added to WordPress's internal stack (this happened when the blog ID matched the current blog). This caused code running after a template render to operate on the main site instead of the currently switched-to site.
The fix aligns Timber's behavior with WordPress core: switch_to_blog() is always called so the switch is always registered in the stack, and restore_current_blog() then correctly undoes it. (#3223)
PostsIterator No Longer Resets Post Global in the Admin
PostsIterator was incorrectly resetting the global $post object inside the WordPress admin, which could interfere with admin screens that rely on the global post context. (#3117)
PHP 8.5 Compatibility
Fixed null index deprecation notices introduced in PHP 8.5, keeping Timber forward-compatible with the upcoming PHP release.
🧰 Developer Experience
- Expanded Twig function/filter docs — The documentation for available Twig functions and filters has been significantly expanded. (#3194)
- Deprecated escaper functions removed — Old deprecated escaper functions have been cleaned up and the escaper setup streamlined. (#3193)
- Code quality improvements — Several Rector and coding standard fixes were applied across the codebase. (#3164, #3237)
- Test suite modernized — The test suite has been upgraded to support PHPUnit 11.5+/12 and Mantle Testkit, and refactored for improved idempotency.
📦 How to upgrade
2.4.0 contains no breaking changes. Update via Composer:
:::bash
composer update timber/timber
🙏 Contributors
Thank you to everyone who contributed to this release:
@cbirdsong, @gchtr, @Levdbas, @nlemoine, @menno-ll, @JacobPrice, @langhenet
New Contributors
The following contributors made their first contribution to Timber. Congratulations! ✨
- @n3storm made their first contribution in https://github.com/timber/timber/pull/3156
- @mrleblanc101 made their first contribution in https://github.com/timber/timber/pull/3199
- @cnizzardini made their first contribution in https://github.com/timber/timber/pull/3218
- @menno-ll made their first contribution in https://github.com/timber/timber/pull/3223
📋 Full list of changes
Features
- Add
ancestors()method toTimber\Post(#3158) (a4e02a9) - Add merge/group option for
Timber::get_terms()(#3213) (d493b19) - Add missing class properties to Timber core entities (#3198) (389f0ed)
- Add
PostQuery::terms()to get terms scoped to the current query (#3234) (f25f6ab) - Add support for Twig Blocks (#3236) (c41dffa)
Bug Fixes
- Fix content cache poisoning when
Post::excerpt()is called first (#3208) (93e65ab) - Fix bug in image letterboxing when transparent background was converted to black (#3233) (a9d62e8)
- Fix bug when image resize with invalid input causes a fatal error (#3235) (b66b208)
- Fix bug with transient cache expiration (#3220) (091e60e)
- Fix context stacking issue with
Timber\Site::switch_to_blog()in multisite environment (#3223) (1ff183b) - Fix empty
$paramsreturning Timber object inTimber::get_posts()(#3179) (e732609) - Fix timezone bug when transforming ACF date fields (#3163) (513fc62)
- Improve performance for reading attachment image dimensions (#3232) (6b76d75)
- Fix bug in image letterboxing when transparent background was converted to black (#3201) (22b2d51)
- PHP 8.5
nullindex deprecations (86a6b44) - Prevent
Timber\PostsIteratorfrom resetting the post global in the admin (#3117) (9f14d48) - Refactor ACF transform hooks (279e932)
- Update lint script to exclude 'tmp' directory (a28a34d)
- Update static analysis workflow to correctly handle changed files (533181b)
Tests
- Add callable test for
timber/menu/classmap(f724acf) - Enhance test suite idempotency (b9d8260)
- Fix ancestors post tests (c49cd8f)
- Fix issue with image test that sets a constant (#3181) (6ae6e2a)
- Fix PHP 8.5 deprecation notice (8d2788e)
- Fix test pollution and WP 7.0 compatibility in test suite (7ff0b73)
- Fix
TimberWidgetsTestwith sidebar registration and widget setup methods (#3239) (3084850) - Modernize test suite with PHPUnit 11.5+/12 and Mantle Testkit (c854758)
- Refactor menu tests to use static factory methods for term and post creation (1e4e027)
- Update ACF field keys to ensure uniqueness (a38fa40)
- Update ACF field names for consistency and clarity (80b7986)
- Update tests for improved idempotency and accuracy in post queries (bd3eb80)
- Update tests to prevent auto-marking of current menu items (fd7920f)
- Update WPMLTest to use dynamic menu values (251c505)
Documentation
- Fix classname for
Timber\Helperin performance docs (#3156) (1798ba9) - Expand documentation for available Twig functions and filters (#3194) (75145db)
- Fix docblock example of
Site::__call()(#3185) (9003468) - Fix wrong use of
apply_filters()instead ofadd_filter()in Performance Guide (#3229) (a29dfc0) - Update links in documentation and fix spelling mistakes (#3238) (5af4b84)
- Fix WooCommerce Guide - tease-product.twig (#3199) (9a7d411)
Continuous Integration
- Bump Conductor PHP version (21f7794)
- Update default PHP version to 8.2 in setup action (e052190)
- Update PHP/WP test versions (98b010f)
- Upgrade Composer install action to version 4 (#3215) (967541e)
Styles
Miscellaneous Chores
- deps-dev: bump league/commonmark from 2.8.0 to 2.8.1 (#3207) (93f8ece)
- deps-dev: bump symfony/process from 7.4.3 to 7.4.5 (#3187) (c81733b)
- deps: bump actions/checkout from 4 to 6 (f1efafd)
- deps: bump actions/checkout from 5 to 6 (#3160) (4d6b9a6)
- deps: bump codecov/codecov-action from 5 to 6 (#3224) (abb44ac)
- deps: bump lycheeverse/lychee-action from 2.6.1 to 2.7.0 (#3157) (4f7bde4)
- deps: bump lycheeverse/lychee-action from 2.7.0 to 2.8.0 (#3205) (b64b67a)
- deps: bump peter-evans/create-issue-from-file from 5 to 6 (#3154) (1bfd169)
- deps: bump ramsey/composer-install from 3 to 4 (#3214) (944c871)
- deps: bump WyriHaximus/github-action-composer-php-versions-in-range (#3225) (d31a521)
- deps: Update Composer dependencies (#3165) (31c78f9)
- deps: update content-hash and bump easy-coding-standard to version 13.0.4 (c451ded)
- deps: update mantle-framework dependencies to version 1.16 (e752c14)
- Final code quality improvements before release (#3237) (966873e)
- Final dependency update and Rector fix (a76daed)
- Remove deprecated escaper functions and streamline escaper setup (#3193) (072b4fa)
- Update Changelog sections in release configuration (223d674)
- Update CODEOWNERS (#3166) (aa38e98)
- Update README badges for Codecov and remove Coveralls and Scrutinizer links (d89bff9)
Full Changelog: https://github.com/timber/timber/compare/v2.3.3...v2.4.0
Become a sponsor
Do you love using Timber for your projects? Consider supporting us by becoming a sponsor. Your sponsorship helps us maintain & improve Timber for everyone! 💚🌲 Join the Timber family today.