# 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](https://make.wordpress.org/core/2019/09/23/date-time-improvements-wp-5-3/) 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](https://github.com/timber/timber/discussions/2804). 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](https://timber.github.io/docs/v1/getting-started/switch-to-composer/).
After that, you can follow the [Upgrade to 2.0 guide](https://timber.github.io/docs/v2/upgrade-guides/2.0/) 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**

```php
new Timber\Timber();
```

The new way to initialize Timber is to call `Timber\Timber::init()`.

**✅ Now**

```php
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**

```php
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`.

```php
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:

```bash
composer require upstatement/routes
```

Or you can use one of the available libraries and hook it into your code. Follow the [Routing Guide](https://timber.github.io/docs/v2/guides/routing/) for more information.

### Removed Twig cache extension

Timber used the [`twig/cache-extension`](https://github.com/twigphp/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`](https://github.com/twigphp/cache-extra) instead, which you can implement yourself. Check you the [relevant section in the Performance/Caching Guide](https://timber.github.io/docs/v2/guides/performance/#cache-parts-of-the-twig-file-and-data) for more information.

If you still need the `twig/cache-extension` package before moving to [`twig/cache-extra`](https://github.com/twigphp/cache-extra), you can still install it yourself:

```bash
composer require twig/cache-extension
```

And then, you can enable it with the following filter:

**functions.php**

```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](https://timber.github.io/docs/v2/guides/context/). With this you had access to `$_GET` and `$_POST` in Twig.

**🚫 This doesn’t work anymore**

```twig
{{ 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](https://github.com/Nyholm/psr7) – A super lightweight PSR-7 implementation.
- [guzzle/psr7](https://github.com/guzzle/psr7) – PSR-7 HTTP message library.
- [laminas/laminas-diactoros](https://github.com/laminas/laminas-diactoros) – PSR HTTP Message implementations

And here’s an example for how you could add [Nyholm/psr7](https://github.com/Nyholm/psr7) as a `request` object in Twig.

```php
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`, use `Timber\Archives` instead
- `TimberComment`, use `Timber\Comment` instead
- `TimberCore`, use `Timber\Core` instead
- `TimberFunctionWrapper`, use `Timber\FunctionWrapper` instead
- `TimberHelper`, use `Timber\Helper` instead
- `TimberImage`, use `Timber\Image` instead
- `TimberImageHelper`, use `Timber\ImageHelper` instead
- `TimberIntegrations`, use `Timber\Integrations` instead
- `TimberLoader`, use `Timber\Loader` instead
- `TimberMenu`, use `Timber\Menu` instead
- `TimberMenuItem`, use `Timber\MenuItem` instead
- `TimberPost`, use `Timber\Post` instead
- `TimberPostCollection`, without replacement
- `Timber\PostsCollection`, without replacement
- `TimberPostGetter`, without replacement
- `TimberQueryIterator`, without replacement
- `TimberRequest`, without replacement
- `TimberSite`, use `Timber\Site` instead
- `TimberTerm`, use `Timber\Term` instead
- `TimberTermGetter`, without replacement
- `TimberTheme`, use `Timber\Theme` instead
- `TimberTwig`, use `Timber\Twig` instead
- `TimberURLHelper`, use `Timber\URLHelper` instead
- `TimberUser`, use `Timber\User` instead
- `TimberCommand`, use `Timber\Command` instead
- `Timber_WP_CLI_Command`, use `Timber\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](https://www.php.net/manual/en/language.namespaces.importing.php).

### Twig classes

Timber requires a Twig version (2.12) which comes with its own namespaced classes:

- Instead of `Twig_Function` or `Twig_SimpleFunction`, you need to use `Twig\TwigFunction`.
- Instead of `Twig_Filter` or `Twig_SimpleFilter`, you need to use `Twig\TwigFilter`.
- Instead of `Twig_Environment`, you need to use `Twig\Environment`.

You maybe use one of those classes with the [`timber/twig` filter](https://timber.github.io/docs/v2/hooks/filters/#timber/twig). 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. Using `new Timber\Post()` will not work anymore.
- Use `Timber::get_posts()` to get posts. Using `new Timber\PostQuery()` will not work anymore.
- Use `Timber::get_term()` to get a term. Using `new Timber\Term()` will not work anymore.
- Use `Timber::get_terms()` to get terms.
- Use `Timber::get_comment()` to get a comment. Using `new Timber\Comment()` will not work anymore.
- Use `Timber::get_comments()` to get comments.
- Use `Timber::get_menu()` to get a menu. Using `new Timber\Menu()` will not work anymore.
- Use `Timber::get_user()` to get a user. Using `new 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](https://en.wikipedia.org/wiki/Factory_method_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](https://timber.github.io/docs/v2/guides/extending-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](https://timber.github.io/docs/v2/guides/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 new `Timber\PostCollectionInterface`. [Learn more about Post Collections](https://timber.github.io/docs/v2/guides/posts/#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()`](https://timber.github.io/docs/v2/reference/timber-post/#setup) and [`teardown()`](https://timber.github.io/docs/v2/reference/timber-post/#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](https://timber.github.io/docs/v2/guides/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](https://timber.github.io/docs/v2/guides/posts/).

### 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**

```php
// Figure out post to get from current query.
$post = Timber::get_post();
```

**Any template**

```php
// 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**

```php
function get_post($query = false, $PostClass = 'Timber\Post')
{
};
```

**✅ Now**

```php
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](https://timber.github.io/docs/v2/guides/class-maps/#the-post-class-map). 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**

```php
$query = [
    'post_type' => 'book',
    'posts_per_page' => 10,
    'post_status' => 'publish',
];

$latest_books = new Timber\PostQuery($query);
```

**✅ Now**

```php
$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**

```php
Timber::get_posts('post_type=article');
```

**✅ Now**

```php
Timber::get_posts([
    'post_type' => 'article',
]);
```

#### Updated function signature for `Timber::get_posts()`

We updated the function parameters for `Timber::get_posts()`.

**🚫 Before**

```php
function get_posts($query = false, $PostClass = 'Timber\Post', $return_collection = false)
{
}
```

**✅ Now**

```php
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](https://timber.github.io/docs/v2/guides/class-maps/#the-post-class-map).
- **`$return_collection`** – You can’t tell the function whether it should return a `Timber\PostCollection` or an array of posts. It will always return an object implementing `Timber\PostCollectionInterface`. But you can always convert to collection to posts when you use `to_array()`:

```php
$posts_as_array = Timber::get_posts()->to_array();
```

**🚫 Before**

```php
$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**

```php
$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()`](https://timber.github.io/docs/v2/reference/timber-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).

```twig
{# Hash notation #}
{% set posts = get_posts({
    post_type: 'post',
    post_status: 'publish',
    posts_per_page: 10
}) %}
{% if posts is not empty %}
    <ul>
        {% for post in posts %}
            <li><a href="{{ post.link }}">{{ post.title }}</a></li>
        {% endfor %}
    </ul>
{% endif %}
```

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](https://timber.github.io/docs/v2/guides/posts/#post-collections), which are returned when you use `Timber::get_posts()`.

Make sure you also read about [the Laziness of posts](https://timber.github.io/docs/v2/guides/posts/#laziness-and-caching).

### Post Serialization

When converting post data to JSON, for example to use post data in JavaScript, then we talk about [Serialization](https://timber.github.io/docs/v2/guides/posts/#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](https://timber.github.io/docs/v2/guides/terms/).

### 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.

```php
// 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**

```php
function get_term($term, $taxonomy = 'post_tag', $TermClass = 'Timber\Term')
{
};
```

**✅ Now**

```php
function get_term($term = null)
{
};
```

- We removed the `$taxonomy` parameter. Timber needs the ID of a term or a `WP_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](https://timber.github.io/docs/v2/guides/class-maps/#the-term-class-map).

#### 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](https://github.com/timber/timber/pull/2664/), 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**

```php
function get_terms($args = null, $maybe_args = [], $TermClass = 'Timber\Term')
{
};
```

**✅ Now**

```php
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 to [`WP_Term_Query()`](https://developer.wordpress.org/reference/classes/WP_Term_Query/__construct/). Most of your calls to `Timber::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](https://timber.github.io/docs/v2/guides/class-maps/#the-term-class-map).

#### 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](https://developer.wordpress.org/reference/classes/WP_Term_Query/__construct/). 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`.

```php
$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**

```php
$comment = new Timber\Comment($comment_id);
```

**✅ Now**

```php
$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**

```php
$menu = new Timber\Menu('primary');
```

**✅ Now**

```php
$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.

```php
$menu = new Timber\Menu();
```

**✅ Now**

Always pass a parameter.

```php
$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**

```php
$menu = new Timber\Menu();
```

**✅ Now**

```php
$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**

```php
$user = new Timber\User($user_id);
```

**✅ Now**

```php
$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](https://timber.github.io/docs/v2/guides/context/#template-contexts) for an overview.

Through the context, compatibility for third party plugins will be improved as well. Refer to the new [Context Guide](https://timber.github.io/docs/v2/guides/context/) 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 up `post`.
- If you decide to still use `$context['post'] = …` in your template file, then you should to set up your post through `$context['post']->setup()`. The `setup()` function improves compatibility with third-party plugins.
- It’s now possible to [overwrite the default arguments](https://timber.github.io/docs/v2/guides/context/#change-arguments-for-default-query) 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**

```php
$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**

```php
$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() }}`<br>`{{ Post() }}`   | `{{ get_post() }}`<br>`{{ get_posts() }}`                                                                                   |
| `{{ TimberTerm() }}`<br>`{{ Term() }}`   | `{{ get_term() }}`<br>`{{ get_terms() }}`                                                                                   |
| `{{ TimberImage() }}`<br>`{{ Image() }}` | `{{ get_image() }}`<br>`{{ get_post() }}`<br>`{{ get_attachment() }}`<br>`{{ get_attachment_by() }}`<br>`{{ get_posts() }}` |
| `{{ TimberUser() }}`<br>`{{ User() }}`   | `{{ get_user() }}`<br>`{{ 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](https://timber.github.io/docs/v2/guides/extending-twig/#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](https://timber.github.io/docs/v2/guides/extending-twig/) 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](https://timber.github.io/docs/v2/guides/template-locations/#register-your-own-namespaces).

### Better compatibility with Twig date functions

Twig has a [`date`](https://twig.symfony.com/doc/filters/date.html) filter as well as a [`date()`](https://twig.symfony.com/doc/functions/date.html) 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):

```php
// 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](https://timber.github.io/docs/v2/guides/date-time/) 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:

```twig
{% do action('my_action', 'foo') %}
```

```php
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:

```twig
{% do action('my_action', 'foo', post) %}
```

```php
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](https://twig.symfony.com/doc/2.x/deprecated.html) – Make sure you check out the deprecated features for [Tags](https://twig.symfony.com/doc/2.x/deprecated.html#tags).
- [Deprecated features in Twig 3.x](https://twig.symfony.com/doc/3.x/deprecated.html) – 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](https://twig.symfony.com/doc/3.x/tags/apply.html) using the [`spaceless` filter](https://twig.symfony.com/doc/2.x/filters/spaceless.html) instead.

**🚫 Before**

```twig
{% spaceless %}

{% endspaceless %}
```

**✅ Now**

```twig
{% apply spaceless %}

{% endapply %}
```

#### Filter tag

The `filter` tag is deprecated. Use the [`apply` tag](https://twig.symfony.com/doc/3.x/tags/apply.html) in combination with `apply_filters()` instead.

**🚫 Before**

```twig
{% filter apply_filters('your_filter_name') %}

{% endapply %}
```

**✅ Now**

```twig
{% 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:

```twig
{{ 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()`:

```twig
{{ 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:

```twig
{{ post.raw_meta('my_custom_field_name') }}
```

### The custom property

Maybe you were also used to use the `$custom` property on an object:

```twig
{{ 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:

```twig
{{ 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](https://timber.github.io/docs/v2/guides/custom-fields/) as well as the [ACF Integrations Guide](https://timber.github.io/docs/v2/integrations/advanced-custom-fields/).

## 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 the `Timber\Attachment` class. All your code should already be compatible with this change. But in the future, you could use the new `Timber\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](https://timber.github.io/docs/v2/upgrade-guides/2.0/#new-functions).
- To get attachments from attachment IDs in Twig, you can use `{{ get_attachment(attachment_id) }}`. Behind the curtains, Timber uses [Class Maps](https://timber.github.io/docs/v2/guides/class-maps/) to use the [`Timber\Image`](https://timber.github.io/docs/v2/reference/timber-image/) and [`Timber\Attachment`](https://timber.github.io/docs/v2/reference/timber-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()` - use `context()` instead.
- `Timber::$autoescape` – use the `timber/twig/environment/options` filter instead.
- `Timber::$twig_cache` – use the `timber/twig/environment/options` filter instead.
- `Timber::$cache` – use the `timber/twig/environment/options` filter instead.

### Timber\Post

- `get_preview()` - use `excerpt()` instead.
- `get_field()` - use `meta()` instead.
- `import_field()` - use `meta()` instead.
- `preview()` - use `excerpt()` instead.
- `update()` - use WordPress core’s `update_post_meta()` instead.

### Timber\PostQuery

- `get_query()`, use `query()` instead.

### Timber\Image and Timber\Attachment

- `get_pathinfo()` – use `pathinfo()` instead.
- `get_dimensions()` – use `width()` or `height()` instead.
- `get_dimensions_loaded()` – use `width()` or `height()` instead.
- `get_dimension()` – use `width()` or `height()` instead.
- `get_dimension_loaded()` – use `width()` or `height()` instead.
- `get_post_custom()` – use `meta()` instead.

### Timber\Term

- `get_children()` – use `children()` instead.
- `get_edit_url()` – use `edit_link()` instead.
- `get_field()` – use `meta()` instead.
- `get_meta_field()` – use `meta()` instead.
- `get_posts()` – use `posts()` instead.
- `update()` - use WordPress core’s `update_metadata()` instead.

### Timber\Comment

- `get_field()` - use `{{ comment.meta('my_field_name') }}` instead
- `get_meta_field()` - use `{{ comment.meta('my_field_name') }}` instead
- `update()` - use WordPress core’s `update_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()` – use `option()` instead.
- `update()` – use WordPress core’s `update_blog_option()` instead.
- `url()` – use `link()` instead.

### Timber\Archives

- `get_items()` – use `items()` instead.

### Timber\Twig

- `intl_date()`, use `Timber\DateTimeHelper::wp_date()` instead.
- `time_ago()`, use `DateTimeHelper::time_ago()` instead.

### Timber\Loader

- `template_exists()` – No longer used internally.

### Timber\LocationManager

- `get_locations_user()` – Use `add_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](https://timber.github.io/docs/v2/guides/pagination/) for more information.

### Timber\Site

- `get_link()`, use `{{ site.link }}` instead
- `get_url()`, use `{{ site.link }}` instead

### Timber\Post

- `audio()`, use [get_media_embedded_in_content()](https://developer.wordpress.org/reference/functions/get_media_embedded_in_content/) instead.
- `get_author()` – use `{{ post.author }}` instead
- `get_categories()` – use `{{ post.categories }}` instead
- `get_category()` – use `{{ post.category }}` instead
- `get_children()` – use `{{ post.children }}` instead
- `get_comment_count()` – use `{{ post.comment_count }}` instead
- `get_comments()` – use `{{ post.comments }}` instead
- `get_content()` – use `{{ post.content }}` instead
- `get_edit_url()` – use `link()` instead
- `get_format()` – use `{{ post.format }}` instead
- `get_image()` – use `{{ get_image(post.meta('my_image')) }}` or `{{ get_attachment(post.meta('my_file')) }}` instead
- `get_link()` – use `{{ post.link }}` instead
- `get_modified_author()` – use `{{ post.modified_author }}` instead
- `get_modified_date()` – use `{{ post.modified_date }}` instead
- `get_modified_time()` – use `{{ post.modified_time }}` instead
- `get_next()` – use `{{ post.next }}` instead
- `get_pagination()` – use `{{ post.pagination }}` instead
- `get_parent()` – use `{{ post.parent }}` instead
- `get_path()` – use `{{ post.path }}` instead
- `get_permalink()` – use `{{ post.link }}` instead
- `get_post_id_by_name()`
- `get_post_type()` – use `{{ post.type() }}` instead
- `get_prev()` – use `{{ post.prev }}` instead
- `get_tags()` – use `{{ post.tags }}` instead
- `get_terms()` – use `{{ post.term }}` instead
- `get_thumbnail()` – use `{{ post.thumbnail }}` instead
- `get_title()` – use `{{ post.title }}` instead
- `init()` – no replacement.
- `permalink()` – use `{{ post.link }}` instead
- `prepare_post_info()`
- `video()`, use [get_media_embedded_in_content()](https://developer.wordpress.org/reference/functions/get_media_embedded_in_content/) instead.

### Timber\Term

- `get_link()` – use `{{ term.link }}` instead
- `get_path()` – use `{{ term.path }}` instead
- `get_term_from_query()` - use `Timber::get_term()` instead

### Timber\Image

- `init()` – only relevant if you’ve extended the `Timber\Image` class
- `determine_id()` – only relevant if you’ve extended the `Timber\Image` class
- `get_attachment_info()` – use `get_info()` instead
- `get_src()` – use `{{ image.src }}` instead
- `get_url()` – use `{{ image.src }}` instead
- `url()` – use `{{ image.src }}` instead
- `is_image()` – without replacement. Timber now checks whether a post is an image if you use `Timber::get_post()` or `Timber::get_image()` and only returns a `Timber\Image` object for attachments that are images.
- `$caption` property – use `caption()` 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 - use `sizes()` 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 }}` instead
- `get_path()` – use `{{ item.path }}` instead
- `permalink()` – use `{{ item.link }}` instead
- `type()` – use the the `MenuItem::$type` property instead. In Twig, this is still the same: `{{ item.type }}`.

### Timber\Comment

- `$PostClass` property – use [Class Maps](https://timber.github.io/docs/v2/guides/class-maps/) instead.

### Timber\CommentThread

- `$CommentClass` property – use [Class Maps](https://timber.github.io/docs/v2/guides/class-maps/) instead.

### Timber\User

- `$name` property – use `name()` 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' ) }}` instead
- `trim_words()` – use `TextHelper::trim_words()` instead
- `close_tags()` – use `TextHelper::close_tags()` instead
- `get_comment_form()` – use `{{ function('comment_form') }}` instead
- `paginate_links()` – use `Pagination::paginate_links()` instead
- `get_current_url()` – use `Timber\URLHelper::get_current_url()` instead
- `filter_array()` – use `array_filter()` or `Timber\Helper::wp_list_filter()` instead.

### Timber\TextHelper

- `starts_with()` - use `str_starts_with()` instead.
- `ends_with()` - use `str_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()`, use `Timber\Cache\Cleaner::clear_cache()` instead.
- `clear_cache_timber()`, use `Timber\Cache\Cleaner::clear_cache_timber()` instead.
- `clear_cache_twig()`, use `Timber\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](https://timber.github.io/docs/v2/guides/menus/#the-current-menu-item).
- `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 of `16555 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](https://timber.github.io/docs/v2/guides/class-maps/) instead to control which class to instantiate child posts with.
- `comments()` – We removed the `$CommentClass` parameter in this function. Use [Class Maps](https://timber.github.io/docs/v2/guides/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**

```php
// 🚫 Before
function terms($args = [], $merge = true, $term_class = '')
{
};

// ✅ Now
function terms($query_args = [], $options = [])
{
};
```

**PHP**

```php
// 🚫 Before
$terms = $post->terms('category');

// ✅ Now
$terms = $post->terms([
    'taxonomy' => 'category',
]);
```

or

```php
// 🚫 Before
$terms = $post->terms([
    'query' => [
        'taxonomy' => 'custom_tax',
        'orderby' => 'count',
    ],
    'merge' => false,
]);

// ✅ Now
$terms = $post->terms([
    'taxonomy' => 'custom_tax',
    'orderby' => 'count',
], [
    'merge' => false,
]);
```

**Twig**

```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**

```php
protected function get_info($post_id)
{

}
```

**Now**

```php
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**

```php
// 🚫 Before
function posts($numberposts_or_args = 10, $post_type_or_class = 'any', $post_class = '')
{
};

// ✅ Now
function posts($query_args = [])
{
};
```

**🚫 Old usage**

In PHP:

```php
$genre->posts(-1, 'book');
```

And in Twig:

```twig
{% for book in genre.posts(-1, 'book) %}
```

**✅ New usage**

In PHP:

```php
$genre->posts([
    'post_type' => 'book',
    'post_per_page' => -1,
    'orderby' => 'menu_order',
]);
```

And in Twig:

```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.

```php
$genre->posts(3);
```

This is equivalent to:

```php
$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 to `Timber\PostExcerpt`.
- Added a `Timber\Post::excerpt()` function that you should use instead of `Timber\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 is `false`.
- `always_add_end` – Whether the end string should be added even if the excerpt isn’t trimmed. The default is `false`.

### 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`.

```php
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**

```php
$post->excerpt([
    'words' => 50,
    'chars' => false,
    'end' => '&hellip;',
    'force' => false,
    'strip' => true,
    'read_more' => 'Read More',
]);
```

**Twig**

```twig
{{ post.excerpt({
    words: 50,
    chars: false,
    end: "&hellip;",
    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()`](https://developer.wordpress.org/reference/functions/apply_filters_deprecated/), so you should get a proper warning when `WP_DEBUG` is set to `true`.

**Timber\Timber**

- `timber_render_file`, use `timber/render/file` instead
- `timber_render_data`, use `timber/render/data` instead
- `timber_compile_file`, use `timber/compile/file` instead
- `timber_compile_data`, use `timber/compile/data` instead
- `timber_compile_done`, use `timber/compile/done` instead

**Timber\Post**

- `timber_post_get_meta_field_pre`, use `timber/post/pre_meta` instead
- `timber_post_get_meta_pre`, use `timber/post/pre_get` instead
- `timber_post_get_meta_field`, use `timber/post/meta` instead
- `timber_post_get_meta`, use `timber/post/meta` instead
- `Timber\PostClassMap`, use `timber/post/classmap` instead

**Timber\PostGetter**

- `Timber\PostClassMap`, use `timber/post/classmap`

**Timber\PostPreview**

- `timber/post/preview/read_more_class`, use `timber/post/excerpt/read_more_class` instead
- `timber/post/get_preview/read_more_link`, use `timber/post/excerpt/read_more_link` instead

**Timber\Term**

- `timber/term/meta/field`, use `timber/term/meta` instead
- `timber_term_get_meta_field`, use `timber/term/meta` instead
- `timber_term_get_meta`, use `timber/term/meta` instead

**Timber\Comment**

- `timber_comment_get_meta_field_pre`, use `timber/comment/pre_meta` instead
- `timber_comment_get_meta_pre`, use `timber/comment/pre_meta` instead
- `timber_comment_get_meta_field`, use `timber/comment/meta` instead
- `timber_comment_get_meta`, use `timber/comment/meta` instead

**Timber\User**

- `timber_user_get_meta_field_pre`, use `timber/user/pre_meta` instead
- `timber_user_get_meta_pre`, use `timber/user/pre_meta` instead
- `timber_user_get_meta_field`, use `timber/user/meta` instead
- `timber_user_get_meta`, use `timber/user/meta` instead

**Timber\Site**

- `timber_site_set_meta`, use `timber/site/update_option` instead

**Timber\Loader**

- `timber/cache/location`, use an absolute path in the `cache` option in the `timber/twig/environment/options` filter instead.
- `timber/loader/paths`, use `timber/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`, use `timber/url_helper/url_to_file_system/path` instead
- `timber/URLHelper/file_system_to_url`, use `timber/url_helper/file_system_to_url` instead
- `timber/URLHelper/get_content_subdir/home_url`, use `timber/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` and `timber/term/meta/set` were deprecated. They were used by `Term::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](https://timber.github.io/docs/v2/guides/class-maps/#the-post-class-map) instead.
- `timber/get_posts/mirror_wp_get_posts` – This filter was used so that `Timber::get_posts()` mimics the behavior of WordPress’s `get_posts()` function. We removed it because `Timber::get_posts()` is the new official API. If you want the same behavior, there are [arguments that you can pass to this function](https://timber.github.io/docs/v2/guides/posts#differences-from-wp-cores-get_posts).
- `timber_post_getter_get_posts` – Removed because the `Timber\PostGetter` class doesn’t exist anymore.
- `timber/class/posts_iterator` – Removed because the `Timber\PostCollection` class doesn’t exist anymore. Use custom `Post::setup()` and `Post::teardown()` methods instead. Read more about this in the [Better compatibility with plugins section](#better-compatibility-with-plugins).

### 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`](https://timber.github.io/docs/v2/hooks/filters/#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](https://twig.symfony.com/doc/2.x/api.html#environment-options) instead. You’ll need to use the `timber/twig/environment/options` filter:

```php
add_filter('timber/twig/environment/options', function ($options) {
    $options['autoescape'] = 'html';

    return $options;
});
```

Read more about this in the [Escaping Guide](https://timber.github.io/docs/v2/guides/escaping/).

## 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](https://timber.github.io/docs/v2/guides/custom-integrations/).

## 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](https://timber.github.io/docs/v2/guides/wp-cli/).

We updated the WP-CLI commands for Timber:

**🚫 Before**

```bash
wp timber clear_cache
wp timber clear_cache_timber
wp timber clear_cache_twig
```

**✅ After**

```bash
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](https://timber.github.io/docs/v2/guides/posts/)
- [Terms Guide](https://timber.github.io/docs/v2/guides/terms/)
- [Users Guide](https://timber.github.io/docs/v2/guides/users/)
- [Class Maps Guide](https://timber.github.io/docs/v2/guides/class-maps/)
- [Context Guide](https://timber.github.io/docs/v2/guides/context/)
- [Custom Fields Guide](https://timber.github.io/docs/v2/guides/custom-fields/), which is now decoupled from the [ACF Integrations Guide](https://timber.github.io/docs/v2/integrations/advanced-custom-fields/).
- [Custom Integrations Guide](https://timber.github.io/docs/v2/guides/custom-integrations/)
- [WP CLI Guide](https://timber.github.io/docs/v2/guides/wp-cli/)