Upgrade to 2.0
Version 2.0 of Timber
- Will only work as a Composer package.
- Tries to make the naming of functions and filters more consistent.
- Refactors how Timber Core works under the hood to improve compatibility with WordPress Core and be ready for future challenges.
- Removes a lot of deprecated code.
New requirements #
We upgraded Timber to work with more modern PHP and functionalities that only were introduced in newer versions of Timber. That’s why we now have the following requirements to run Timber:
- Timber 2.0 requires you to use a PHP version >= 7.4.
- Timber 2.0 requires WordPress 5.3 or greater. This was changed to take advantage of new Date/Time improvements built into WordPress. If you need to use WordPress 5.2 or earlier, please continue to use Timber 1.x.
- Timber now requires Twig in a version >= 2.15.3.
No more plugin support #
As of version 2.0, We will stop releasing Timber as a plugin. You need to install it through Composer.
If you are currently using Timber as a plugin, you can follow this guide to switch from the plugin to Timber 1.x. After that, you can follow the Upgrade to 2.0 guide to switch from (Composer-based) Timber 1.x to Timber 2.0.
Initializing Timber #
Up until now, you had to initialize Timber by instantiating a new object of the Timber\Timber
class. The constructor of that class is now protected and you’ll run into an error (Call to protected Timber\Timber::__construct() from invalid context
) if you try to initialize Timber with it.
🚫 Before
new Timber\Timber();
The new way to initialize Timber is to call Timber\Timber::init()
.
✅ Now
Timber\Timber::init();
Don’t use a $timber
global #
The Timber\Timber::init()
method doesn’t return anything. If you use an older pattern where you use a global variable to assign Timber, then it won’t do anything:
🚫 Don’t do this
global $timber;
$timber = Timber\Timber::init();
Checking Timber’s version #
If you need to check what Timber version is installed, you can use Timber::version
.
if (version_compare(Timber::$version, '2.0.0', '>=')) {
// Timber 2.x is installed.
}
Removed functionality #
Removed Routes #
The routing feature had been deprecated in Timber 1.x and was fully removed in Timber 2.0. Routing in Timber is outside its primary mission. Many of its use cases can usually be solved via existing WordPress functionality. We wanted to make it easier for developers to use other routing libraries.
In case you still need routing as it were before, you can install the library that Timber used before:
composer require upstatement/routes
Or you can use one of the available libraries and hook it into your code. Follow the Routing Guide for more information.
Removed Twig cache extension #
Timber used the twig/cache-extension
package, which was abandoned. You only need this package if you’ve used the {% cache %}
tag in Twig.
You should use twig/cache-extra
instead, which you can implement yourself. Check you the relevant section in the Performance/Caching Guide for more information.
If you still need the twig/cache-extension
package before moving to twig/cache-extra
, you can still install it yourself:
composer require twig/cache-extension
And then, you can enable it with the following filter:
functions.php
add_filter('timber/cache/enable_extension', '__return_true');
Removed Timber\Request
class #
We removed the Timber\Request
class without replacement, because it provided only limited functionality.
The Timber\Request
class was used to create a request
entry in the global Timber context. With this you had access to $_GET
and $_POST
in Twig.
🚫 This doesn’t work anymore
{{ request.get }}
{% if request.get.something %}{% endif %}
{{ request.post }}
{% if request.post.something %}{% endif %}
If you still need to work with requests in Twig, you can use a maintained and well-tested library. Here are some examples:
- Nyholm/psr7 – A super lightweight PSR-7 implementation.
- guzzle/psr7 – PSR-7 HTTP message library.
- laminas/laminas-diactoros – PSR HTTP Message implementations
And here’s an example for how you could add Nyholm/psr7 as a request
object in Twig.
add_filter('timber/context', function($context) {
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
$creator = new \Nyholm\Psr7Server\ServerRequestCreator(
$psr17Factory, // ServerRequestFactory
$psr17Factory, // UriFactory
$psr17Factory, // UploadedFileFactory
$psr17Factory // StreamFactory
);
$context['request'] = $creator->fromGlobals();
return $context;
});
Namespaced class names #
Namespaced class names were already introduced in Timber version 1.0. Up until now, you could still the use the old, non-namespaced class names. Only namespaced class names are used now. In version 2.0, we removed the following class aliases:
TimberArchives
, useTimber\Archives
insteadTimberComment
, useTimber\Comment
insteadTimberCore
, useTimber\Core
insteadTimberFunctionWrapper
, useTimber\FunctionWrapper
insteadTimberHelper
, useTimber\Helper
insteadTimberImage
, useTimber\Image
insteadTimberImageHelper
, useTimber\ImageHelper
insteadTimberIntegrations
, useTimber\Integrations
insteadTimberLoader
, useTimber\Loader
insteadTimberMenu
, useTimber\Menu
insteadTimberMenuItem
, useTimber\MenuItem
insteadTimberPost
, useTimber\Post
insteadTimberPostCollection
, without replacementTimber\PostsCollection
, without replacementTimberPostGetter
, without replacementTimberQueryIterator
, without replacementTimberRequest
, without replacementTimberSite
, useTimber\Site
insteadTimberTerm
, useTimber\Term
insteadTimberTermGetter
, without replacementTimberTheme
, useTimber\Theme
insteadTimberTwig
, useTimber\Twig
insteadTimberURLHelper
, useTimber\URLHelper
insteadTimberUser
, useTimber\User
insteadTimberCommand
, useTimber\Command
insteadTimber_WP_CLI_Command
, useTimber\Timber_WP_CLI_Command
instead
Timber\Timber #
A special case is the class alias Timber
for the Timber\Timber
class. We decided to keep it, because it’s more convenient to write Timber::render()
instead of Timber\Timber::render()
if you don’t use the use
operator.
Twig classes #
Timber requires a Twig version (2.12) which comes with its own namespaced classes:
- Instead of
Twig_Function
orTwig_SimpleFunction
, you need to useTwig\TwigFunction
. - Instead of
Twig_Filter
orTwig_SimpleFilter
, you need to useTwig\TwigFilter
. - Instead of
Twig_Environment
, you need to useTwig\Environment
.
You maybe use one of those classes with the timber/twig
filter. Make sure you update them.
In Timber v1, we used to have Timber\Twig_Function
and Timber\Twig_Filter
as interim classes that could be used for better compatibility with the different class names that exist with Twig. These are now removed as well. Use the classes Twig\TwigFunction
and Twig\TwigFilter
instead.
An updated API to get Timber objects #
In Timber 2.0, we updated the API to get Timber objects to avoid certain pitfalls. Here’s the short list:
- Use
Timber::get_post()
to get a post. Usingnew Timber\Post()
will not work anymore. - Use
Timber::get_posts()
to get posts. Usingnew Timber\PostQuery()
will not work anymore. - Use
Timber::get_term()
to get a term. Usingnew Timber\Term()
will not work anymore. - Use
Timber::get_terms()
to get terms. - Use
Timber::get_comment()
to get a comment. Usingnew Timber\Comment()
will not work anymore. - Use
Timber::get_comments()
to get comments. - Use
Timber::get_menu()
to get a menu. Usingnew Timber\Menu()
will not work anymore. - Use
Timber::get_user()
to get a user. Usingnew Timber\User()
will not work anymore. - Use
Timber::get_users()
to get users.
You will find more details in the following sections.
Factories #
Behind the scenes, Timber now uses a Factory Pattern to get Timber objects. We added factories for posts, terms, menus, menu items, comments and users. Most of the work for version 2 went into the factories.
No worries, you don’t have to understand this programming pattern to work with Timber. But it is very helpful for developers who use advanced coding patterns and extend Timber with their own PHP classes. Now it’s easier to control which PHP classes are used to create the different Timber objects using Class Maps.
Removed classes #
By using the Factory Pattern, we refactored a lot of code and by moving logic into the factory classes were able to remove the following classes:
Timber\PostCollection
– Timber now uses different classes for collections of posts, that all implement the newTimber\PostCollectionInterface
. Learn more about Post Collections in the Posts Guide.Timber\PostGetter
Timber\TermGetter
Timber\QueryIterator
Better compatibility with plugins #
We updated the Timber\PostsIterator
class for better compatibility with other WordPress plugins when looping over posts. We took great care to make sure that Timber calls the right functions and WordPress hooks in the right places when looping over posts.
For this, we used setup()
and teardown()
methods for posts. These methods can be used if you need to manipulate post objects before they are accessed in a loop.
We removed the timber/class/posts_iterator
filter hook, which you could use to use a custom post iterator. The functionality that you handled with a custom post iterator can now be handled with the setup()
and teardown()
methods on a post. To write custom setup()
and teardown()
methods, you can extend the Timber\Post
class. Learn more about extending Timber in the Extending Timber Guide.
If you’ve used a custom post iterator to handle a plugin incompatibility before, it might be that you won’t need to do anything and that the issue is resolved with the existing post iterator implementation in Timber 2.0.
Posts #
Before version 2.0, when you wanted to get a collection of posts, the standard way was to use Timber::get_posts()
, and Timber::get_post()
to get a single post. But you could also use new Timber\Post()
or new Timber\PostQuery()
.
We deprecated the possibility to instantiate objects directly. Instead, you should only use Timber::get_post()
and Timber::get_posts()
.
Make sure you also read the new Posts Guide.
Timber::get_post() #
Use Timber::get_post()
instead of new Timber\Post()
#
It’s not possible anymore to directly instantiate a post with new Timber\Post()
. Instead, you always need to use Timber::get_post()
and pass in the ID of the post.
single.php
// Figure out post to get from current query.
$post = Timber::get_post();
Any template
// Pass in a post ID to get a particular post.
$post = Timber::get_post(56);
Updated function signature for Timber::get_post()
#
We updated the function parameters for Timber::get_post()
.
🚫 Before
function get_post($query = false, $PostClass = 'Timber\Post')
{
};
✅ Now
function get_post(mixed $query = false, array $options = [])
{
};
We deprecated the $PostClass
parameter. If you want to control the class your post should be instantiated with, use a Class Map filter. Instead, we now have an $options
array.
Changed return type Timber::get_post()
on failure #
Timber::get_post()
returned false
if no post could be found in 1.x. Now, that function will return null
if no post could be found. We recommend using null
instead of false
for when no objects are found to match modern PHP practices.
Timber::get_posts() #
Use Timber::get_posts()
instead of new Timber\PostQuery()
#
🚫 Before
$query = [
'post_type' => 'book',
'posts_per_page' => 10,
'post_status' => 'publish',
];
$latest_books = new Timber\PostQuery($query);
✅ Now
$query = [
'post_type' => 'book',
'posts_per_page' => 10,
'post_status' => 'publish',
];
$latest_books = Timber::get_posts($query);
foreach ($latest_books->to_array() as $book) {
// Do something.
}
Use an array instead of query strings in Timber::get_posts()
#
It will no longer be possible to pass in arguments to Timber::get_posts()
as a query string. Instead, you will have to use the array notation.
🚫 Before
Timber::get_posts('post_type=article');
✅ Now
Timber::get_posts([
'post_type' => 'article',
]);
Updated function signature for Timber::get_posts()
#
We updated the function parameters for Timber::get_posts()
.
🚫 Before
function get_posts($query = false, $PostClass = 'Timber\Post', $return_collection = false)
{
}
✅ Now
function get_posts(array $query, array $options = [])
{
}
The function still accepts the $query
parameters as the first argument. Most of your queries will still work.
The following two parameters were removed:
$PostClass
– You can’t directly pass the class to instantiate the object with anymore. Instead, you will have to use a Class Map filter.$return_collection
– You can’t tell the function whether it should return aTimber\PostCollection
or an array of posts. It will always return an object implementingTimber\PostCollectionInterface
. But you can always convert to collection to posts when you useto_array()
:
$posts_as_array = Timber::get_posts()->to_array();
🚫 Before
$args = [
'post_type' => 'book',
'posts_per_page' => 10,
'post_status' => 'publish',
];
$latest_books_collection = Timber::get_posts($args, 'Book', true);
$latest_books_array = Timber::get_posts($args, 'Book');
✅ Now
$args = [
'post_type' => 'book',
'posts_per_page' => 10,
'post_status' => 'publish',
];
$latest_books_collection = Timber::get_posts($args);
$latest_books_array = Timber::get_posts($args)->to_array();
The new $options
parameter can be used to pass in options for the query. Check out the documentation for Timber::get_posts()
to see all options.
Post queries in Twig #
You can run post queries in Twig. Pass the parameters in an argument hash (in Twig, key-value arrays are called hashes, and you use curly braces {}
instead of brackets []
for them).
{# Hash notation #}
{% for post in get_posts({
post_type: 'post',
post_status: 'publish',
posts_per_page: 10
}) %}
<a href="{{ post.link }}">{{ post.title }}</a>
{% endfor %}
We still recommend you to run queries and prepare posts in PHP and not in Twig. But sometimes this is not possible when you don’t have access to the relevant PHP files, but only to Twig.
Post Collections #
We added some documentation about how to work with Post Collections, which are returned when you use Timber::get_posts()
.
Make sure you also read about the Laziness of posts.
Post Serialization #
When converting post data to JSON, for example to use post data in JavaScript, then we talk about Serialization. We added some documentation for that.
Terms #
Similar to posts, when you wanted to get a Timber term object before version 2.0, you could either use Timber::get_term()
or new Timber\Term()
. Now, instantiating Timber\Term
directly doesn’t work anymore. You will always have to use Timber::get_term()
.
Make sure you also read the new Terms Guide.
Timber::get_term() #
Use Timber::get_term()
instead of new Timber\Term()
#
It’s not possible anymore to directly instantiate a term with new Timber\Term()
. Instead, you always need to use Timber::get_term()
and pass in the ID of the term.
// Pass in a term ID (or a WP_Term object) to get a particular term.
$term = Timber::get_term(17);
Updated function signature for Timber::get_term()
#
We updated the function parameters for Timber::get_term()
.
🚫 Before
function get_term($term, $taxonomy = 'post_tag', $TermClass = 'Timber\Term')
{
};
✅ Now
function get_term($term = null)
{
};
- We removed the
$taxonomy
parameter. Timber needs the ID of a term or aWP_Term
object. It can figure out the taxonomy itself. - We removed the
$TermClass
parameter. If you want to control the class your term should be instantiated with, use a Term Class Map filter.
Removed edge case logic #
When you use Timber::get_term()
without any parameter, Timber returns the queried term if the queried object is a term.
There was a hidden logic in Timber::get_term()
for cases where there was no queried term: When the global query already had a tax_query
definition that queried specific terms, Timber would return the first of these terms.
We removed that logic, because it could also lead to unexpected behavior.
Timber::get_terms() #
Updated function signature for Timber::get_terms()
#
We updated the function parameters for Timber::get_terms()
.
🚫 Before
function get_terms($args = null, $maybe_args = [], $TermClass = 'Timber\Term')
{
};
✅ Now
function get_terms($args = null, array $options = [])
{
};
- The function still accepts an
$args
parameter where you pass in the same arguments that you would pass toWP_Term_Query()
. Most of your calls toTimber::get_terms()
should still work the same. - The
$options
parameter is not used yet, but it might be used in the future. - We removed the
$TermClass
parameter. If you want to control the class your term should be instantiated with, use a Term Class Map filter.
Updated default query parameters for Timber::get_terms()
#
Before Timber 2.0, we set the default argument hide_empty
to false
in the term query, which is not what WordPress does by default. WordPress always hides empty terms by default in term queries. For version 2, we don’t change these defaults and follow what WordPress does.
If you need empty terms, you need to explicitly set hide_empty
to false
.
$terms = Timber::get_terms([
'taxonomy' => 'category',
'hide_empty' => false,
]);
Comments #
To get a Timber comment object before version 2.0, you would use new Timber\Comment( $comment_id )
. We removed the possibility to instantiate comment objects directly. You will always have to use the new Timber::get_comment()
function.
🚫 Before
$comment = new Timber\Comment($comment_id);
✅ Now
$comment = Timber::get_comment($comment_id);
Menus #
Before version 2.0, when you wanted to get a menu, the standard way was to use new Timber\Menu()
. We removed the possibility to instantiate menu objects directly. Instead, you should only use the new Timber::get_menu()
function.
🚫 Before
$menu = new Timber\Menu('primary');
✅ Now
$menu = Timber::get_menu('primary');
A parameter is always required #
Before 2.0, you could pass in nothing to the constructor of Timber\Menu
to get the first menu Timber found. We removed the possibility to pass in nothing when getting a menu because it led to confusing cases.
🚫 Before
This will cause an error.
$menu = new Timber\Menu();
✅ Now
Always pass a parameter.
$menu = Timber::get_menu('primary');
The Pages Menu #
Previously, if you didn’t provide a parameter to Timber\Menu()
and didn’t have any menus registered, Timber would build a menu from your existing pages. To achieve this same functionality, you must now use the new Timber::get_pages_menu()
function.
🚫 Before
$menu = new Timber\Menu();
✅ Now
$menu = Timber::get_pages_menu();
If Timber::get_menu()
can’t find a menu with the parameters you used, it will now return null
.
Users #
To get a Timber user object before version 2.0, you would use new Timber\User( $user_id )
. We removed the possibility to instantiate user objects directly. You will always have to use the new Timber::get_user()
function.
🚫 Before
$user = new Timber\User($user_id);
✅ Now
$user = Timber::get_user($user_id);
Context #
Global context #
The context variables {{ wp_head }}
and {{ wp_footer }}
were removed definitely from the global context. Use {{ function('wp_head') }}
and {{ function('wp_footer') }}
in your Twig template directly.
Template context #
Version 2.0 introduces the concept of template contexts for Timber. This means that Timber will automatically set different variables like post
in your context for singular templates and posts
and maybe term
or author
for archive templates. Check out the section in the Context Guide for an overview.
Through the context, compatibility for third party plugins will be improved as well. Refer to the new Context Guide to learn more.
In short:
- If you use
$context['post'] = Timber::get_post()
or$context['posts'] = Timber::get_posts()
in your template files, you can probably omit these, because the context will do this for you. You might also benefit from better compatibility with third party plugins, because for singular templates, Timber will handle certain globals and hooks when setting uppost
. - If you decide to still use
$context['post'] = …
in your template file, then you should to set up your post through$context['post']->setup()
. Thesetup()
function improves compatibility with third-party plugins. - It’s now possible to overwrite the default arguments that are passed to the default query for
posts
. - When you need the global context in partials, then use
Timber::context_global()
to only load the global context.
Passing variables directly to context #
It’s now possible to pass data directly to Timber::context()
.
Here’s an example: Instead of setting a posts
variable in $context
after you’ve called Timber::context()
, you can pass an associative array with a posts
key and the same value to the Timber::context()
function:
Still correct
$context = Timber::context();
$context['posts'] = Timber::get_posts([
'post_type' => 'book',
'posts_per_page' => -1,
'post_status' => 'publish',
]);
Timber::render('archive.twig', $context);
Another way to do it
$context = Timber::context( [
'posts' => Timber::get_posts( [
'post_type' => 'book',
'posts_per_page' => -1,
'post_status' => 'publish',
],
] );
Timber::render( 'archive.twig', $context );
Twig #
Updated function names #
In Twig, you could use functions that were called the same as classes to convert objects or IDs of objects into Timber object. To have the same function names as in Timber’s public API, we’ve added the following functions:
{{ get_post() }}
{{ get_posts() }}
{{ get_attachment_by() }}
{{ get_term() }}
{{ get_terms() }}
{{ get_user() }}
{{ get_users() }}
{{ get_comment() }}
{{ get_comments() }}
The following functions are now deprecated:
Deprecated functions | Use one of these instead |
---|---|
{{ TimberPost() }} {{ Post() }} | {{ get_post() }} {{ get_posts() }} |
{{ TimberTerm() }} {{ Term() }} | {{ get_term() }} {{ get_terms() }} |
{{ TimberImage() }} {{ Image() }} | {{ get_image() }} {{ get_post() }} {{ get_attachment() }} {{ get_attachment_by() }} {{ get_posts() }} |
{{ TimberUser() }} {{ User() }} | {{ get_user() }} {{ get_users() }} |
Updated timber/twig filters #
In Timber 1.x, apart from timber/twig
, we had various (non-official) filters to filter the Twig Environment.
timber/twig/functions
timber/twig/filters
timber/twig/escapers
You shouldn’t use any of those to add functionality to the Twig Environment (\Twig\Environment
) anymore. In Timber 2.x, you should only use timber/twig
to extend \Twig\Environment
.
If you use any of the above filters to extend the Twig Environment, you’ll get a Call to a member function addFunction() on array
or a Call to a member function addFilter() on array
error.
Here’s how the filters are used in Timber 2.0:
Filter | Usage before | Usage now |
---|---|---|
timber/twig | Extend \Twig\Environment | Extend \Twig\Environment |
timber/twig/functions | 🚫 Extend \Twig\Environment | ✅ Add/remove Twig functions |
timber/twig/filters | 🚫 Extend \Twig\Environment | ✅ Add/remove Twig filters |
timber/twig/escapers | 🚫 Extend \Twig\Environment | ✅ Add/remove Twig escapers |
Check out the new Extending Twig Guide to learn more and see some examples.
Namespaced Twig locations #
You can now use namespaced Twig locations. Read more about this in the Template Locations Guide.
Better compatibility with Twig date functions #
Twig has a date
filter as well as a date()
function. Timber 2.0 has much better compatibility with this functionality than before. Now, you can use any of the functionality described in Twig’s documentation.
Timber will automatically load the timezone as well as the default date format from the WordPress settings when you work with dates in Twig. Even if it’s advised in the Twig documentation, you shouldn’t update the timezone in Twig (unless you know what you’re doing):
// Don’t do this!
$twig = new \Twig\Environment($loader);
$twig->getExtension(\Twig\Extension\CoreExtension::class)->setTimezone('Europe/Paris');
Also refer to the new Date/Time Guide for extended information on working with dates in Timber.
No context argument when calling actions in Twig #
In version 1.x of Timber, you would always get the context as a last argument in the hook function:
{% do action('my_action', 'foo') %}
add_action('my_action_with_args', 'my_function_with_args', 10, 3);
function my_function_with_args($foo, $post, $context)
{
echo 'I say ' . $foo . '!';
echo 'For the post with title ' . $context['post']->title();
}
In version 2.0, a context argument will no longer be passed to the hook function. Now, if you want anything from the template’s context, you’ll need to pass in the argument manually:
{% do action('my_action', 'foo', post) %}
add_action('my_action_with_args', 'my_function_with_args', 10, 2);
function my_function_with_args($foo, $post)
{
echo 'I say ' . $foo . '!';
echo 'For the post with title ' . $post->title();
}
Deprecated Twig filters #
The following Twig filters have been deprecated and will be removed in future versions of Timber:
|get_class
|print_r
In addition, the confusingly named (and non-functional) |get_type
filter has been removed.
Twig deprecations #
Apart from the deprecations listed above, you should also consider the deprecations by Twig itself.
- Deprecated features in Twig 2.x – Make sure you check out the deprecated features for Tags.
- Deprecated features in Twig 3.x – Twig mainly removes all deprecated features in 2.x.
There are a couple we want to highlight:
Spaceless #
The spaceless
tag is deprecated, you should use the apply
tag using the spaceless
filter instead.
🚫 Before
{% spaceless %}
{% endspaceless %}
✅ Now
{% apply spaceless %}
{% endapply %}
Filter tag #
The filter
tag is deprecated. Use the apply
tag in combination with apply_filters()
instead.
🚫 Before
{% filter apply_filters('your_filter_name') %}
{% endapply %}
✅ Now
{% apply apply_filters('your_filter_name') %}
{% endapply %}
Meta #
Deprecating direct access to meta values #
In Timber 1.x, it was possible to access meta values via dynamic properties. For example, you could do:
{{ post.my_custom_field_name }}
This is no longer the recommended way to access meta values because there might be conflicts with existing Timber methods or properties. For example, if you named a custom field date
and accessed it through {{ post.date }}
, it wasn’t clear if you would get the posts’ date or the value for your custom field named date
.
Access meta values through meta() #
The new recommended way to access meta values is through meta()
:
{{ post.meta('my_custom_field_name') }}
This way, your values will be filtered by third-party plugins like ACF.
Access raw meta values #
If you want to access the raw and unfiltered value directly from the database instead, you can use the new raw_meta()
method:
{{ post.raw_meta('my_custom_field_name') }}
The custom property #
Maybe you were also used to use the $custom
property on an object:
{{ post.custom.my_custom_field_name }}
This property was removed and you can no longer access meta values through it. It was only meant as a reference for you to see which meta values exist for an object when you use {{ dump() }}
or var_dump()
. To access the values, you should always use the meta('field_name')
and raw_meta('field_name')
methods. If you still need to know which meta values exist on an object, you can use meta()
or raw_meta()
without a field name:
{{ dump(post.meta()) }}
{{ dump(post.raw_meta()) }}
This is only recommended for development purposes, because it might affect your performance if you always request all values.
The meta()
and raw_meta()
methods work the same way for all Timber\Post
, Timber\Term
, Timber\User
and Timber\Comment
objects. You can read more about this in the Custom Fields Guide as well as the ACF Integrations Guide.
New classes #
New Attachment class #
Up until now, there was only a representation for WordPress image attachments in Timber. With version 2.0, we introduce a new Timber\Attachment
class that represents WordPress attachments – including the ones that might not necessarily be images, like PDF files.
- The
Timber\Image
class now extends theTimber\Attachment
class. All your code should already be compatible with this change. But in the future, you could use the newTimber\Attachment
class if you work with an attachment that is not an image. - We’ve added new methods for
Timber\Attachment
. See the section below. - To get attachments from attachment IDs in Twig, you can use
{{ get_attachment(attachment_id) }}
. Behind the curtains, Timber uses Class Maps to use theTimber\Image
andTimber\Attachment
classes for attachment posts.
New ExternalImage class #
With Timber 2.0, we differentiate between images that are WordPress attachments and "external" images that are loaded from a different location in your WordPress installation, for example from your theme folder.
Previously, when you used {{ Image(url).src }}
in Twig, you could also provide a path or URL to an external image. Now, you should use {{ get_external_image(url).src }}
.
With the new Timber::get_external_image()
function, you can load an external image in PHP.
New PagesMenu class #
To handle cases where you wanted to build a menu from your existing pages separately, we added a Timber\PagesMenu
class that will be used when you use the new Timber::get_pages_menu()
function.
Deprecated functions and variables #
The following functions are being deprecated and will be removed in a future version of Timber.
Timber\Timber #
get_context()
- usecontext()
instead.Timber::$autoescape
– use thetimber/twig/environment/options
filter instead.Timber::$twig_cache
– use thetimber/twig/environment/options
filter instead.Timber::$cache
– use thetimber/twig/environment/options
filter instead.
Timber\Post #
get_preview()
- useexcerpt()
instead.get_field()
- usemeta()
instead.import_field()
- usemeta()
instead.preview()
- useexcerpt()
instead.update()
- use WordPress core’supdate_post_meta()
instead.
Timber\PostQuery #
get_query()
, usequery()
instead.
Timber\Image and Timber\Attachment #
get_pathinfo()
– usepathinfo()
instead.get_dimensions()
– usewidth()
orheight()
instead.get_dimensions_loaded()
– usewidth()
orheight()
instead.get_dimension()
– usewidth()
orheight()
instead.get_dimension_loaded()
– usewidth()
orheight()
instead.get_post_custom()
– usemeta()
instead.
Timber\Term #
get_children()
– usechildren()
instead.get_edit_url()
– useedit_link()
instead.get_field()
– usemeta()
instead.get_meta_field()
– usemeta()
instead.get_posts()
– useposts()
instead.update()
- use WordPress core’supdate_metadata()
instead.
Timber\Comment #
get_field()
- use{{ comment.meta('my_field_name') }}
insteadget_meta_field()
- use{{ comment.meta('my_field_name') }}
insteadupdate()
- use WordPress core’supdate_metadata()
instead.
Timber\MenuItem #
external()
– use{{ item.is_external }}
instead.get_children()
– use{{ item.children }}
instead.get_field()
– use{{ item.meta }}
instead.
Timber\User #
get_field()
– use{{ user.meta('my_field_name') }}
instead.get_meta()
– use{{ user.meta('my_field_name') }}
instead.get_meta_field()
– use{{ user.meta('my_field_name') }}
instead.
Timber\Site #
$pingback
property – use$pingback_url
.meta()
– useoption()
instead.update()
– use WordPress core’supdate_blog_option()
instead.url()
– uselink()
instead.
Timber\Archives #
get_items()
– useitems()
instead.
Timber\Twig #
intl_date()
, useTimber\DateTimeHelper::wp_date()
instead.time_ago()
, useDateTimeHelper::time_ago()
instead.
Timber\Loader #
template_exists()
– No longer used internally.
Timber\LocationManager #
get_locations_user()
– Useadd_filter( 'timber/locations', $locations )
instead
Removed functions and properties #
The following functions and properties were removed from the codebase, either because they were already deprecated or because they’re not used anymore.
Timber\Timber #
add_route()
- The routes feature was completely removed in 2.0.get_pagination()
– Use{{ posts.pagination }}
instead. Follow the Pagination Guide for more information.
Timber\Site #
get_link()
, use{{ site.link }}
insteadget_url()
, use{{ site.link }}
instead
Timber\Post #
audio()
, use get_media_embedded_in_content() instead.get_author()
– use{{ post.author }}
insteadget_categories()
– use{{ post.categories }}
insteadget_category()
– use{{ post.category }}
insteadget_children()
– use{{ post.children }}
insteadget_comment_count()
– use{{ post.comment_count }}
insteadget_comments()
– use{{ post.comments }}
insteadget_content()
– use{{ post.content }}
insteadget_edit_url()
– uselink()
insteadget_format()
– use{{ post.format }}
insteadget_image()
– use{{ get_image(post.meta('my_image')) }}
or{{ get_attachment(post.meta('my_file')) }}
insteadget_link()
– use{{ post.link }}
insteadget_modified_author()
– use{{ post.modified_author }}
insteadget_modified_date()
– use{{ post.modified_date }}
insteadget_modified_time()
– use{{ post.modified_time }}
insteadget_next()
– use{{ post.next }}
insteadget_pagination()
– use{{ post.pagination }}
insteadget_parent()
– use{{ post.parent }}
insteadget_path()
– use{{ post.path }}
insteadget_permalink()
– use{{ post.link }}
insteadget_post_id_by_name()
get_post_type()
– use{{ post.type() }}
insteadget_prev()
– use{{ post.prev }}
insteadget_tags()
– use{{ post.tags }}
insteadget_terms()
– use{{ post.term }}
insteadget_thumbnail()
– use{{ post.thumbnail }}
insteadget_title()
– use{{ post.title }}
insteadinit()
– no replacement.permalink()
– use{{ post.link }}
insteadprepare_post_info()
video()
, use get_media_embedded_in_content() instead.
Timber\Term #
get_link()
– use{{ term.link }}
insteadget_path()
– use{{ term.path }}
insteadget_term_from_query()
- useTimber::get_term()
instead
Timber\Image #
init()
– only relevant if you’ve extended theTimber\Image
classdetermine_id()
– only relevant if you’ve extended theTimber\Image
classget_attachment_info()
– useget_info()
insteadget_src()
– use{{ image.src }}
insteadget_url()
– use{{ image.src }}
insteadurl()
– use{{ image.src }}
insteadis_image()
– without replacement. Timber now checks whether a post is an image if you useTimber::get_post()
orTimber::get_image()
and only returns aTimber\Image
object for attachments that are images.$caption
property – usecaption()
method instead. You can still use{{ image.caption }}
in Twig. In PHP$image->caption
also still works because magic properties will automatically resolve to$image->caption()
.$sizes
property - usesizes()
method instead. You can still use{{ image.sizes }}
in Twig. In PHP$image->sizes
also still works because magic properties will automatically resolve to$image->sizes()
.
Timber\MenuItem #
get_link()
– use{{ item.link }}
insteadget_path()
– use{{ item.path }}
insteadpermalink()
– use{{ item.link }}
insteadtype()
– use the theMenuItem::$type
property instead. In Twig, this is still the same:{{ item.type }}
.
Timber\Comment #
$PostClass
property – use Class Maps instead.
Timber\CommentThread #
$CommentClass
property – use Class Maps instead.
Timber\User #
$name
property – usename()
method instead. You can still use{{ user.name }}
in Twig.$first_name
property – use{{ user.meta('first_name') }}
instead.$last_name
property – use{{ user.meta('last_name') }}
instead.$description
property – use{{ user.meta('description') }}
instead.
Timber\Helper #
function_wrapper()
– use{{ function( 'function_to_call' ) }}
insteadtrim_words()
– useTextHelper::trim_words()
insteadclose_tags()
– useTextHelper::close_tags()
insteadget_comment_form()
– use{{ function('comment_form') }}
insteadpaginate_links()
– usePagination::paginate_links()
insteadget_current_url()
– useTimber\URLHelper::get_current_url()
insteadfilter_array()
– usearray_filter()
orTimber\Helper::wp_list_filter()
instead.
Timber\TextHelper #
starts_with()
- usestr_starts_with()
instead.ends_with()
- usestr_ends_with()
instead.
Timber\Integration\Command #
The whole Timber\Integration\Command
class was removed. Its methods were moved over to the Timber\Cache\Cleaner
class.
clear_cache()
, useTimber\Cache\Cleaner::clear_cache()
instead.clear_cache_timber()
, useTimber\Cache\Cleaner::clear_cache_timber()
instead.clear_cache_twig()
, useTimber\Cache\Cleaner::clear_cache_twig()
instead.
New functions #
Timber\Timber
context_global()
- Gets the global context.context_post()
- Gets post context for a singular template.context_posts()
- Gets posts context for an archive template.get_attachment()
– Gets an attachment.get_attachment_by()
– Gets an attachment by its URL or absolute file path.get_menu()
– Gets a menu object for a specific menu.get_image()
– Gets an image that is a WordPress attachment.get_external_image()
– Gets an image that is not a WordPress attachment.get_pages_menu()
– Gets a menu object built from your existing pages.get_post_by()
– Gets a post by title or slug.get_term_by()
– Gets a post by title or slug.get_user()
– Gets a single user.get_users()
– Gets one or more users as an array.get_user_by()
– Gets a user by field.
Timber\Post
raw_meta()
– Gets a post meta value directly from the database.wp_object()
- Gets the underlying WordPress Core object.
Timber\Term
raw_meta()
– Gets a term meta value directly from the database.wp_object()
- Gets the underlying WordPress Core object.
Timber\User
raw_meta()
– Gets a user meta value directly from the database.wp_object()
- Gets the underlying WordPress Core object.edit_link()
– Gets the edit link for a user if the current user has the correct rights.
Timber\Comment
raw_meta()
– Gets a comment meta value directly from the database.wp_object()
- Gets the underlying WordPress Core object.edit_link()
- Gets the edit link for a comment if the current user has the correct rights.
Timber\Menu
current_item()
– Gets the current menu item. Read more about this in the functions’s documentation or the Menu Guide.current_top_level_item()
– Gets the top level parent of the current menu item.wp_object()
- Gets the underlying WordPress Core object.
Timber\MenuItem
target()
– Gets the target of a menu item according to the «Open in new tab» option in the menu item options.is_target_blank()
– Checks whether the «Open in new tab» option checked in the menu item options in the backend.wp_object()
- Gets the underlying WordPress Core object.
Timber\Attachment
size()
- Gets the filesize of an attachment in bytes.size_raw()
- Gets the filesize of an attachment in a human-readable format. E.g.16 KB
instead of16555 bytes
.extension()
- Gets the extension of the attached file.
Timber\User
wp_object()
- Gets the underlying WordPress Core object.
New and updated functions #
Timber\Timber
get_post_by()
– Gets a post by post slug or post title.
Timber\Post
children()
– We removed the$child_post_class
parameter in this function. Use Class Maps instead to control which class to instantiate child posts with.comments()
– We removed the$CommentClass
parameter in this function. Use Class Maps instead to control which class to instantiate child posts with.
Timber\Cache\Cleaner
clear_cache()
- We remove the possibility to pass an array to that function. Now, only strings are accepted.
Timber\Post::terms() #
The Timber\Term::terms()
function already supported using an array of query arguments. But to make it more consistent with how other functions are used in Timber, we changed the signature to use two parameters: $query_args
and $options
.
Function Signature
// 🚫 Before
function terms($args = [], $merge = true, $term_class = '')
{
};
// ✅ Now
function terms($query_args = [], $options = [])
{
};
PHP
// 🚫 Before
$terms = $post->terms('category');
// ✅ Now
$terms = $post->terms([
'taxonomy' => 'category',
]);
or
// 🚫 Before
$terms = $post->terms([
'query' => [
'taxonomy' => 'custom_tax',
'orderby' => 'count',
],
'merge' => false,
]);
// ✅ Now
$terms = $post->terms([
'taxonomy' => 'custom_tax',
'orderby' => 'count',
], [
'merge' => false,
]);
Twig
{# 🚫 Before #}
{% for term in post.terms('category') %}
{# ✅ Now #}
{% for term in post.terms({ taxonomy: 'category' }) %}
{# or #}
{# ✅ Now #}
{% for term in post.terms({
query: {
taxonomy: 'custom_tax',
orderby: 'count'
},
merge: false
})}
Timber\Post::get_info() #
We changed the function signature for the get_info()
function. This function is responsible for the properties that are imported in the Timber\Post
object from a WP_Post
object.
This change is only relevant if you extend the Timber\Post
class and add a custom get_info()
method.
Before, the function got a post ID as a parameter and returned a WP_Post
object. Now, the function gets an array of post data and returns an updated array with data that will then be imported.
Before
protected function get_info($post_id)
{
}
Now
protected function get_info(array $data) : array
{
// Access the original post through $this->wp_object.
return $data;
}
Timber\Term::posts() #
We changed the function parameters for the Timber\Term::posts()
function to better support different use cases. Basically, you’ll use the same query parameters that you already know from WP_Query
.
Function Signature
// 🚫 Before
function posts($numberposts_or_args = 10, $post_type_or_class = 'any', $post_class = '')
{
};
// ✅ Now
function posts($query_args = [])
{
};
🚫 Old usage
In PHP:
$genre->posts(-1, 'book');
And in Twig:
{% for book in genre.posts(-1, 'book) %}
✅ New usage
In PHP:
$genre->posts([
'post_type' => 'book',
'post_per_page' => -1,
'orderby' => 'menu_order',
]);
And in Twig:
{% for book in genre.posts({
post_type: 'book',
posts_per_page: -1,
orderby: 'menu_order'
}) %}
You can still also pass an integer as the sole argument, to tell it how many posts you want.
$genre->posts(3);
This is equivalent to:
$genre->posts([
'posts_per_page' => 3,
'post_type' => 'any',
]);
Timber\URLHelper #
Timber\URLHelper::get_params()
#
The Timber\URLHelper::get_params()
method now returns false
if passed an index that does not exist, whereas before it returned null
in that case. This is for better consistency with other core API methods and only affects code using ===
or equivalent checks on return values from this method.
Timber\URLHelper::is_local()
and Timber\URLHelper::is_external()
#
The Timber\URLHelper::is_local()
and Timber\URLHelper::is_external()
methods were updated to fix some bugs with false positives, e.g. when an URL appeared as part of the query. The methods will now analyze the hostname instead of just checking the whole URL for the site’s URL.
Post Excerpts #
Excerpt instead of Preview #
We renamed the Timber\PostPreview
class to Timber\PostExcerpt
and also changed all instances of the term "preview" to "excerpt". The reason for this change is that the term "preview" in WordPress is mainly used for previewing a post before you save changes in the admin. For the short texts that are used in post teasers, the term "excerpt" is used. We wanted to follow the WordPress terminology here.
For this, we…
- Renamed the
Timber\PostPreview
class toTimber\PostExcerpt
. - Added a
Timber\Post::excerpt()
function that you should use instead ofTimber\Post::preview()
. - Changed filter names.
Read more links and end strings will not always be added #
We updated the logic for when read more links and end strings (…
by default) will be added. For example, if the excerpt is generated from the post’s content, but the post’s content isn’t longer than the excerpt, then no read more link and no end string will be added.
You can control this behavior with these two new parameters for excerpts:
always_add_read_more
– Controls whether a read more link should be added even if the excerpt isn’t trimmed (when the excerpt isn’t shorter than the post’s content). The default isfalse
.always_add_end
– Whether the end string should be added even if the excerpt isn’t trimmed. The default isfalse
.
Filter the defaults #
There’s a new timber/post/excerpt/defaults
filter that can be used to update default options for excerpts, including always_add_read_more
and always_add_end
.
add_filter('timber/post/excerpt/defaults', function ($defaults) {
// Only add a read more link if the post content isn’t longer than the excerpt.
$defaults['always_add_read_more'] = false;
// Set a default character limit.
$defaults['words'] = 240;
return $defaults;
});
post.excerpt
takes arguments in array/hash notation #
The {{ post.excerpt }}
function was added as a replacement for {{ post.preview }}
. When using {{ post.preview }}
, you could pass in arguments by chaining them. It’s now possible to pass in arguments as an array in PHP or in hash style in Twig. This finally cleans up how the previews of posts are handled. Here’s an example:
PHP
$post->excerpt([
'words' => 50,
'chars' => false,
'end' => '…',
'force' => false,
'strip' => true,
'read_more' => 'Read More',
]);
Twig
{{ post.excerpt({
words: 50,
chars: false,
end: "…",
force: false,
strip: true,
read_more: "Read More"
}) }}
Hooks #
In version 1.0, we already introduced some filters and actions that were namespaced with a timber/
prefix. In version 2.0, we refactored all filters to have the same naming standard. If you still use an old action or filter, you will see a warning with the name of the new hook. See the Hooks section in the documentation that lists all the current hooks. There, you’ll also see which hooks are deprecated.
Deprecated hooks #
You should update the following hooks because they will be removed in a future version of Timber. We use apply_filters_deprecated()
, so you should get a proper warning when WP_DEBUG
is set to true
.
Timber\Timber
timber_render_file
, usetimber/render/file
insteadtimber_render_data
, usetimber/render/data
insteadtimber_compile_file
, usetimber/compile/file
insteadtimber_compile_data
, usetimber/compile/data
insteadtimber_compile_done
, usetimber/compile/done
instead
Timber\Post
timber_post_get_meta_field_pre
, usetimber/post/pre_meta
insteadtimber_post_get_meta_pre
, usetimber/post/pre_get
insteadtimber_post_get_meta_field
, usetimber/post/meta
insteadtimber_post_get_meta
, usetimber/post/meta
insteadTimber\PostClassMap
, usetimber/post/classmap
instead
Timber\PostGetter
Timber\PostClassMap
, usetimber/post/classmap
Timber\PostPreview
timber/post/preview/read_more_class
, usetimber/post/excerpt/read_more_class
insteadtimber/post/get_preview/read_more_link
, usetimber/post/excerpt/read_more_link
instead
Timber\Term
timber/term/meta/field
, usetimber/term/meta
insteadtimber_term_get_meta_field
, usetimber/term/meta
insteadtimber_term_get_meta
, usetimber/term/meta
instead
Timber\Comment
timber_comment_get_meta_field_pre
, usetimber/comment/pre_meta
insteadtimber_comment_get_meta_pre
, usetimber/comment/pre_meta
insteadtimber_comment_get_meta_field
, usetimber/comment/meta
insteadtimber_comment_get_meta
, usetimber/comment/meta
instead
Timber\User
timber_user_get_meta_field_pre
, usetimber/user/pre_meta
insteadtimber_user_get_meta_pre
, usetimber/user/pre_meta
insteadtimber_user_get_meta_field
, usetimber/user/meta
insteadtimber_user_get_meta
, usetimber/user/meta
instead
Timber\Site
timber_site_set_meta
, usetimber/site/update_option
instead
Timber\Loader
timber/cache/location
, use an absolute path in thecache
option in thetimber/twig/environment/options
filter instead.timber/loader/paths
, usetimber/locations
instead
Timber\URLHelper
The following filter names have changed to match the WordPress naming convention for hooks, which says that hooks should be all lowercase:
timber/URLHelper/url_to_file_system/path
, usetimber/url_helper/url_to_file_system/path
insteadtimber/URLHelper/file_system_to_url
, usetimber/url_helper/file_system_to_url
insteadtimber/URLHelper/get_content_subdir/home_url
, usetimber/url_helper/get_content_subdir/home_url
instead
Deprecated without replacement #
The following filters were deprecated without a replacement and will be removed in the next major version:
- The filters
timber_term_set_meta
andtimber/term/meta/set
were deprecated. They were used byTerm::update()
, which is now deprecated as well (without a replacement).
timber/term/meta #
If you’ve used timber/term/meta
before, you might have to switch to timer/term/get_meta_fields
. The timber/term/meta
filter was introduced in 2.0 to be used instead of timber/term/meta/field
and timber_term_get_meta_field
. However, a filter timber/term/meta
already existed in Term::get_meta_values()
for version 1.0 and was renamed to timber/term/get_meta_fields
to match the new naming conventions.
Removed hooks #
The following filters were removed without a replacement:
Timber\PostClassMap
– use the Post Class Map instead.timber/get_posts/mirror_wp_get_posts
– This filter was used so thatTimber::get_posts()
mimics the behavior of WordPress’sget_posts()
function. We removed it becauseTimber::get_posts()
is the new official API. If you want the same behavior, there are arguments that you can pass to this function.timber_post_getter_get_posts
– Removed because theTimber\PostGetter
class doesn’t exist anymore.timber/class/posts_iterator
– Removed because theTimber\PostCollection
class doesn’t exist anymore. Use customPost::setup()
andPost::teardown()
methods instead. Read more about this in the Better compatibility with plugins section.
New hooks #
We added new hooks that let you filter things you couldn’t filter before:
timber/term/classmap
timber/menu/classmap
timber/menuitem/class
timber/pages_menu/class
timber/user/class
timber/comment/classmap
timber/twig/environment/options
Sideloaded/external images #
We changed the folder for images that are loaded from external URLs through Timber\ImageHelper::sideload_image()
or when you use the |resize
filter.
They will now be loaded to a folder named external in your uploads folder. We added this change because when you use a year-month-based folder structure, sideloaded images would be downloaded again each month.
You can control this behavior using the timber/sideload_image/subdir
filter.
We also integrated sideloading images in the new Timber::get_external_image()
function. If you provide it with an external image, it will sideload the image and return a Timber\ExternalImage
object to easily work with it.
Escaping #
While Twig has escaping enabled by default, Timber doesn’t automatically enable escaping for Twig. To enable autoescaping in Timber 1.x, you would use Timber::$autoescape = true
. The value true
was deprecated for Twig 2.0, you now have to use html
or another auto-escaping strategy instead. You’ll need to use the timber/twig/environment/options
filter:
add_filter('timber/twig/environment/options', function ($options) {
$options['autoescape'] = 'html';
return $options;
});
Read more about this in the Escaping Guide.
Integrations #
There’s a new way how integrations are initialized internally. Timber uses a new timber/integrations
filter to get all integrations it should init. Each integration uses an interface with a should_init()
and an init()
method that Timber can use to check whether an integration should be initialized before it actually initializes an integration.
With that filter and the interface, it’s easier to add your own integrations and remove or replace existing Timber integrations. Read all about it in the new Custom Integrations Guide.
WP-CLI #
Timber already had an integration for WP-CLI in version 1, but only few people knew about it. It is now documented in the WP CLI Guide.
We updated the WP-CLI commands for Timber:
🚫 Before
wp timber clear_cache
wp timber clear_cache_timber
wp timber clear_cache_twig
✅ After
wp timber clear-cache
wp timber clear-cache timber
wp timber clear-cache twig
Documentation #
We added a couple of new guides that you may want to read through in addition to this Upgrade Guide:
- Posts Guide
- Terms Guide
- Users Guide
- Class Maps Guide
- Context Guide
- Custom Fields Guide, which is now decoupled from the ACF Integrations Guide.
- Custom Integrations Guide
- WP CLI Guide