Download Latest Version v2.4.0 source code.tar.gz (186.7 kB)
Email in envelope

Get an email when there's a new version of Timber themes

Home / v2.4.0
Name Modified Size InfoDownloads / 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! ✨

📋 Full list of changes

Features

  • Add ancestors() method to Timber\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 $params returning Timber object in Timber::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 null index deprecations (86a6b44)
  • Prevent Timber\PostsIterator from 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 TimberWidgetsTest with 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\Helper in 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 of add_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.

Source: README.md, updated 2026-04-20