Docs Contents

Getting Started

Getting Started: Setup

Installation

Via WordPress.org (easy)

You can just grab the all-things-included plugin at WordPress.org either through the WP site or your Plugins->Add New in wp-admin. Then skip ahead to using the starter theme.

Via GitHub (for developers)

The GitHub version of Timber requires Composer. If you’d prefer one-click installation, you should use the WordPress.org version.

composer require timber/timber

If your theme is not setup to pull in Composer’s autoload file, you will need to:

<?php
require_once(__DIR__ . '/vendor/autoload.php');

at the top of your functions.php file.

Initialize Timber with:

<?php
$timber = new \Timber\Timber();

Use the starter theme

This is for starting a project from scratch. You can also use Timber in an existing theme.

Like where twentyeleven and twentytwelve live. The Timber Starter will live at the same level.

/wp-content/themes /twentyeleven /twentytwelve /timber-starter-theme

You should now have:

/wp-content/themes/timber-starter-theme

You should probably rename this to something better.

1. Activate Timber

It will be in wp-admin/plugins.php

2. Select your theme in WordPress

Make sure you select the Timber-enabled theme after you activate the plugin. The theme will crash unless Timber is activated. Use the timber-starter-theme theme from the step above (or whatever you renamed it).

3. Let’s write our theme!

Getting Started: Themeing

Your first Timber project

Let’s start with your single post

Find this file:

    wp-content/themes/{timber-starter-theme}/views/single.twig

Brilliant! Open it up.

{% extends "base.twig" %}
{% block content %}
    <div class="content-wrapper">
        <article class="post-type-{{ post.post_type }}" id="post-{{ post.ID }}">
            <section class="article-content">
                <h1 class="article-h1">{{ post.title }}</h1>
                <h2 class="article-h2">{{ post.subtitle }}</h2>
                <p class="blog-author"><span>By</span> {{ post.author.name }} <span>&bull;</span> {{ post.post_date|date }}</p>
                {{ post.content }}
            </section>
        </article>
    </div> <!-- /content-wrapper -->
{% endblock %}

This is the fun part.

<h1 class="article-h1">{{ post.title }}</h1>

This is now how we now call stuff from the WordPress API. Instead of this familiar face:

<h1 class="article-h1"><?php the_title(); ?></h1>

This is how WordPress wants you to interact with its API. Which sucks. Because soon you get things like:

<h1 class="article-h1"><a href="<?php get_permalink(); ?>"><?php the_title(); ?></a></h1>

Okay, not too terrible, but doesn’t this (Timber) way look so much nicer:

<h1 class="article-h1"><a href="{{ post.link }}">{{ post.title }}</a></h1>

It gets better. Let’s explain some other concepts.

{% extends "base.twig" %}

This means that single.twig is using base.twig as its parent template. That’s why you don’t see any <head>, <header>, or <footer> tags, those site-wide (usually) things are all controlled in base.twig. You can create any number of base files to extend from (the “base” naming convention is recommended, but not requrired).

What if you want modify <head>, etc? Read on to learn all about blocks.

Blocks

Blocks are the single most important and powerful concept in managing your templates. The official Twig Documentation has more details. Let’s cover the basics.

In single.twig you see opening and closing block declarations that surround the main page contents.

{% block content %} {# other stuff here ... #} {% endblock %}

If you were to peek into base.twig you would see a matching set of {% block content %} / {% endblock %} tags. single.twig is replacing the content of base’s {% block content %} with its own.

Nesting Blocks, Multiple Inheritance

This is when things get really cool. Whereas most people use PHP includes in a linear fashion, you can create infinite levels of nested blocks to particularly control your page templates. For example, let’s say you occasionally want to replace the title/headline on your single.twig template with a custom image or typography.

For this demo let’s assume that the name of the page is “All about Jared” (making its slug all-about-jared). First, I’m going to surround the part of the template I want to control with block declarations:

{# single.twig #}
{% extends "base.twig" %}
{% block content %}
    <div class="content-wrapper">
        <article class="post-type-{{ post.post_type }}" id="post-{{ post.ID }}">
            <section class="article-content">
                {% block headline %}
                    <h1 class="article-h1">{{ post.title }}</h1>
                    <h2 class="article-h2">{{ post.subtitle }}</h2>
                {% endblock %}
                <p class="blog-author"><span>By</span> {{ post.author.name }} <span>&bull;</span> {{ post.post_date|date }}</p>
                {{ post.content }}
            </section>
        </article>
    </div> <!-- /content-wrapper -->
{% endblock %}

Compared to the earlier example of this page, we now have the {% block headline %} bit surrounding the <h1> and <h2>.

To inject my custom bit of markup, I’m going to create a file called single-all-about-jared.twig in the views directory. The logic for which template should be selected is controlled in single.php but generally follows WordPress conventions on Template Hierarchy. Inside that file, all I need is:

{# single-all-about-jared.twig #}
{% extends "single.twig" %}
{% block headline %}
    <h1><img src="wp-content/uploads/2014/05/jareds-face.jpg" alt="Jared's Mug"/></h1>
{% endblock %}

So two big concepts going on here:

  1. Multiple Inheritance I’m extending {% single.twig %}, which itself extends {% base.twig %}. Thus we stay true to DRY and don’t have very similar code between my two templates hanging around.
  2. Nested Blocks {% block headline %} is located inside {% block content %}. So while I’m replacing the headline, I get to keep all the other markup and variables found in the parent template.

What if you want to add to the block as opposed to replace? No prob, just call {{ parent() }} where the parent’s content should go.

Loop / Index page

Let’s crack open index.php and see what’s inside:

<?php
$context = Timber::get_context();
$context['posts'] = Timber::get_posts();
Timber::render('index.twig', $context);

This is where we are going to handle the logic that powers our index file. Let’s go step-by-step.

Get the starter

<?php
$context = Timber::get_context();

This is going to return an object with a lot of the common things we need across the site. Things like your nav, wp_head and wp_footer you’ll want to start with each time (even if you over-write them later). You can do a print_r($context); to see what’s inside or open-up timber.php to inspect for yourself

Grab your posts

<?php
$context['posts'] = Timber::get_posts();

We’re now going to grab the posts that are inside the loop and stick them inside our data object under the posts key.

Timber::get_posts() usage
Use a WP_Query array
    <?php
    $args = array(
        'post_type' => 'post',
        'tax_query' => array(
            'relation' => 'AND',
            array(
                'taxonomy' => 'movie_genre',
                'field' => 'slug',
                'terms' => array( 'action', 'comedy' )
            ),
            array(
                'taxonomy' => 'actor',
                'field' => 'id',
                'terms' => array( 103, 115, 206 ),
                'operator' => 'NOT IN'
            )
        )
    );
    $context['posts'] = Timber::get_posts($args);
Use a WP_Query string
    <?php
    $args = 'post_type=movies&numberposts=8&orderby=rand';
    $context['posts'] = Timber::get_posts($args);
Use Post ID numbers
    <?php
    $ids = array(14, 123, 234, 421, 811, 6);
    $context['posts'] = Timber::get_posts($ids);

Render

<?php
Timber::render('index.twig', $context);

We’re now telling Twig to find index.twig and send it our data object.

Timber will look first in the child theme and then falls back to the parent theme (same as WordPress logic). The official load order is:

  1. User-defined locations
  2. Directory of calling PHP script (but not theme)
  3. Child theme
  4. Parent theme
  5. Directory of calling PHP script (including the theme)

Item 2 is inserted above others so that if you’re using Timber in a plugin it will use the twig files in the plugin’s directory.

Video Tutorials

I’m in the midst of an install and walk-through on Timber, here are the screencasts thus far:

1. Install Timber

Option 1: Via GitHub (for developers)

1) Navigate to your WordPress plugins directory

$ cd ~/Sites/mywordpress/wp-content/plugins

2) Use git to grab the repo

$ git clone git@github.com:jarednova/timber.git

3) Use Composer to download the dependencies (Twig, etc.)

$ cd timber $ composer install

Option 2: Via Composer (for developers)

1) Navigate to your WordPress plugins directory

$ cd ~/Sites/mywordpress/wp-content/plugins

2) Use Composer to create project and download the dependencies (Twig, etc.)

$ composer create-project –no-dev jarednova/timber ./timber

Option 3: Via WordPress plugins directory (for non-developers)

If you’d prefer one-click installation, you should use the WordPress.org version.

Now just activate in your WordPress admin screen. Inside of the timber directory there’s a timber-starter-theme. To use this move it into your themes directory (probably want to rename it too) and select it.


2. Including a Twig template and sending data

Installing Timber

In which we use an existing WordPress template and implement a very simple Timber usage.

Here’s the relevant code:

<?php
/* index.php */
$context = array();
$context['headline'] = 'Welcome to my new Timber Blog!';
Timber::render('welcome.twig', $context);
{# welcome.twig #}
<section class="welcome-block">
    <div class="inner">
        <h3>{{headline}}</h3>
        <p>This will be a superb blog, I will inform you every day</p>
    </div>
</section>

3. Connecting Twig to your WordPress Admin

Connecting Timber

<?php
$context = array();
$context['welcome'] = Timber::get_post(56);
Timber::render('welcome.twig', $context);
<section class="welcome-block">
    <div class="inner">
        <h3>{{welcome.post_title}}</h3>
        <p>{{welcome.get_content}}</p>
        <p>Follow me on <a href="https://twitter.com/{{welcome.twitter_handle}}" target="_blank">Twitter!</a></p>
    </div>
</section>

4. Converting HTML to Twig Templates

Connecting HTML Templates

<?php
$context['posts'] = Timber::get_posts();
Timber::render('home-main.twig', $context);
{# home-main.twig #}
{% for post in posts %}
    {% include "tz-post.twig" %}
{% endfor %}

5. Using Custom Post Types with Timber + Twig

Using Custom Post Types with Timber

{# home-main.twig #}
{% for post in posts %}
    {# you can send includes an array, in order of precedence #}
    {% include ["tz-"~post.post_type~".twig", "tz-post.twig"] %}
{% endfor %}
{# tz-recipe.twig #}
<article id="post-{{post.ID}}" class="post-{{post.ID}} {{post.post_type}} type-{{post.post_type}} status-publish hentry">
    {% if post.get_thumbnail %}
        <img src="{{post.get_thumbnail.get_src|resize(600, 300)}}" />
    {% endif %}
    <h2>{{post.post_title}}</h2>
    <div class="post-body">
        {{post.get_content}}
    </div>
</article>

6. Extending Templates

Todo: Record Screencast showing this

This is a really important concept for DRY. I’ll show how to create a base template that can power your site:

Create a base.twig file:
{# base.twig #}
{% include "html-header.twig" %}
{% block head %}
    <!-- This is where you'll put template-specific stuff that needs to go in the head tags like custom meta tags, etc. -->
{% endblock %}
</head>
<body class="{{body_class}}">
{% block content %}
    <!-- The template's main content will go here. -->
{% endblock %}
{% include "footer.twig" %}
{{wp_footer}}
</body>
</html>
You can use this in a custom single.twig file:
{# single.twig #}
{% extends "base.twig" %}
{% block head %}
    <meta property="og:title" value="{{post.title}}" />
{% endblock %}
{% block content %}
    <div class="main">
        <h1>{{post.title}}</h1>
        <p>{{post.get_content}}</p>
    </div>
{% endblock %}

Tools for Working with Twig

Text editor add-ons

Other

JavaScript

Guides

1.0 Upgrade Guide

A significant part of this 1.0 release is removing lots of deprecated functions and methods that go back to the earliest designs for Timber in early 2013.

Should I upgrade?

Maybe. Read on:

I have a legacy site

If you wrote a theme in 2014, it’s running fine and the development of the site has concluded: stop right there! Do not upgrade! While there are some slight performance benefits (and not having the warning light on the plugin dashboard) it’s not worth it to you. Close this window and forget you ever heard about Timber 1.0

I have a site under continued development

Maybe. There are going to be tradeoffs with whatever route you pick. I recommend testing your site thoroughly with a staging or local copy to find all the issues before applying Timber 1.0 to production. However, you’re the best judge of how much potential refactoring you’re looking at and whether it’s worth the benefits.

I am starting a new site.

Definitely. You’ll get the benefits of being on the newest platform. We have some cool stuff ahead that you’ll want to include in your development workflow, and of course you’ll learn about the newest/best ways to make Timber themes.

Routes

The Routes feature has now been totally deprecated. If you have custom routes you’ll need to add the Upstatement/routes repo to your theme? How to do this?

Simply add Upstatement/routes to your composer.json file like so:

"require": {
    "timber/timber":"1.*",
    "advanced-custom-fields/advanced-custom-fields-pro": "*",
    "Upstatement/routes": "*"
},

Here’s a full Gist of what this looks like. Run a composer install to update your theme’s dependencies.

Note: because we’re not monsters, the [Upstatement/routes] repo will continue to be included with Timber for the next several versions

2. Add to your theme:

If you haven’t already, you’ll need to load Composer’s autoload file into your theme via:

<?php
/* functions.php */
require_once('vendor/autoload.php');
3. Update your Routes:

Now just update the PHP for your old routes to the new ones. It’ll be basically the same code, but with cleaner syntax and a different arguments order for the ::load method

Before:
<?php
Timber::add_route('myfoo/bar', 'my_callback_function');
Timber::add_route('my-events/:event', function($params) {
    $query = new WP_Query('post_type=event');
    Timber::load_view('single.php', $query, 200, $params);
});
After:
<?php
Routes::map('myfoo/bar', 'my_callback_function');
Routes::map('my-events/:event', function($params) {
    $query = new WP_Query('post_type=event');
    /* please note the different order of arguments vs. Timber::load_template */
    Routes::load('single.php', $params, $query, 200);
});

… and that’s the hardest part, done!

Object Properties

Post

Static methods

Way back in 0.18, a new helper library was added for URLs. In my infinite wisdom I called it TimberURLHelper. Previously old static methods in TimberHelper still worked (like TimberHelper::get_current_url) — they don’t any longer. The full list of methods you’ll need to update is…

Deprecated

Many of the aliases with get_* are now deprecated. For awhile, we’ve recommended using post.title instead of post.get_title, post.thumbnail instead of post.get_thumbnail, etc. In future versions this will be enforced.

ACF Cookbook

Timber is designed to play nicely with (the amazing) Advanced Custom Fields. It’s not a requirement, of course.

While data saved by ACF is available via {{post.my_acf_field}} you will often need to do some additional work to get back the kind of data you want. For example, images are stored as image ID#s which you might want to translate into a specific image object. Read on to learn more about those specific exceptions.

WYSIWYG field (and other requiring text):

<h3>{{post.title}}</h3>
<div class="intro-text">
     {{post.get_field('my_wysiwyg_field')}}
</div>

This will apply your expected paragraph breaks and other pre-processing to the text.

Image field type:

You can retrieve an image from a custom field, then use it in a Twig template. The most reliable approach is this: When setting up your custom fields you’ll want to save the image_id to the field. The image object, url, etc. will work but it’s not as fool-proof.

The quick way (for most situations)
<img src="{{TimberImage(post.get_field('hero_image')).src}}" />
The long way (for some special situations)

This is where we’ll start in PHP.

<?php
/* single.php */
$post = new TimberPost();
if (isset($post->hero_image) && strlen($post->hero_image)){
    $post->hero_image = new TimberImage($post->hero_image);
}
$data = Timber::get_context();
$data['post'] = $post;
Timber::render('single.twig', $data);

TimberImage should be initialized using a WordPress image ID#. It can also take URLs and image objects, but that requires extra processing.

You can now use all the above functions to transform your custom images in the same way, the format will be:

<img src="{{post.hero_image.src|resize(500, 300)}}" />

{% for image in post.get_field('gallery') %}
    <img src="{{ TimberImage(image) }}" />
{% endfor %}

Repeater field

You can access repeater fields within twig files:

{# single.twig #}
<h2>{{post.title}}</h2>
<div class="my-list">
    {% for item in post.get_field('my_repeater') %}
        <div class="item">
            <h4>{{item.name}}</h4>
            <h6>{{item.info}}</h6>
            <img src="{{TimberImage(item.picture).src}}" />
        </div>
    {% endfor %}
</div>
Nested?

When you run get_field on an outer ACF field, everything inside is ready to be traversed. You can refer to nested fields via item_outer.inner_repeater

{% for item_outer in post.get_field('outer') %}
     {{item_outer.title}}

     {% for item_inner in item_outer.inner_repeater %}
          {{item_inner.title}}
     {% endfor %}

{% endfor %}
Troubleshooting

A common problem in working with repeaters is that you should only call the get_field method once on an item. In other words if you have a field inside a field (for example, a relationship inside a repeater or a repeater inside a repeater, do not call get_field on the inner field). More:

DON’T DO THIS: (Bad)
{% for gear in post.get_field('gear_items') %}
    <h3> {{ gear.brand_name }} </h3>
    {% for gear_feature in gear.get_field('features') %}
        <li> {{gear_feature}} </li>
    {% endfor %}
{% endfor %}
Do THIS: (Good)
{% for gear in post.get_field('gear_items') %}
    <h3> {{ gear.brand_name }} </h3>
    {% for gear_feature in gear.features %}
        <li> {{gear_feature}} </li>
    {% endfor %}
{% endfor %}

Flexible content field

Similar to repeaters, get the field by the name of the flexible content field:

{% for media_item in post.get_field('media_set') %}
    {% if media_item.acf_fc_layout == 'image_set' %}
        <img src="{{TimberImage(media_item.image).src}}" />
        <p class="caption">{{TimberImage(media_item.image).caption}}</p>
        <aside class="notes">{{media_item.notes}}</aside>
    {% elseif media_item.acf_fc_layout == 'video_set' %}
        <iframe width="560" height="315" src="http://www.youtube.com/embed/{{media_item.youtube_id}}" frameborder="0" allowfullscreen></iframe>
        <p class="caption">{{media_item.caption}}</p>
    {% endif %}
{% endfor %}

Options Page

    <?php
    $context['site_copyright_info'] = get_field('copyright_info', 'options');
    Timber::render('index.twig', $context);
    <footer>{{site_copyright_info}}</footer>
Get all info from your options page
    <?php
    $context['options'] = get_fields('options');
    Timber::render('index.twig', $context);

ACF Pro has a built in options page, and changes the get_fields('options') to get_fields('option').

    <footer>{{options.copyright_info}}</footer>
Use options info site wide

To use any options fields site wide, add the option context to your functions.php file

<?php
/* functions.php */
add_filter( 'timber_context', 'mytheme_timber_context'  );

function mytheme_timber_context( $context ) {
    $context['options'] = get_fields('option');
    return $context;
}

Now, you can use any of the option fields across the site instead of per template.

/* footer.twig */
<footer>{{options.copyright_info}}</footer>

Getting ACF info:

You can grab specific field label data like so:

<?php
/* single.php */
$context["acf"] = get_field_objects($data["post"]->ID);
{{ acf.your_field_name_here.label }}

Query by custom field value:

Use a WP_Query array

Basic Example

This example shows the arguments to find all posts where a custom field called ‘color’ has a value of ‘red’.

<?php
$args = array(
    'numberposts' => -1,
    'post_type' => 'post',
    'meta_key' => 'color',
    'meta_value' => 'red'
);
$context['posts'] = Timber::get_posts($args);

Cheatsheet

Here are some helpful conversions for functions you’re probably well familiar with in WordPress and their Timber equivalents. These assume a PHP file with the Timber::get_context(); function at the top. For example:

$context = Timber::get_context();
$context['post'] = new TimberPost();
Timber::render('single.twig', $context);

Blog Info

Body Class

Post

Theme

In WordPress parlance, stylesheet_directory = child theme, template directory = parent theme. Both WP and Timber functions safely return the current theme info if there’s no parent/child going on.

wp_functions

Image Cookbook

Timber makes it damn easy to use an image in a tag. Automatically, Timber will interpret images attached to a post’s thumbnail field (“Featured Image” in the admin) and treat them as TimberImages. Then, in your Twig templates, you can access them via {{post.thumbnail}}. If you want to see what’s inside the TimberImage object you can run a…

{{post.thumbnail|print_r}}

…inside one of your Twig templates.

Basic Image stuff

Again, pretty damn easy:

<img src="{{post.thumbnail.src}}" class="my-thumb-class" alt="Image for {{post.title}}" />

Use a WP image size

You can use WP’s image sizes (including ones you register with your theme/plugin) by passing the name of the size to src like so:

<img src="{{post.thumbnail.src('medium')}}" class="my-thumb-class" alt="Image for {{post.title}}" />

Note: If the WP size (e.g medium) has not been generated, it will return an empty string.

Arbitrary Resizing of Images

Want to resize an image? Easy! Here we’re going to use Twig Filters.

<img src="{{post.thumbnail.src|resize(300, 200)}}" />

The first parameter is width the second is height (but it’s optional) so if you don’t know the height but just want to scale proportionally:

<img src="{{post.thumbnail.src|resize(640)}}" />

All of these filters are written specifically to interact with WordPress’s image API. (So don’t worry, no weird TimThumb stuff going on—this is all using WP’s internal image sizing stuff).

Letterboxing Images

Let’s say you have an image that you want to contain to a certain size without any cropping. If the proportions don’t fit you’ll letterbox the extra space. I find this is really useful when getting logos to all appear next to eachother. You can do this with:

<img src="{{post.thumbnail.src|letterbox(400, 400, '#FFFFFF')}}" />

Here width and height are required. The third argument is the background color in hex format (default is #000000).

Converting images

Let’s say your client or editor can be a bit lazy (no!), resorting to PNGs where only JPGs are required. I’ve seen this a lot. People will just upload screenshots that are saved by default as PNGs. No problemo!

<img src="{{post.thumbnail.src|tojpg}}" />

You can use this in conjunction with other filters.

<img src="{{post.thumbnail.src|tojpg|resize(300, 300)}}" />

Filters are executed from left to right. You’ll probably want to convert to JPG before running the resizing, etc.

Generating Retina Sizes

You can use Timber to generate @2x image sizes for retina devices. For example, using srcset:

<img src="{{ post.thumbnail.src }}" srcset="{{ post.thumbnail.src | retina(1) }} 1x,
    {{ post.thumbnail.src | retina(2) }}  2x,
    {{ post.thumbnail.src | retina(3) }}  3x,
    {{ post.thumbnail.src | retina(4) }}  4x">

This can be used in conjunction with other filters, so for example:

<img src="{{ post.thumbnail.src|resize(400, 300) }}" srcset="{{ post.thumbnail.src |resize(400, 300) | retina(1) }} 1x,
    {{ post.thumbnail.src | resize(400, 300) | retina(2) }}  2x,
    {{ post.thumbnail.src | resize(400, 300) | retina(3) }}  3x,
    {{ post.thumbnail.src | resize(400, 300) | retina(4) }}  4x">

Using images in custom fields:

Let’s say you’re using a custom field plugin (like the amazing Advanced Custom Fields). You can use the resulting images in your Twig templates very easily.

When setting up your custom fields you’ll want to save the image_id to the field. The image object, url, etc. will work but it’s not as fool-proof.

The quick way (for most situations)
<img src="{{TimberImage(post.hero_image).src}}" />
The long way (for some special situations)

This is where we’ll start in PHP.

<?php
/* single.php */
$post = new TimberPost();
if (isset($post->hero_image) && strlen($post->hero_image)){
    $post->hero_image = new TimberImage($post->hero_image);
}
$data = Timber::get_context();
$data['post'] = $post;
Timber::render('single.twig', $data);

TimberImage should be initialized using a WordPress image ID#. It can also take URLs and image objects, but that requires extra processing.

You can now use all the above functions to transform your custom images in the same way, the format will be:

<img src="{{post.hero_image.src|resize(500, 300)}}" />

…etc

Text Cookbook

There’s tons of stuff you can do with Twig and Timber filters to make complex transformations easy (and fun!)

Dates

Timber does bylines like a boss:
<p class="byline">
    <span class="name">By {{ post.author.name }}</span>
    <span class="date">{{ post.post_date|date('F j, Y') }}</span>
</p>
Renders:
<p class="byline"><span class="name">By Mr. WordPress</span><span class="date">September 28, 2013</span></p>
<footer>
    <p class="copyright">&copy; {{ now|date('Y') }} by {{ bloginfo('name') }}</p>
</footer>
Renders:
<footer><p class="copyright">&copy; 2015 by The Daily Orange</p></footer>

Standard transforms

<p class="tweet">{{ post.content|twitterify }}</p>
Run WordPress’ auto-paragraph filter
<p class="content">{{ post.my_custom_text|wpautop }}</p>
Run WordPress shortcodes over a block of text
<p class="content">{{ post.my_custom_text|shortcodes }}</p>
Code samples? Lord knows I’ve got ‘em:
<div class="code-sample">{{ post.code_samples|pretags }}</div>
Functions inside of your templates, plugin calls:

Old template:

<p class="entry-meta"><?php twentytwelve_entry_meta(); ?></p>

Timber-fied template:

<p class="entry-meta">{{ function('twentytwelve_entry_meta') }}</p>
Functions “with params” inside of your templates, plugin calls:

Old template:

<p class="entry-meta"><?php get_the_title( $post->ID ); ?></p>

Timber-fied template:

<p class="entry-meta">{{ function('get_the_title', post.ID) }}</p>

Debugging

What properties are inside my object?
{{ dump(post) }}
What properties and methods are inside my object?

Warning: Experimental!

{{ post|print_a }}

This outputs both the database stuff (like {{ post.post_content }}) and the contents of methods (like {{ post.thumbnail }})

What type of object am I working with?
{{ post|get_class }}

… will output something like TimberPost or your custom wrapper object

Twig Cookbook

Using Twig vars in live type

Imagine this scenario, I let the users set this in the Admin panel:

Copyright {{year}} by Upstatement, LLC. All Rights Reserved

But on the site I want it to render as:

Copyright 2013 by Upstatement, LLC. All Rights Reserved

Ready? There are a bunch of ways, but my favorite is:

In your PHP file

<?php
$data['year'] = date('Y');
$data['copyright'] = get_option("footer_message"); //"Copyright {{year}} by Upstatement, LLC. All Rights Reserved"
render_twig('footer.twig', $data);
{% include template_from_string(copyright) %}

Includes

Simple include

{% include "footer.twig" %}

Notes

Dynamic includes

Use a variable to determine the included file!

{% include ['blocks/block-'~block.slug~'.twig', 'blocks/blog.twig'] ignore missing %}

Huh?

Custom Page Templates

There are a few ways to manage custom pages in WordPress and Timber, in order from simple-to-complex:

Custom Twig File

Say you’ve created a page called “About Us” and WordPress has given it the slug about-us. If you’re using the Timber Starter Theme you can simply create a file called page-about-us.twig inside your views and go crazy. I recommend copying-and-pasting the contents of page.twig into here so you have something to work from.

How does this work?

In the page.php file you’ll see this code…

<?php
Timber::render(array('page-' . $post->post_name . '.twig', 'page.twig'), $context);

Which is telling PHP to first look for a twig file named page-{{slug}}.twig and falling back to page.twig if that doesn’t exist.


Custom PHP File

If you need to do something special for this page in PHP, you can use standard WordPress template hierarchy to gather and manipulate data for this page. In the above example, you would create a file called /wp-content/themes/my-theme/page-about-us.php and populate it with the necessary PHP. Again, you can use the contents of the starter theme’s page.php file as a guide.


Custom Page Template

<?php
/*
 * Template Name: My Custom Page
 * Description: A Page Template with a darker design.
 */

// Code to display Page goes here...

In the WordPress admin, this will now display in your page’s list of available templates like so:

wordpress custom page template chooser

I recommend naming it something like /wp-content/themes/my-theme/template-my-custom-page.php. Do NOT name it something beginning with page- or WP will get very confused. Here’s an example of what the PHP in this file looks like.

Debugging

Using Twig’s native functions

Twig includes a dump function that can output the properties of an object. To use WP_DEBUG must be set to true.

wp-config.php
<?php
define('WP_DEBUG', true);
single.twig
{{dump(post)}}

Which will give you:

You can also dump everything sent to your template via:

{{dump()}}

This will give you something like:

<a href="http://imgur.com/5ZD8VDd"><img src="http://i.imgur.com/5ZD8VDd.png" title="Hosted by imgur.com"/></a>

Using Timber Debug Bar plugin

There’s a Timber add-on for the WordPress debug bar. Warning: this currently requries PHP 5.4. I’m working on fixing whatever’s going on for PHP 5.3

Using (Legacy) Timber Filters

You can also use some quick filters on an object. These are legacy and will be removed in favor of using Twig’s built-in functionality. However, these do not require that WP_DEBUG be turned on.

    {{post|print_r}}

Escapers

General Escapers

Twig offers a variety of escapers out of the box. These are intended to escape a string for safe insertion into the final output and there are multiple functions to conform to the strategy dependant on the context. In addition, Timber has added some valuable custom escapers for your WP theme. To use the escaper (see documentation link above) you use pipe your content through a function e if you want to use a custom escaper you would supply an argument to the function, e.g. e('wp_kses_post')

This all follows the WordPress (and greater development philosophy) to:

  1. Never trust user input.
  2. Escape as late as possible.
  3. Escape everything from untrusted sources (like databases and users), third-parties (like Twitter), etc.
  4. Never assume anything.
  5. Never trust user input.
  6. Sanitation is okay, but validation/rejection is better.
  7. Never trust user input.

Relevant Documentation

wp_kses_post

Background on KSES. KSES is a recursive acronym for KSES Kills Evil Scripts. It’s goal is to ensure only “allowed” HTML element names, attribute names and attribute values plus only sane HTML entities in the string. Allowed is based on a configuration.

This uses ths internal WordPress method that sanitize content for allowed HTML tags for post content. The configuration used can be found by running wp_kses_allowed_html( 'post' ); WordPress Documentation

Twig:

<p class="intro">{{post.post_content|e('wp_kses_post')}}</p>

In this example, post.post_content is:

<div foo="bar" src="bum">Foo</div><script>DoEvilThing();</script>

Output:

<div>Foo</div>DoEvilThing();


esc_url

Uses WordPress’ internal esc_url function on text. This should be used to sanitize URLs. WordPress Documentation

Twig:

<a href="{{ post.get_field('custom_link')|e('esc_url'); }}"></a>

Output

<a href="http://google.com"></a>


esc_html

Escaping for HTML blocks. It converts any potentially conflicting HTML entities to their encoded equivalent to prevent them from being rendered as markup by the browser, e.g. converts “<” to “<” and double quotes “ to ”$quot;“

This is for plain old text. If your content has HTML markup you should not use esc_html which will render the HTML as it looks in your code editor – to preserve the HTML you will want to use wp_kses_post

Twig:

<div class="equation">{{ post.get_field('equation')|e('esc_html') }}</div>

Output

<div class="equation">is x &lt; y?</div>


esc_js

Escapes text strings for echoing in JS. It is intended to be used for inline JS (in a tag attribute, for example onclick=”…”). Note that the strings have to be in single quotes. The filter ‘js_escape’ is also applied.

Twig:

<script>var bar = '{{ post.get_field('name') }}';</script>

Output

<script>var bar = 'Gabrielle';</script>

Extending Objects

Myth: Timber is for making simple themes. Fact: It’s for making incredibly complex themes look easy. But yes, you can also make simple sites from it.

The beauty of Timber is that the object-oriented nature lets you extend it to match the exact requirements of your theme.

Timber’s objects like TimberPost, TimberTerm, etc. are a great starting point to build your own subclass from. For example, on this project each post was a part of an “issue” of a magazine. I wanted an easy way to reference the issue in the twig file:

<h1>{{ post.title }}</h1>
<h3>From the {{ post.issue.title }} issue</h3>

Of course, TimberPost has no built-in concept of an issue (which I’ve built as a custom taxonomy called “issues”). So we’re going to extend TimberPost to give it one:

<?php
class MySitePost extends TimberPost {

    var $_issue;

    public function issue() {
        $issues = $this->get_terms('issues');
        if (is_array($issues) && count($issues)) {
            return $issues[0];
        }
    }
}

So now I’ve got an easy way to refer to the {{ post.issue }} in our twig templates. If you want to make this production-ready I recommend a bit of internal caching so that you don’t re-query every time you need to get the issue data:

<?php
class MySitePost extends TimberPost {

    var $_issue;

    public function issue() {
        if (!$this->_issue) {
            $issues = $this->get_terms('issues');
            if (is_array($issues) && count($issues)) {
                $this->_issue = $issues[0];
            }
        }
        return $this->_issue;
    }
}

Right now I’m in the midst of building a complex site for a hybrid foundation and publication. The posts on the site have some very specific requirements that requires a fair amount of logic. I can take the simple TimberPost object and extend it to make it work perfectly for this theme.

For example, I have a plugin that let’s people insert manually related posts, but if they don’t, WordPress will pull some automatically based on how the post is tagged.

    <?php
    class MySitePost extends TimberPost {

        function get_related_auto() {
            $tags = $this->tags();
            if (is_array($tags) && count($tags)) {
                $search_tag = $tags[0];
                $related = Timber::get_posts('tag_id='.$search_tag->ID);
                return $related;
            } else {
                //not tagged, cant do related on it
                return false;
            }
        }

        function get_related_manual() {
            if (isset($this->related_manual) && is_array($this->related_manual)){
                foreach($this->related_manual as &$related){
                    $related = new MySitePost($related);
                }
                return $this->related_manual;
            }
            return false;
        }

        function related($limit = 3) {
            $related = $this->get_related_manual();
            if (!$related){
                $related = $this->get_related_auto();
            }
            if (is_array($related)) {
                array_splice($related, 0, $limit);
            }
            return $related;
        }
    }

These can get pretty complex. And that’s the beauty. The complexity lives inside the context of the object, but very simple when it comes to your templates.

Adding to Twig

This is the correct formation for when you need to add custom functions, filters to twig:

<?php
/* functions.php */

add_filter('timber/twig', 'add_to_twig');

function add_to_twig($twig) {
    /* this is where you can add your own fuctions to twig */
    $twig->addExtension(new Twig_Extension_StringLoader());
    $twig->addFilter(new Twig_SimpleFilter('whatever', 'my_whatever'));
    return $twig;
}

function my_whatever($text) {
    $text .= ' or whatever';
    return $text;
}

This can now be called in your twig files with:

<h2>{{ post.title|whatever }}</h2>

Which will output:

<h2>Hello World! or whatever</h2>

Filters

General Filters

Twig offers a variety of filters to transform text and other information into the desired output. In addition, Timber has added some valuable custom filters for your WP theme:

excerpt

When you need to trim text to a desired length (in words)

Twig:
<p class="intro">{{post.post_content|excerpt(30)}}...</p>
Output:
<p class="intro">Steve-O was born in London, England. His mother, Donna Gay (née Wauthier), was Canadian, and his father, Richard Glover, was American. His paternal grandfather was English and his maternal step-grandfather ...</p>

function

Runs a function where you need. Really valuable for integrating plugins or existing themes

Twig:
<div class="entry-meta">{{function('twenty_ten_entry_meta')}}</div>
Output
<div class="entry-meta">Posted on September 6, 2013</div>

function (deprecated)

Runs a function where you need. Really valuable for integrating plugins or existing themes

Twig:
<div class="entry-meta">{{'twenty_ten_entry_meta'|function}}</div>
Output
<div class="entry-meta">Posted on September 6, 2013</div>

relative

Converts an absolute URL into a relative one, for example:

My custom link is <a href="{{ 'http://example.org/2015/08/my-blog-post' | relative }}">here!</a>
My custom link is <a href="/2015/08/my-blog-post">here!</a>

pretags

Converts tags like <span> into &lt;span&gt;, but only inside of <pre> tags. Great for code samples when you need to preserve other formatting in the non-code sample content.


sanitize

Converts Titles like this into titles-like-this

Twig:
{{post.title|sanitize}}
Output:
my-awesome-post

shortcodes

Runs text through WordPress’s shortcodes filter. In this example imagine that you’ve added a shortcode to a custom field like [gallery id="123" size="medium"]

Twig:
<section class="gallery">
{{post.custom_shortcode_field|shortcodes}}
</section>
Output
<section class="gallery">
Here is my gallery <div class="gallery" id="gallery-123"><img src="...." />...</div>
</section>

time_ago

Displays a date in timeago format:

Twig:
<p class="entry-meta">Posted: <time>{{post.post_date_gmt|time_ago}}</time></p>
Output:
<p class="entry-meta">Posted: <time>3 days ago</time></p>

truncate

Twig:
<p class="entry-meta">{{ post.character.origin_story | truncate(8) }} ...</p>
Output:
<p class="entry-meta">Bruce Wayne's parents were shot outside the opera ...</p>

wpautop

Adds paragraph breaks to new lines

Twig:
<div class="body">
    {{post.custom_text_area|wpautop}}
</div>
Output:
<div class="body">
    <p>Sinatra said, "What do you do?"</p>
    <p>"I'm a plumber," Ellison said.</p>
    <p>"No, no, he's not," another young man quickly yelled from across the table. "He wrote The Oscar."</p>
    <p>"Oh, yeah," Sinatra said, "well I've seen it, and it's a piece of crap."</p>
</div>

list

Converts an array of strings into a comma-separated list.

PHP:
<?php
$context['contributors'] = array('Blake Allen','Rachel White','Maddy May');
Twig:
Contributions made by {{contributors|list(',','&')}}
Output:
Contributions made by Blake Allen, Rachel White & Maddy May

Internationalization

Internationalization of a Timber theme works pretty much the same way as it does for default WordPress themes. Follow the guide in the WordPress Theme Handbook to setup i18n for your theme.

Twig has its own i18n extension that gives you {% trans %} tags to define translatable blocks, but there’s no need to use it, because with Timber, you have all you need.

Translation functions

Timber supports all the translation functions used in WordPress:

The functions _e() and _ex() are also supported, but you probably won’t need them in Twig, because {{ }} already echoes the output.

WordPress:

<p class="entry-meta"><?php _e( 'Posted on', 'my-text-domain' ) ?> [...]</p>

Timber:

<p class="entry-meta">{{ __('Posted on', 'my-text-domain') }} [...]</p>

sprintf notation

You can use sprintf-type placeholders, using the format filter:

WordPress:

<p class="entry-meta"><?php printf( __('Posted on %s', 'my-text-domain'), $posted_on_date ) ?></p>

Timber:

<p class="entry-meta">{{ __('Posted on %s', 'my-text-domain')|format(posted_on_date) }}</p>

If you want to use the sprintf function in Twig, you have to add it yourself using function_wrapper.

Generating localization files

To generate .pot, .po and .mo files, you need a tool that supports parsing Twig files to detect all your translations. While there are a lot of tools that can parse PHP files, the solution that works best for Twig files is Poedit.

Generating l10n files with Poedit 2

Poedit 2 fully supports Twig file parsing (Pro version only) with the following functions: __(), _x(), _n(), _nx().

Generating l10n files with Poedit 1.x

Internationalization functions in Twig files are not automatically parsed by gettext in Poedit 1.x. The quick and dirty workaround is to start each .twig file with {#<?php#}. By doing this, gettext will interpret whatever comes next as PHP, and start looking for __.


Another solution is Twig-Gettext-Extractor, a special Twig parser for Poedit. The linked page contains instructions on how to set it up.


Alternatively, you can use a custom parser for Python instead. This will throw a warning or two, but your strings are extracted! To add the parser, follow these steps:

  1. Create a Poedit project for your theme if you haven’t already, and make sure to add __ on the Sources keywords tab.
  2. Go to Edit > Preferences.
  3. On the Parsers tab, add a new parser with these settings:
    • Language: Timber
    • List of extensions: *.twig
    • Parser command: xgettext --language=Python --add-comments=TRANSLATORS --force-po -o %o %C %K %F
    • An item in keyword list: -k%k
    • An item in input files list: %f
    • Source code charset: --from-code=%c
  4. Save and Update!

Be aware that with the Python parser, strings inside HTML attributes will not be recognized. This will not work:

<nav aria-label="{{ __('Main Menu', 'my-text-domain') }}">

As a workaround, you can assign the translation to a variable, which you can then use in the attribute.

{% set nav_aria_label = __('Main Menu', 'my-text-domain') %}
<nav aria-label="{{ nav_aria_label }}">

Pagination

Do you like pagination? Stupid question, of course you do. Well, here’s how you do it.

This will only work in a php file with an active query (like archive.php or home.php):

    <?php
    $context = Timber::get_context();
    $context['posts'] = new PostQuery();
    Timber::render('archive.twig', $context);

You can then markup the output like so (of course, the exact markup is up to YOU):

<div class="tool-pagination">
    {% if posts.pagination.prev %}
        <a href="{{posts.pagination.prev.link}}" class="prev {{posts.pagination.prev.link|length ? '' : 'invisible'}}">Prev</a>
    {% endif %}
    <ul class="pages">
        {% for page in posts.pagination.pages %}
            <li>
                {% if page.link %}
                    <a href="{{page.link}}" class="{{page.class}}">{{page.title}}</a>
                {% else %}
                    <span class="{{page.class}}">{{page.title}}</span>
                {% endif %}
            </li>
        {% endfor %}
    </ul>
    {% if posts.pagination.next %}
        <a href="{{posts.pagination.next.link}}" class="next {{posts.pagination.next.link|length ? '' : 'invisible'}}">Next</a>
    {% endif %}
</div>

What if I’m not using the default query?

No problem!

    <?php
    global $paged;
    if (!isset($paged) || !$paged){
        $paged = 1;
    }
    $context = Timber::get_context();
    $args = array(
        'post_type' => 'event',
        'posts_per_page' => 5,
        'paged' => $paged
    );
    $context['posts'] = new Timber\PostQuery($args);
    Timber::render('page-events.twig', $context);

The pre_get_posts Way

Custom query_posts sometimes shows 404 on example.com/page/2. In that case you can also use pre_get_posts in your functions.php file:

    <?php
    function my_home_query( $query ) {
      if ( $query->is_main_query() && !is_admin() ) {
        $query->set( 'post_type', array( 'movie', 'post' ));
      }
    }
    add_action( 'pre_get_posts', 'my_home_query' );

In your archive.php or home.php template:

    <?php
    $context = Timber::get_context();
    $context['posts'] = new Timber\PostQuery();
    Timber::render('archive.twig', $context);

Performance

Timber, especially in conjunction with WordPress and Twig, offers a variety of caching strategies to optimize performance. Here’s a quick rundown of some of the options, ranked in order of most-broad to most-focused.

tl;dr

In my tests with Debug Bar, Timber has no measurable performance hit. Everything compiles to PHP. @fabpot has an overview of the performance costs on his blog (scroll down to the table)

You can…


Cache Everything

You can still use plugins like W3 Total Cache in conjunction with Timber. In most settings, this will skip the Twig/Timber layer of your files and serve static pages via whatever mechanism the plugin or settings dictate.


Cache the Entire Twig File and Data

When rendering, use the $expires argument in Timber::render. For example:

<?php
$data['posts'] = Timber::get_posts();
Timber::render('index.twig', $data, 600);

Timber will cache the template for 10 minutes (600 / 60 = 10). But here’s the cool part. Timber hashes the fields in the view context. This means that as soon as the data changes, the cache is automatically invalidated (yay!).

Full Parameters:

<?php
Timber::render(
    $filenames,
    $data,
    $expires, /** Default: false. False disables cache altogether. When passed an array, the first value is used for non-logged in visitors, the second for users **/
    $cache_mode /** Any of the cache mode constants defined in TimberLoader **/
);

The cache modes are:

<?php
TimberLoader::CACHE_NONE /** Disable caching **/
TimberLoader::CACHE_OBJECT /** WP Object Cache **/
TimberLoader::CACHE_TRANSIENT  /** Transients **/
TimberLoader::CACHE_SITE_TRANSIENT /** Network wide transients **/
TimberLoader::CACHE_USE_DEFAULT /** Use whatever caching mechanism is set as the default for TimberLoader, the default is transient **/

This method is very effective, but crude - the whole template is cached. So if you have any context dependent sub-views (eg. current user), this mode won’t do.


Cache Parts of the Twig File and Data

This method implements the Twig Cache Extension. It adds the cache tag, for use in templates. Best shown by example:

{% cache 'index/content' posts %}
    {% for post in posts %}
        {% include ['tease-'~post.post_type~'.twig', 'tease.twig'] %}
    {% endfor %}
{% endcache %}

'index/content' will be used as annotation (ie. label) for the cache, while posts will be encoded with all its public fields. You can use anything for the label (“foo”, “elephant”, “single-posts”, whatever).

The mechanism behind it is the same as with render - the cache key is determined based on a hash of the object/array passed in (in the above example posts).

The cache method used is always the default mode, set using the bespoke filter (by default, transient cache).

This method allows for very fine-grained control over what parts of templates are being cached and which are not. When applied on resource-intensive sections, the performance difference is huge.

In your cache, the eventual key will be:

<?php
$annotation . '__GCS__' . $key

That is in this scenario:

<?php
'index/content__GCS__' . md5( json_encode( $context['post'] ) )
Extra: TimberKeyGeneratorInterface

Instead of hashing a whole object, you can specify the cache key in the object itself. If the object implements TimberKeyGeneratorInterface, it can pass a unique key through the method get_cache_key. That way a class can for example simply pass last_updated as the unique key. If arrays contain the key _cache_key, that one is used as cache key.

This may save yet another few processor cycles.


Cache the Twig File (but not the data)

Every time you render a .twig file, Twig compiles all the html tags and variables into a big, nasty blob of function calls and echo statements that actually gets run by PHP. In general, this is pretty efficient. However, you can cache the resulting PHP blob by turning on Twig’s cache via:

<?php
/* functions.php */
if (class_exists('Timber')){
    Timber::$cache = true;
}

You can look in your your /wp-content/plugins/timber/twig-cache directory to see what these files look like.

This does not cache the contents of the variables. This is recommended as a last-step in the production process. Once enabled, any change you make to a .twig file (just tweaking the html for example) will not go live until the cache is flushed.


Cache the PHP data

Sometimes the most expensive parts of the operations are generating the data needed to populate the twig template. You can of course use WordPress’s default Transient API to store this data.

You can also use some syntactic sugar to make the checking/saving/retrieving of transient data a bit easier:

<?php
/* home.php */

$context = Timber::get_context();
$context['main_stories'] = TimberHelper::transient('main_stories', function(){
    $posts = Timber::get_posts();
    //do something expensive with these posts
    $extra_teases = get_field('my_extra_teases', 'options');
    foreach($extra_teases as &$tease){
        $tease = new TimberPost($tease);
    }
    $main_stories = array();
    $main_stories['posts'] = $posts;
    $main_stories['extra_teases'] = $extra_teases;
    return $main_stories;
}, 600);
Timber::render('home.twig', $context);

Here main_stories is a totally made-up variable. It could be called foo, bar, elephant, etc.


Measuring Performance

Some tools like Debug Bar may not properly measure performance because its data (as in, the actual HTML it’s generating to tell you the timing, number of queries, etc.) is swept-up by the page’s cache.

Timber provides some quick shortcuts to measure page timing. Here’s an example of them in action:

<?php
/* single.php */
$start = TimberHelper::start_timer(); //this generates a starting time
$context = Timber::get_context();
$context['post'] = new TimberPost();
$context['whatever'] = get_my_foo();
Timber::render('single.twig', $context, 600);
echo TimberHelper::stop_timer($start); //this reports the time diff by passing the $start time

Routing

Among its other special powers, Timber includes modern routing in the Express.js/Ruby on Rails mold, making it easy for you to implement custom pagination–and anything else you might imagine in your wildest dreams of URLs and parameters. OMG so easy!

Some examples

In your functions.php file, this can be called anywhere (don’t hook it to init or another action or it might be called too late)

<?php
Routes::map('blog/:name', function($params){
    $query = 'posts_per_page=3&post_type='.$params['name'];
    Routes::load('archive.php', null, $query, 200);
});

Routes::map('blog/:name/page/:pg', function($params){
    $query = 'posts_per_page=3&post_type='.$params['name'].'&paged='.$params['pg'];
    $params = array('thing' => 'foo', 'bar' => 'I dont even know');
    Routes::load('archive.php', $params, $query);
});

map

Routes::map($pattern, $callback)
Usage:

A functions.php where I want to display custom paginated content:

<?php
Routes::map('info/:name/page/:pg', function($params){
    //make a custom query based on incoming path and run it...
    $query = 'posts_per_page=3&post_type='.$params['name'].'&paged='.intval($params['pg']);

    //load up a template which will use that query
    Routes::load('archive.php', null, $query);
});
Arguments:

$pattern (required) Set a pattern for Timber to match on, by default everything is handled as a string. Any segment that begins with a : is handled as a variable, for example:

To paginate: page/:pagenum

To edit a user: my-users/:userid/edit

$callback A function that should fire when the pattern matches the request. Callback takes one argument which is an array of the parameters passed in the URL.

So in this example: 'info/:name/page/:pg', $params would have data for: * $data['name'] * $data['pg']

… which you can use in the callback function as a part of your query


load

Routes::load($php_file, $args, $query = null, $status_code = 200)
Arguments:

$php_file (required) A PHP file to load, in my experience this is usually your archive.php or a generic listing page (but don’t worry it can be anything!)

$template_params Any data you want to send to the resulting view. Example:

<?php
/* functions.php */

Routes::map('info/:name/page/:pg', function($params){
    //make a custom query based on incoming path and run it...
    $query = 'posts_per_page=3&post_type='.$params['name'].'&paged='.intval($params['pg']);

    //load up a template which will use that query
    $params = array();
    $params['my_title'] = 'This is my custom title';
    Routes::load('archive.php', $params, $query, 200);
});
<?php
/* archive.php */

global $params;
$context['wp_title'] = $params['my_title']; // "This is my custom title"
/* the rest as normal... */
Timber::render('archive.twig', $context)

$query The query you want to use, it can accept a string or array just like Timber::get_posts – use the standard WP_Query syntax (or a WP_Query object too)

$status_code Send an optional status code. Defaults to 200 for ‘Success/OK’

Sidebars

So you want a sidebar?

Method 1: PHP file

Let’s say every page on the site has the same content going into its sidebar. If so, you would: Create a sidebar.php file in your theme directory (so wp-content/themes/mytheme/sidebar.php)

<?php
/* sidebar.php */
$context = array();
$context['widget'] = my_function_to_get_widget();
$context['ad'] = my_function_to_get_an_ad();
Timber::render('sidebar.twig', $context);
<?php
/* single.php */
$context = Timber::get_context();
$context['sidebar'] = Timber::get_sidebar('sidebar.php');
Timber::render('single.twig', $context);
{# single.twig #}
<aside class="sidebar">
    {{sidebar}}
</aside>

Method 2: Twig file

In this example, you would populate your sidebar from your main PHP file (home.php, single.php, archive.php, etc).

{# views/sidebar-related.twig #}
<h3>Related Stories</h3>
{% for post in related %}
    <h4><a href="{{post.get_path}}">{{post.post_title}}</a></h4>
{% endfor %}
<?php
/* single.php */
$context = Timber::get_context();
$post = new TimberPost();
$post_cat = $post->get_terms('category');
$post_cat = $post_cat[0]->ID;
$context['post'] = $post;

$sidebar_context = array();
$sidebar_context['related'] = Timber::get_posts('cat='.$post_cat);
$context['sidebar'] = Timber::get_sidebar('sidebar-related.twig', $sidebar_context);
Timber::render('single.twig', $context);
{# single.twig #}
<aside class="sidebar">
    {{sidebar}}
</aside>

Method 3: Dynamic

This is using WordPress’s built-in dynamic_sidebar tools (which, confusingly, are referred to as “Widgets” in the interface). Since sidebar is already used; I used widgets in the code to describe these:

<?php
$context = array();
$context['dynamic_sidebar'] = Timber::get_widgets('dynamic_sidebar');
Timber::render('sidebar.twig', $context);
<aside class="my-sidebar">
{{dynamic_sidebar}}
</aside>

Template Locations

You can set arbitrary locations for your twig files with…

<?php
/* functions.php */
Timber::$locations = '/Users/jared/Sandbox/templates';

Use the full file path to make sure Timber knows what you’re trying to draw from. You can also send an array for multiple locations:

<?php
/* functions.php */
Timber::$locations = array( '/Users/jared/Sandbox/templates',
                            '~/Sites/timber-templates/',
                            ABSPATH.'/wp-content/templates'
                        );

You only need to do this once in your project (like in functions.php) then when you call from a PHP file (say single.php) Timber will look for twig files in these locations before the child/parent theme.


Changing the default folder for .twig files

By default, Timber looks in your child and parent theme’s views directory to pull .twig files. If you don’t like the default views directory (which by default resides in your theme folder) you can change that to. Example: I want to use /wp-content/themes/my-theme/twigs:

Configure with a string:
<?php
/* functions.php */
Timber::$dirname = 'twigs';
You can also send an array with fallbacks:
<?php
/* functions.php */
Timber::$dirname = array('templates', 'templates/shared/mods', 'twigs', 'views');

A quick note on subdirectories: you can always reference these relatively. For example:

<?php
Timber::render('shared/headers/header-home.twig');

… might correspond to a file in /wp-content/themes/my-theme/views/shared/headers/header-home.twig.

Testing

PHPUnit

To setup tests

Full code
cd ~/Sites
git clone git@github.com:Varying-Vagrant-Vagrants/VVV.git
cd VVV/www
git clone git@github.com:timber/timber.git
vagrant ssh
cd /srv/www/timber
composer install
phpunit
Gotchas!
sudo /bin/dd if=/dev/zero of=/var/swap.1 bs=1M count=1024
sudo /sbin/mkswap /var/swap.1
sudo /sbin/swapon /var/swap.1

WooCommerce

Point of entry - main WooCommerce PHP file

The first step to get your WooCommerce project integrated with Timber is creating a file named woocommerce.php in the root of your theme. That will establish the context and data to be passed to your twig files:

<?php

if (!class_exists('Timber')){
    echo 'Timber not activated. Make sure you activate the plugin in <a href="/wp-admin/plugins.php#timber">/wp-admin/plugins.php</a>';
    return;
}

$context            = Timber::get_context();
$context['sidebar'] = Timber::get_widgets('shop-sidebar');

if (is_singular('product')) {

    $context['post']    = Timber::get_post();
    $product            = get_product( $context['post']->ID );
    $context['product'] = $product;

    Timber::render('views/woo/single-product.twig', $context);

} else {

    $posts = Timber::get_posts();
    $context['products'] = $posts;

    if ( is_product_category() ) {
        $queried_object = get_queried_object();
        $term_id = $queried_object->term_id;
        $context['category'] = get_term( $term_id, 'product_cat' );
        $context['title'] = single_term_title('', false);
    }

    Timber::render('views/woo/archive.twig', $context);

}

You will now need the two twig files loaded from woocommerce.php:

Archives

Create a Twig file accordingly to the location asked by the above file, in this example that would be views/woo/archive.twig:

{% extends "base.twig" %}

{% block content %}

    {% do action('woocommerce_before_main_content') %}

    <div class="before-shop-loop">
        {% do action( 'woocommerce_before_shop_loop') %}
    </div>

    <div class="loop">
        {% for post in products %}

            {% include ["partials/tease-product.twig"] %}

        {% endfor %}
    </div>

    {% do action('woocommerce_after_shop_loop') %}
    {% do action('woocommerce_after_main_content') %}

{% endblock  %}

You’ll notice the inclusion of several woocommerce’s default hooks, which you’ll need to keep the integration seamless and allow any WooCommerce extension plugin to still work.

Next, we’ll take care of the single product view.

Single Product

Create a Twig file accordingly to the location asked by the above file, in this example that would be views/woo/single-product.twig:

{% extends "base.twig" %}

{% block content %}

    {% do action('woocommerce_before_single_product') %}

    <article itemscope itemtype="http://schema.org/Product" class="single-product-details {{post.class}}">

        <div class="entry-images">
            {% do action('woocommerce_before_single_product_summary') %}
            <img src="{{ post.thumbnail.src('shop_single') }}" />
        </div>

        <div class="summary entry-summary">
            {% do action('woocommerce_single_product_summary') %}
        </div>

        {% do action('woocommerce_after_single_product_summary') %}

        <meta itemprop="url" content="{{ post.link }}" />

    </article>

    {% do action('woocommerce_after_single_product') %}

{% endblock  %}

Again we are keep things simple by using WooCommerce’s default hooks. If you need to override the output of any of those hooks, my advice would be to remove and add the relevant actions using PHP, keeping your upgrade path simple.

Finally, we’ll need to create a teaser file for product in the loops. Considering the code above that would be views/partials/tease-product.twig:

Tease Product


<article {{ fn('post_class', ['$classes', 'entry'] ) }}>

    {{ fn('timber_set_product', post)}}

    <div class="Media">

        {% if showthumb %}
            <div class="Media-figure {% if not post.thumbnail %}placeholder{% endif %}">
                <a href="{{post.link}}">
                    {% if post.thumbnail %}
                        <img src="{{ post.thumbnail.src|resize(post_thumb_size[0], post_thumb_size[1]) }}" />
                    {% else %}
                        <span class="thumb-placeholder"><i class="icon-camera"></i></span>
                    {% endif %}
                </a>
            </div>
        {% endif %}

        <div class="Media-content">
            {% do action('woocommerce_before_shop_loop_item_title') %}
            {% if post.title %}
                <h3 class="entry-title"><a href="{{post.link}}">{{ post.title }}</a></h3>
            {% else %}
                <h3 class="entry-title"><a href="{{post.link}}">{{ fn('the_title') }}</a></h3>
            {% endif %}
            {% do action( 'woocommerce_after_shop_loop_item_title' ) %}
            {% do action( 'woocommerce_after_shop_loop_item' ) %}
        </div>

    </div>

</article>

This should all sound familiar by now, except for one line:

{{ fn('timber_set_product', post)}}

For some reason products in the loop don’t get the right context by default. This line will call the following function that you need to add somewhere in your functions.php file:

<?php
function timber_set_product( $post ) {
    global $product;
    if ( is_woocommerce() ) {
        $product = get_product($post->ID);
    }
}

Without this, some elements of the listed products would show the same information as the first product in the loop.

Note: Some users reported issues with the loop context even when using the timber_set_product helper function. Turns out the default WooCommerce hooks interfere with the output of the aforementioned function.

One way to get around this is by building your own image calls, that means removing WooCommerce’s default hooks and declare on your template the html to show the image:

{% if post.thumbnail %}
   <img src="{{ post.thumbnail.src|resize(shop_thumbnail_image_size) }}" />
{% endif %}

To remove the default image, add the following to your functions.php file:

<?php
remove_action('woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail');

This comes with the added benefit that you’ll have total control over where your image is created in the markup.

WordPress Integration

Timber plays nice with your existing WordPress setup. You can still use other plugins, etc. Here’s a rundown of the key points:


the_content

You’re probably used to calling the_content() in your theme file. This is good. Before outputting, WordPress will run all the filters and actions that your plugins and themes are using. If you want to get this into your new Timber theme (and you probably do). Call it like this:

<div class="my-article">
   {{post.content}}
</div>

This differs from {{post.post_content}} which is the raw text stored in your database


Hooks

Timber hooks to interact with WordPress use this/style/of_hooks instead of this_style_of_hooks. This matches the same methodology as Advanced Custom Fields.

Full documentation to come


Scripts + Stylesheets

What happened to wp_head() and wp_footer()? Don’t worry, they haven’t gone away. In fact, they have a home now in the Timber::get_context() object. When you setup your PHP file, you should do something like this:

<?php
/* single.php */
$data = Timber::get_context();
$data['post'] = new TimberPost();
Timber::render('single.twig', $data);

Now in the corresponding Twig file:

{# single.twig #}
<html>
    <head>
    <!-- Add whatever you need in the head, and then...-->
    {{wp_head}}
    </head>

    <!-- etc... -->

    <footer>
        Copyright &copy; {{"now"|date('Y')}}
    </footer>
    {{wp_footer}}
    </body>
</html>

WordPress will inject whatever output had been loaded into wp_head() and wp_footer() through these variables.


Functions

But my theme/plugin has some functions I need! Do I really have to re-write all of them?

No.

Let’s say you modified twentyeleven and need some of the functions back. Here’s the quick-and-dirty way:

<div class="posted-on">{{function("twentyeleven_posted_on")}}</div>

Oh. That’s not so bad. What if there are arguments? Easy:

{# single.twig #}
<div class="admin-tools">
    {{function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>')}}
</div>

Nice! Any gotchas? Unfortunately yes. While the above example will totally work in a single.twig file it will not in a loop. Why? Single.twig/single.php retain the context of the current post. Thus for a function like edit_post_link (which will try to guess the ID# of the post you want to edit, based on the current post in the loop), the same function requires some modification in a file like archive.twig or index.twig. There, you will need to explicitly set the post ID:

{# index.twig #}
<div class="admin-tools">
    {{function('edit_post_link', 'Edit', '<span class="edit-link">', '</span>', post.ID)}}
</div>

You can also use fn('my_function') as an alias.

For a less quick-and-dirty way, you can use the TimberFunctionWrapper. This class sets up your PHP functions as functions you can use in your Twig templates. It will execute them only when you actually call them in your template. You can quickly set up a TimberFunctionWrapper using TimberHelper:

<?php
/**
 * @param string $function_name
 * @param array (optional) $defaults
 * @param bool (optional) $return_output_buffer Return function output instead of return value (default: false)
 * @return \TimberFunctionWrapper
 */
TimberHelper::function_wrapper( $function_name, $defaults = array(), $return_output_buffer = false );

So if you want to add edit_post_link to your context, you can do something like this:

<?php
/* single.php */
$data = Timber::get_context();
$data['post'] = new TimberPost();
$data['edit_post_link'] = TimberHelper::function_wrapper( 'edit_post_link', array( __( 'Edit' ), '<span class="edit-link">', '</span>' ) );
Timber::render('single.twig', $data);

Now you can use it like a ‘normal’ function:

{# single.twig #}
<div class="admin-tools">
    {{edit_post_link}}
</div>
{# Calls edit_post_link using default arguments #}

{# single-my-post-type.twig #}
<div class="admin-tools">
    {{edit_post_link(null, '<span class="edit-my-post-type-link">')}}
</div>
{# Calls edit_post_link with all defaults, except for second argument #}

Actions

Call them in your Twig template…

{% do action('my_action') %}
{% do action('my_action_with_args', 'foo', 'bar') %}

… in your functions.php file:

<?php
add_action('my_action', 'my_function');

function my_function($context){
    //$context stores the template context in case you need to reference it
    echo $context['post']->post_title; //outputs title of yr post
}
<?php
add_action('my_action_with_args', 'my_function_with_args', 10, 2);

function my_function_with_args($foo, $bar){
    echo 'I say '.$foo.' and '.$bar;
}

You can still get the context object when passing args, it’s always the last argument…

<?php
add_action('my_action_with_args', 'my_function_with_args', 10, 3);

function my_function_with_args($foo, $bar, $context){
    echo 'I say '.$foo.' and '.$bar;
    echo 'For the post with title '.$context['post']->post_title;
}

Please note the argument count that WordPress requires for add_action


Filters

{{ post.content|apply_filters('my_filter') }}
{{ "my custom string"|apply_filters('my_filter',param1,param2,...) }}

Widgets

Everyone loves widgets! Of course they do…

<?php
$data['footer_widgets'] = Timber::get_widgets('footer_widgets');

…where 'footer_widgets’ is the registered name of the widgets you want to get(in twentythirteen these are called sidebar-1 and sidebar-2 )

Then use it in your template:

{# base.twig #}
<footer>
    {{footer_widgets}}
</footer>

Using Timber inside your own widgets

You can also use twig templates for your widgets! Let’s imagine we want a widget that shows a random number each time it is rendered.

Inside the widget class, the widget function is used to show the widget:

<?php
public function widget($args, $instance) {
    $number = rand();
    Timber::render('random-widget.twig', array('args' => $args, 'instance' => $instance, 'number' => $number));
}

The corresponding template file random-widget.twig looks like this:

{{ args.before_widget | raw }}
{{ args.before_title | raw }}{{ instance.title | apply_filters('widget_title') }}{{ args.after_title | raw }}

<p>Your magic number is: <strong>{{ number }}</strong></p>

{{ args.after_widget | raw }}

The raw filter is needed here to embed the widget properly.

You may also want to check if the Timber plugin was loaded before using it:

<?php
public function widget($args, $instance) {
    if (!class_exists('Timber')) {
        // if you want to show some error message, this is the right place
        return;
    }
    $number = rand();
    Timber::render('random-widget.twig', array('args' => $args, 'instance' => $instance, 'number' => $number));
}

Shortcodes

Well, if it works for widgets, why shouldn’t it work for shortcodes ? Of course it does !

Let’s implement a [youtube] shorttag which embeds a youtube video. For the desired usage of [youtube id=xxxx] we only need a few lines of code:

<?php
// should be called from within an init action hook
add_shortcode('youtube', 'youtube_shorttag');

function youtube_shorttag($atts) {
    if(isset($atts['id'])) {
        $id = sanitize_text_field($atts['id']);
    } else {
        $id = false;
    }
    // this time we use Timber::compile since shorttags should return the code
    return Timber::compile('youtube-short.twig', array('id' => $id));
}

In youtube-short.twig we have the following template:

{% if id %}
<iframe width="560" height="315" src="//www.youtube.com/embed/{{ id }}" frameborder="0" allowfullscreen></iframe>
{% endif %}

Now, when the YouTube embed code changes, we only need to edit the youtube-short.twig template. No need to search your PHP files for this one particular line.

Layouts with Shortcodes

Timber and Twig can process your shortcodes by using the {% filter shortcodes %} tag. Let’s say you’re using a [tab] shortcode, for example:

{% filter shortcodes %}
    [tabs tab1="Tab 1 title" tab2="Tab 2 title" layout="horizontal" backgroundcolor="" inactivecolor=""]
        [tab id=1]
            Something something something
        [/tab]

        [tab id=2]
            Tab 2 content here
        [/tab]
    [/tabs]
{% endfilter %}

Object Reference

Timber\Timber

Timber Class. Main class called Timber for this plugin.

PHP
<?php
 $posts = Timber::get_posts();
 $posts = Timber::get_posts('post_type = article')
 $posts = Timber::get_posts(array('post_type' => 'article', 'category_name' => 'sports')); // uses wp_query format.
 $posts = Timber::get_posts(array(23,24,35,67), 'InkwellArticle');
 $context = Timber::get_context(); // returns wp favorites!
 $context['posts'] = $posts;
 Timber::render('index.twig', $context);
Name Type Description
compile bool/string
compile_string bool/string
fetch bool/string
get_context array
get_pagination array mixed
get_post array/bool/null
get_posts array/bool/null
get_sidebar bool/string
get_sidebar_from_php string
get_sites array
get_term \Timber\Timber\Term/\Timber\WP_Error/null
get_terms mixed
get_widgets \Timber\TimberFunctionWrapper
query_post array/bool/null
query_posts \Timber\PostCollection
render boolean/string
render_string bool/string

__construct

__construct( )

returns: void

add_route

DEPRECATED since 0.20.0 and will be removed in 1.1

add_route( mixed $route, \Timber\callable $callback, array $args=array() )

returns: void

Add route.

Name Type Description
$route mixed
$callback \Timber\callable
$args array

compile

compile( mixed $filenames, array $data=array(), bool $expires=false, string $cache_mode="default", bool $via_render=false )

returns: bool/string

Compile function.

Name Type Description
$filenames mixed
$data array
$expires bool
$cache_mode string
$via_render bool

compile_string

compile_string( mixed $string, array $data=array() )

returns: bool/string

Compile string.

Name Type Description
$string mixed
$data array

fetch

fetch( mixed $filenames, array $data=array(), bool $expires=false, string $cache_mode="default" )

returns: bool/string

Fetch function.

Name Type Description
$filenames mixed
$data array
$expires bool
$cache_mode string

get_context

get_context( )

returns: array

Get context.

get_pagination

get_pagination( array $prefs=array() )

returns: array mixed

Get pagination.

Name Type Description
$prefs array

get_post

get_post( bool $query=false, string $PostClass="Timber\Post" )

returns: array/bool/null

Get post.

Name Type Description
$query bool
$PostClass string

get_posts

get_posts( bool $query=false, string $PostClass="Timber\Post", bool $return_collection=false )

returns: array/bool/null

Get posts.

Name Type Description
$query bool
$PostClass string
$return_collection bool
PHP
<?php
    $posts = Timber::get_posts();
     $posts = Timber::get_posts('post_type = article')
     $posts = Timber::get_posts(array('post_type' => 'article', 'category_name' => 'sports')); // uses wp_query format.
     $posts = Timber::get_posts('post_type=any', array('portfolio' => 'MyPortfolioClass', 'alert' => 'MyAlertClass')); //use a classmap for the $PostClass

get_sidebar

get_sidebar( string $sidebar="sidebar.php", array $data=array() )

returns: bool/string

Get sidebar.

Name Type Description
$sidebar string
$data array

get_sidebar_from_php

get_sidebar_from_php( string $sidebar="", mixed $data )

returns: string

Get sidebar from PHP

Name Type Description
$sidebar string
$data mixed

get_sites

get_sites( bool/array/bool $blog_ids=false )

returns: array

Get sites.

Name Type Description
$blog_ids bool/array/bool

get_term

get_term( int/\Timber\WP_Term/object $term, string $taxonomy="post_tag", string $TermClass="Timber\Term" )

returns: \Timber\Timber\Term/\Timber\WP_Error/null

Get term.

Name Type Description
$term int/\Timber\WP_Term/object
$taxonomy string
$TermClass string

get_terms

get_terms( mixed/string/array $args=null, array $maybe_args=array(), string $TermClass="Timber\Term" )

returns: mixed

Get terms.

Name Type Description
$args mixed/string/array
$maybe_args array
$TermClass string

get_widgets

get_widgets( mixed $widget_id )

returns: \Timber\TimberFunctionWrapper

Get widgets.

Name Type Description
$widget_id mixed

init_constants

init_constants( )

returns: void

query_post

query_post( bool $query=false, string $PostClass="Timber\Post" )

returns: array/bool/null

Query post.

Name Type Description
$query bool
$PostClass string

query_posts

query_posts( bool $query=false, string $PostClass="Timber\Post" )

returns: \Timber\PostCollection

Query posts.

Name Type Description
$query bool
$PostClass string

render

render( mixed $filenames, array $data=array(), bool $expires=false, string $cache_mode="default" )

returns: boolean/string

Render function.

Name Type Description
$filenames mixed
$data array
$expires bool
$cache_mode string

render_string

render_string( mixed $string, array $data=array() )

returns: bool/string

Render string.

Name Type Description
$string mixed
$data array

init

init( )

returns: void

test_compatibility

test_compatibility( )

returns: void

Tests whether we can use Timber

Timber\Archives

The TimberArchives class is used to generate a menu based on the date archives of your posts. The Nieman Foundation News site has an example of how the output can be used in a real site (screenshot).

PHP
<?php
$context['archives'] = new TimberArchives( $args );
Twig
<ul>
{% for item in archives.items %}
    <li><a href="{{item.link}}">{{item.name}}</a></li>
    {% for child in item.children %}
        <li class="child"><a href="{{child.link}}">{{child.name}}</a></li>
    {% endfor %}
{% endfor %}
</ul>
HTML
<ul>
    <li>2015</li>
    <li class="child">May</li>
    <li class="child">April</li>
    <li class="child">March</li>
    <li class="child">February</li>
    <li class="child">January</li>
    <li>2014</li>
    <li class="child">December</li>
    <li class="child">November</li>
    <li class="child">October</li>
</ul>
Name Type Description
__construct void
get_items array/string
items array the items of the archives to iterate through and markup for your page

__construct

__construct( mixed $args=null, string $base="" )

returns: void

Name Type Description
$args mixed array of arguments {
$base string any additional paths that need to be prepended to the URLs that are generated, for example: “tags”

get_items

get_items( mixed/array/string $args=null )

returns: array/string

Name Type Description
$args mixed/array/string

This class extends \Timber\Core

Timber\Comment

The TimberComment class is used to view the output of comments. 99% of the time this will be in the context of the comments on a post. However you can also fetch a comment directly using its comment ID.

PHP
<?php
$comment = new TimberComment($comment_id);
$context['comment_of_the_day'] = $comment;
Timber::render('index.twig', $context);
Twig
<p class="comment">{{comment_of_the_day.content}}</p>
<p class="comment-attribution">- {{comment.author.name}}</p>
HTML
<p class="comment">But, O Sarah! If the dead can come back to this earth and flit unseen around those they loved, I shall always be near you; in the garish day and in the darkest night -- amidst your happiest scenes and gloomiest hours - always, always; and if there be a soft breeze upon your cheek, it shall be my breath; or the cool air fans your throbbing temple, it shall be my spirit passing by.</p>
<p class="comment-attribution">- Sullivan Ballou</p>
Name Type Description
approved boolean
author \Timber\User
avatar bool/mixed/string
children array Comments
content string
date string
is_child bool
reply_link string
time string

__construct

__construct( int $cid )

returns: void

Name Type Description
$cid int

__toString

__toString( )

returns: void

add_child

add_child( \Timber\Comment $child_comment )

returns: void

Name Type Description
$child_comment \Timber\Comment

approved

approved( )

returns: boolean

Twig
    {% if comment.approved %}
        Your comment is good
    {% else %}
        Do you kiss your mother with that mouth?
    {% endif %}

author

author( )

returns: \Timber\User

Twig
    <h3>Comments by...</h3>
    <ol>
    {% for comment in post.comments %}
        <li>{{comment.author.name}}, who is a {{comment.author.role}}</li>
    {% endfor %}
    </ol>
HTML
    <h3>Comments by...</h3>
    <ol>
        <li>Jared Novack, who is a contributor</li>
        <li>Katie Ricci, who is a subscriber</li>
        <li>Rebecca Pearl, who is a author</li>
    </ol>

avatar

avatar( mixed/int $size=92, string $default="" )

returns: bool/mixed/string

Fetches the Gravatar

Name Type Description
$size mixed/int
$default string
Twig
    <img src="{{comment.avatar(36,template_uri~"img/dude.jpg")}}" alt="Image of {{comment.author.name}}" />
HTML
    <img src="http://gravatar.com/i/sfsfsdfasdfsfa.jpg" alt="Image of Katherine Rich" />

children

children( )

returns: array Comments

content

content( )

returns: string

date

date( string $date_format="" )

returns: string

Name Type Description
$date_format string
Twig
    {% for comment in post.comments %}
    <article class="comment">
      <p class="date">Posted on {{ comment.date }}:</p>
      <p class="comment">{{ comment.content }}</p>
    </article>
    {% endfor %}
HTML
    <article class="comment">
      <p class="date">Posted on September 28, 2015:</p>
      <p class="comment">Happy Birthday!</p>
    </article>

depth

depth( )

returns: void

is_child

is_child( )

returns: bool

meta

meta( string $field_name )

returns: mixed

Name Type Description
$field_name string

reply_link( string $reply_text="Reply" )

returns: string

Enqueue the WP threaded comments javascript, and fetch the reply link for various comments.

Name Type Description
$reply_text string

time

time( string $time_format="" )

returns: string

Name Type Description
$time_format string
Twig
    {% for comment in post.comments %}
    <article class="comment">
      <p class="date">Posted on {{ comment.date }} at {{comment.time}}:</p>
      <p class="comment">{{ comment.content }}</p>
    </article>
    {% endfor %}
HTML
    <article class="comment">
      <p class="date">Posted on September 28, 2015 at 12:45 am:</p>
      <p class="comment">Happy Birthday!</p>
    </article>

update_depth

update_depth( int $depth )

returns: void

Name Type Description
$depth int

This class extends \Timber\Core

This class implements \Timber\CoreInterface

Timber\Image

If TimberPost is the class you’re going to spend the most time, TimberImage is the class you’re going to have the most fun with.

PHP
<?php
$context = Timber::get_context();
$post = new TimberPost();
$context['post'] = $post;
// lets say you have an alternate large 'cover image' for your post stored in a custom field which returns an image ID
$cover_image_id = $post->cover_image;
$context['cover_image'] = new TimberImage($cover_image_id);
Timber::render('single.twig', $context);
Twig
<article>
    <img src="{{cover_image.src}}" class="cover-image" />
    <h1 class="headline">{{post.title}}</h1>
    <div class="body">
        {{post.content}}
    </div>
    <img src="{{ Image(post.custom_field_with_image_id).src }}" alt="Another way to initialize images as TimberImages, but within Twig" />
</article>
HTML
<article>
    <img src="http://example.org/wp-content/uploads/2015/06/nevermind.jpg" class="cover-image" />
    <h1 class="headline">Now you've done it!</h1>
    <div class="body">
        Whatever whatever
    </div>
    <img src="http://example.org/wp-content/uploads/2015/06/kurt.jpg" alt="Another way to initialize images as TimberImages, but within Twig" />
</article>
Name Type Description
alt string alt text stored in WordPress
aspect \Timber\float
caption string $caption the string stored in the WordPress database
class string $class stores the CSS classes for the post (ex: “post post-type-book post-123”)
file_loc string $file_loc the location of the image file in the filesystem (ex: /var/www/htdocs/wp-content/uploads/2015/08/my-pic.jpg)
height int
id integer the ID of the image (which is a WP_Post)
link void
parent bool/\Timber\TimberPost
path string the /relative/path/to/the/file
post_status string $post_status the status of a post (“draft”, “publish”, etc.)
post_type string $post_type the name of the post type, this is the machine name (so “my_custom_post_type” as opposed to “My Custom Post Type”)
slug string $slug the URL-safe slug, this corresponds to the poorly-named “post_name” in the WP database, ex: “hello-world”
src bool/string
width int

__construct

__construct( int/string $iid )

returns: void

Creates a new TimberImage object

Name Type Description
$iid int/string
PHP
<?php
    // You can pass it an ID number
    $myImage = new TimberImage(552);
        //Or send it a URL to an image
    $myImage = new TimberImage('http://google.com/logo.jpg');

__toString

__toString( )

returns: string the src of the file

alt

alt( )

returns: string alt text stored in WordPress

Twig
    <img src="{{ image.src }}" alt="{{ image.alt }}" />
HTML
    <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" alt="W3 Checker told me to add alt text, so I am" />

aspect

aspect( )

returns: \Timber\float

Twig
    {% if post.thumbnail.aspect < 1 %}
        {# handle vertical image #}
        <img src="{{ post.thumbnail.src|resize(300, 500) }}" alt="A basketball player" />
    {% else %}
           <img src="{{ post.thumbnail.src|resize(500) }}" alt="A sumo wrestler" />
    {% endif %}

get_pathinfo

get_pathinfo( )

returns: array

Get a PHP array with pathinfo() info from the file

get_url

DEPRECATED since 0.21.9 use src() instead

get_url( string $size="" )

returns: string

Name Type Description
$size string

height

height( )

returns: int

Twig
    <img src="{{ image.src }}" height="{{ image.height }}" />
HTML
    <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" height="900" />

link( )

returns: void

Returns the link to an image attachment’s Permalink page (NOT the link for the image itself!!)

Twig
    <a href="{{ image.link }}"><img src="{{ image.src }} "/></a>
HTML
    <a href="http://example.org/my-cool-picture"><img src="http://example.org/wp-content/uploads/2015/whatever.jpg"/></a>

parent

parent( )

returns: bool/\Timber\TimberPost

path

path( )

returns: string the /relative/path/to/the/file

Twig
    <img src="{{ image.path }}" />
HTML
    <img src="wp-content/uploads/2015/08/pic.jpg" />

src

src( string $size="full" )

returns: bool/string

Name Type Description
$size string a size known to WordPress (like “medium”)
Twig
    <h1>{{ post.title }}</h1>
    <img src="{{ post.thumbnail.src }}" />
HTML
    <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" />

url

DEPRECATED since 0.21.9 use src() instead

url( string $size="" )

returns: string

Name Type Description
$size string

width

width( )

returns: int

Twig
    <img src="{{ image.src }}" width="{{ image.width }}" />
HTML
    <img src="http://example.org/wp-content/uploads/2015/08/pic.jpg" width="1600" />

wp_upload_dir

wp_upload_dir( )

returns: void

get_post_custom

get_post_custom( mixed $iid )

returns: array

Name Type Description
$iid mixed

This class extends \Timber\Post

This class implements \Timber\CoreInterface

Timber\Menu

In Timber, you can use TimberMenu() to make a standard Wordpress menu available to the Twig template as an object you can loop through. And once the menu becomes available to the context, you can get items from it in a way that is a little smoother and more versatile than Wordpress’s wp_nav_menu. (You need never again rely on a crazy “Walker Function!”). The first thing to do is to initialize the menu using TimberMenu(). This will make the menu available as an object to work with in the context. (TimberMenu can include a Wordpress menu slug or ID, or it can be sent with no parameter–and guess the right menu.)

PHP
<?php
# functions.php
add_filter('timber/context', 'add_to_context');
function add_to_context($data){
    // So here you are adding data to Timber's context object, i.e...
    $data['foo'] = 'I am some other typical value set in your functions.php file, unrelated to the menu';
    // Now, in similar fashion, you add a Timber menu and send it along to the context.
        $data['menu'] = new TimberMenu(); // This is where you can also send a WordPress menu slug or ID
    return $data;
}
# index.php (or any PHP file)
// Since you want a menu object available on every page, I added it to the universal Timber context via the functions.php file. You could also this in each PHP file if you find that too confusing.
$context = Timber::get_context();
$context['posts'] = Timber::get_posts();
Timber::render('index.twig', $context);
?>
Twig
<nav>
    <ul class="main-nav">
    {% for item in menu.get_items %}
        <li class="nav-main-item {{item.classes | join(' ')}}"><a class="nav-main-link" href="{{item.link}}">{{item.title}}</a>
            {% if item.get_children %}
            <ul class="nav-drop">
              {% for child in item.get_children %}
                <li class="nav-drop-item"><a href="{{child.link}}">{{child.title}}</a></li>
              {% endfor %}
             </ul>
          {% endif %}
          </li>
   {% endfor %}
   </ul>
</nav>
Name Type Description
id integer $id the ID# of the menu, corresponding to the wp_terms table
items TimberMenuItem[] null
name string $name of the menu (ex: Main Navigation)
title string $name of the menu (ex: Main Navigation)

__construct

__construct( \Timber\integer/string $slug )

returns: void

Name Type Description
$slug \Timber\integer/string

find_parent_item_in_menu

find_parent_item_in_menu( array $menu_items, int $parent_id )

returns: \Timber\TimberMenuItem/null

Name Type Description
$menu_items array
$parent_id int

get_items

get_items( )

returns: array

This class extends \Timber\Core

Timber\MenuItem

Name Type Description
children array/bool
is_external bool
link string a full URL like http://mysite.com/thing/
name string
slug string the slug of the menu item kinda-like-this

__construct

__construct( array/object $data )

returns: void

Name Type Description
$data array/object

__toString

__toString( )

returns: string the label for the menu item

add_child

add_child( \Timber\TimberMenuItem $item )

returns: void

Name Type Description
$item \Timber\TimberMenuItem

add_class

add_class( mixed $class_name )

returns: void

add a class the menu item should have

Name Type Description
$class_name mixed

children

children( )

returns: array/bool

Get the child TimberMenuItemss of a TimberMenuItem

external

external( )

returns: bool

Checks to see if a link is external, helpful when creating rules for the target of a link

is_external

is_external( )

returns: bool

Checks to see if the menu item is an external link so if my site is example.org, google.com/whatever is an external link. Helpful when creating rules for the target of a link

Twig
    <a href="{{ item.link }}" target="{{ item.is_external ? '_blank' : '_self' }}">

link( )

returns: string a full URL like http://mysite.com/thing/

Get the full link to a Menu Item

Twig
    {% for item in menu.items %}
        <li><a href="{{ item.link }}">{{ item.title }}</a></li>
    {% endfor %}

meta

meta( string $key )

returns: mixed whatever value is storied in the database

Name Type Description
$key string lookup key

name

name( )

returns: string

The label for the menu item

path

path( )

returns: string the path of a URL like /foo

Return the relative path of a Menu Item’s link

Twig
    {% for item in menu.items %}
        <li><a href="{{ item.path }}">{{ item.title }}</a></li>
    {% endfor %}

slug

slug( )

returns: string the slug of the menu item kinda-like-this

The slug for the menu item

Twig
    <ul>
        {% for item in menu.items %}
            <li class="{{item.slug}}">
                <a href="{{item.link}}">{{item.name}}</a>
             </li>
        {% endfor %}
    </ul>

## thumbnail
`thumbnail( )`

**returns:** `string` the public thumbnail url

Gets the post thumbnail image object

###### Twig
```twig
    {% for item in menu.items %}
        <li><a href="{{ item.link }}"><img src="{{ item.thumbnail }}"/></a></li>
    {% endfor %}

title

title( )

returns: string the public label like Foo

Gets the public label for the menu item

Twig
    {% for item in menu.items %}
        <li><a href="{{ item.link }}">{{ item.title }}</a></li>
    {% endfor %}

type

type( )

returns: string

Return the type of the menu item

This class extends \Timber\Core

This class implements \Timber\CoreInterface

Timber\Post

This is the object you use to access or extend WordPress posts. Think of it as Timber’s (more accessible) version of WP_Post. This is used throughout Timber to represent posts retrieved from WordPress making them available to Twig templates. See the PHP and Twig examples for an example of what it’s like to work with this object in your code.

PHP
<?php
// single.php, see connected twig example
$context = Timber::get_context();
$context['post'] = new Timber\Post(); // It's a new Timber\Post object, but an existing post from WordPress.
Timber::render('single.twig', $context);
?>
Twig
{# single.twig #}
<article>
    <h1 class="headline">{{post.title}}</h1>
    <div class="body">
        {{post.content}}
    </div>
</article>
HTML
<article>
    <h1 class="headline">The Empire Strikes Back</h1>
    <div class="body">
        It is a dark time for the Rebellion. Although the Death Star has been destroyed, Imperial troops have driven the Rebel forces from their hidden base and pursued them across the galaxy.
    </div>
</article>
Name Type Description
author \Timber\User/null A User object if found, false if not
categories array of TimberTerms
category \Timber\TimberTerm/null
children array
class string $class stores the CSS classes for the post (ex: “post post-type-book post-123”)
comments bool/array
content string
date string
format mixed
get_preview string of the post preview
id string $id the numeric WordPress id of a post
link string ex: http://example.org/2015/07/my-awesome-post
next mixed
parent bool/\Timber\Timber\Post
password_required boolean
path string
post_status string $post_status the status of a post (“draft”, “publish”, etc.)
post_type string $post_type the name of the post type, this is the machine name (so “my_custom_post_type” as opposed to “My Custom Post Type”)
prev mixed
slug string $slug the URL-safe slug, this corresponds to the poorly-named “post_name” in the WP database, ex: “hello-world”
tags array
terms array
thumbnail \Timber\TimberImage/null of your thumbnail
time string
title string

__construct

__construct( mixed $pid=null )

returns: void

If you send the constructor nothing it will try to figure out the current post id based on being inside The_Loop

Name Type Description
$pid mixed
PHP
<?php
    $post = new Timber\Post();
    $other_post = new Timber\Post($random_post_id);

__toString

__toString( )

returns: string

Outputs the title of the post if you do something like <h1>{{post}}</h1>

author

author( )

returns: \Timber\User/null A User object if found, false if not

Return the author of a post

Twig
    <h1>{{post.title}}</h1>
    <p class="byline">
        <a href="{{post.author.link}}">{{post.author.name}}</a>
    </p>

authors

authors( )

returns: void

categories

categories( )

returns: array of TimberTerms

Get the categoires on a particular post

category

category( )

returns: \Timber\TimberTerm/null

Returns a category attached to a post

children

children( string/string/array $post_type="any", bool/string/bool $childPostClass=false )

returns: array

Returns an array of children on the post as Timber\Posts (or other claass as you define).

Name Type Description
$post_type string/string/array optional use to find children of a particular post type (attachment vs. page for example). You might want to restrict to certain types of children in case other stuff gets all mucked in there. You can use ‘parent’ to use the parent’s post type or you can pass an array of post types.
$childPostClass bool/string/bool optional a custom post class (ex: 'MyTimber\Post’) to return the objects as. By default (false) it will use Timber\Post::$post_class value.
Twig
    {% if post.children %}
        Here are the child pages:
        {% for child in page.children %}
            <a href="{{ child.link }}">{{ child.title }}</a>
        {% endfor %}
    {% endif %}

comment_form

comment_form( array $args=array() )

returns: string of HTML for the form

Gets the comment form for use on a single article page

Name Type Description
$args array

comments

comments( mixed/int $count=null, string $order="wp", string $type="comment", string $status="approve", string $CommentClass="Timber\Comment" )

returns: bool/array

Gets the comments on a Timber\Post and returns them as an array of TimberComments (or whatever comment class you set).

Name Type Description
$count mixed/int Set the number of comments you want to get. 0 is analogous to “all”
$order string use ordering set in WordPress admin, or a different scheme
$type string For when other plugins use the comments table for their own special purposes, might be set to 'liveblog’ or other depending on what’s stored in yr comments table
$status string Could be 'pending’, etc.
$CommentClass string What class to use when returning Comment objects. As you become a Timber pro, you might find yourself extending TimberComment for your site or app (obviously, totally optional)
Twig
    {# single.twig #}
    <h4>Comments:</h4>
    {% for comment in post.comments %}
        <div class="comment-{{comment.ID}} comment-order-{{loop.index}}">
            <p>{{comment.author.name}} said:</p>
            <p>{{comment.content}}</p>
        </div>
    {% endfor %}

content

content( int $page, mixed $len=-1 )

returns: string

Gets the actual content of a WP Post, as opposed to post_content this will run the hooks/filters attached to the_content. \This guy will return your posts content with WordPress filters run on it (like for shortcodes and wpautop).

Name Type Description
$page int
$len mixed
Twig
    <div class="article">
        <h2>{{post.title}}</h2>
        <div class="content">{{ post.content }}</div>
    </div>

convert

convert( array $data, string $class )

returns: void

Finds any WP_Post objects and converts them to Timber\Posts

Name Type Description
$data array
$class string

date

date( string $date_format="" )

returns: string

Get the date to use in your template!

Name Type Description
$date_format string
Twig
    Published on {{ post.date }} // Uses WP's formatting set in Admin
    OR
    Published on {{ post.date | date('F jS') }} // Jan 12th
HTML
    Published on January 12, 2015
    OR
    Published on Jan 12th

edit_link( )

returns: bool/string the edit URL of a post in the WordPress admin

Returns the edit URL of a post if the user has access to it

format

format( )

returns: mixed

get_comment_count

get_comment_count( )

returns: int the number of comments on a post

get_content

get_content( mixed/int $len=-1, int $page )

returns: string

Displays the content of the post with filters, shortcodes and wpautop applied

Name Type Description
$len mixed/int
$page int
Twig
        <div class="article-text">{{post.get_content}}</div>
HTML
        <div class="article-text"><p>Blah blah blah</p><p>More blah blah blah.</p></div>

get_field

get_field( string $field_name )

returns: mixed

Name Type Description
$field_name string

get_image

get_image( string $field )

returns: \Timber\TimberImage

Name Type Description
$field string

get_method_values

get_method_values( )

returns: array

get_paged_content

get_paged_content( )

returns: string

get_post_type

DEPRECATED since 1.0.4

get_post_type( )

returns: \Timber\PostType

Returns the post_type object with labels and other info

Twig
    This post is from <span>{{ post.get_post_type.labels.plural }}</span>
HTML
    This post is from <span>Recipes</span>

get_preview

get_preview( mixed/int $len=50, bool $force=false, string $readmore="Read More", bool/bool/string $strip=true, string $end="&hellip;" )

returns: string of the post preview

get a preview of your post, if you have an excerpt it will use that, otherwise it will pull from the post_content. If there’s a <!– more –> tag it will use that to mark where to pull through.

Name Type Description
$len mixed/int The number of words that WP should use to make the tease. (Isn’t this better than this mess?). If you’ve set a post_excerpt on a post, we’ll use that for the preview text; otherwise the first X words of the post_content
$force bool What happens if your custom post excerpt is longer then the length requested? By default ($force = false) it will use the full post_excerpt. However, you can set this to true to force your excerpt to be of the desired length
$readmore string The text you want to use on the 'readmore’ link
$strip bool/bool/string true for default, false for none, string for list of custom attributes
$end string The text to end the preview with (defaults to …)
Twig
    <p>{{post.get_preview(50)}}</p>

has_field

has_field( string $field_name )

returns: boolean

Name Type Description
$field_name string

has_term

has_term( string/int $term_name_or_id, string $taxonomy="all" )

returns: bool

Name Type Description
$term_name_or_id string/int
$taxonomy string

import_field

import_field( string $field_name )

returns: void

Name Type Description
$field_name string

link( )

returns: string ex: http://example.org/2015/07/my-awesome-post

get the permalink for a post object

Twig
    <a href="{{post.link}}">Read my post</a>

meta

meta( mixed/string $field_name=null )

returns: mixed

Name Type Description
$field_name mixed/string

modified_author

modified_author( )

returns: \Timber\User/null A User object if found, false if not

Get the author (WordPress user) who last modified the post

Twig
    Last updated by {{ post.modified_author.name }}
HTML
    Last updated by Harper Lee

modified_date

modified_date( string $date_format="" )

returns: string

Name Type Description
$date_format string

modified_time

modified_time( string $time_format="" )

returns: string

Name Type Description
$time_format string

name

name( )

returns: string

next

next( bool $in_same_term=false )

returns: mixed

Name Type Description
$in_same_term bool

paged_content

paged_content( )

returns: string

pagination

pagination( )

returns: array

Get a data array of pagination so you can navigate to the previous/next for a paginated post

parent

parent( )

returns: bool/\Timber\Timber\Post

Gets the parent (if one exists) from a post as a Timber\Post object (or whatever is set in Timber\Post::$PostClass)

Twig
    Parent page: <a href="{{ post.parent.link }}">{{ post.parent.title }}</a>

password_required

password_required( )

returns: boolean

whether post requires password and correct password has been provided

path

path( )

returns: string

Gets the relative path of a WP Post, so while link() will return http://example.org/2015/07/my-cool-post this will return just /2015/07/my-cool-post

Twig
    <a href="{{post.path}}">{{post.title}}</a>

DEPRECATED 0.20.0 use link() instead

permalink( )

returns: string

prev

prev( bool $in_same_term=false )

returns: mixed

Get the previous post in a set

Name Type Description
$in_same_term bool
Twig
    <h4>Prior Entry:</h4>
    <h3>{{post.prev.title}}</h3>
    <p>{{post.prev.get_preview(25)}}</p>

preview

preview( )

returns: \Timber\PostPreview

tags

tags( )

returns: array

Gets the tags on a post, uses WP’s post_tag taxonomy

terms

terms( string/string/array $tax="", bool $merge=true, string $TermClass="" )

returns: array

Get the terms associated with the post This goes across all taxonomies by default

Name Type Description
$tax string/string/array What taxonom(y
$merge bool Should the resulting array be one big one (true)? Or should it be an array of sub-arrays for each taxonomy (false)?
$TermClass string
Twig
    <section id="job-feed">
    {% for post in job %}
    <div class="job">
      <h2>{{ post.title }}</h2> 
       <p>{{ post.terms('category') | join(', ') }}
     </div>
    {% endfor %}    
    </section>
HTML
    <section id="job-feed">
      <div class="job">
           <h2>Cheese Maker</h2>
        <p>Food, Cheese, Fromage</p>
      </div>
      <div class="job">
           <h2>Mime</h2>
        <p>Performance, Silence</p>
      </div>
    </section>

thumbnail

thumbnail( )

returns: \Timber\TimberImage/null of your thumbnail

get the featured image as a TimberImage

Twig
    <img src="{{post.thumbnail.src}}" />

time

time( string $time_format="" )

returns: string

Get the time to use in your template

Name Type Description
$time_format string
Twig
    Published at {{ post.time }} // Uses WP's formatting set in Admin
    OR
    Published at {{ post.time | time('G:i') }} // 13:25
HTML
    Published at 1:25 pm
    OR
    Published at 13:25

title

title( )

returns: string

Returns the processed title to be used in templates. This returns the title of the post after WP’s filters have run. This is analogous to the_title() in standard WP template tags.

Twig
    <h1>{{ post.title }}</h1>

type

type( )

returns: \Timber\PostType

Returns the post_type object with labels and other info

Twig
    This post is from <span>{{ post.type.labels.name }}</span>
HTML
    This post is from <span>Recipes</span>

update

update( string $field, mixed $value )

returns: void

updates the post_meta of the current object with the given value

Name Type Description
$field string
$value mixed

get_post_preview_id

get_post_preview_id( mixed $query )

returns: mixed

Name Type Description
$query mixed

get_wp_link_page( int $i )

returns: string

Name Type Description
$i int

maybe_show_password_form

maybe_show_password_form( )

returns: string/void

If the Password form is to be shown, show it!

This class extends \Timber\Core

This class implements \Timber\CoreInterface

Timber\PostPreview

An object that lets a user easily modify the post preview to their liking

Name Type Description

__construct

__construct( \Timber\Post $post )

returns: void

Name Type Description
$post \Timber\Post

__toString

__toString( )

returns: void

chars

chars( bool/\Timber\integer $char_length=false )

returns: void

Name Type Description
$char_length bool/\Timber\integer (in characters) of the target preview

end

end( string $end="&hellip;" )

returns: void

Name Type Description
$end string how should the text in the preview end

force

force( bool/boolean $force=true )

returns: void

Name Type Description
$force bool/boolean If the editor wrote a manual excerpt longer than the set length, should it be “forced” to the size specified?

length

length( mixed/\Timber\integer $length=50 )

returns: void

Name Type Description
$length mixed/\Timber\integer (in words) of the target preview

read_more

read_more( string $readmore="Read More" )

returns: void

Name Type Description
$readmore string What the text displays as to the reader inside of the tag

strip

strip( bool/boolean/string $strip=true )

returns: void

Name Type Description
$strip bool/boolean/string strip the tags or what? You can also provide a list of allowed tags

assemble

assemble( string $text, array/\Timber\booelan $readmore_matches, boolean $trimmed )

returns: void

Name Type Description
$text string
$readmore_matches array/\Timber\booelan
$trimmed boolean was the text trimmed?

run

run( )

returns: void

Timber\PostQuery

A PostQuery allows a user to query for a Collection of WordPress Posts. PostCollections are used directly in Twig templates to iterate through and retrieve meta information about the collection of posts

Name Type Description

__construct

__construct( bool $query=false, string $post_class="\Timber\Post" )

returns: void

Name Type Description
$query bool
$post_class string

pagination

pagination( array/ array $prefs=array() )

returns: Timber\Pagination object

Set pagination for the collection. Optionally could be used to get pagination with custom preferences.

Name Type Description
$prefs array/ array

get_query

get_query( )

returns: mixed the query the user orignally passed to the pagination object

This class extends \Timber\PostCollection

This class implements \IteratorAggregate, \Traversable, \ArrayAccess, \Serializable, \Countable

Timber\Site

TimberSite gives you access to information you need about your site. In Multisite setups, you can get info on other sites in your network.

PHP
<?php
$context = Timber::get_context();
$other_site_id = 2;
$context['other_site'] = new TimberSite($other_site_id);
Timber::render('index.twig', $context);
Twig
My site is called {{site.name}}, another site on my network is {{other_site.name}}
HTML
My site is called Jared's blog, another site on my network is Upstatement.com
Name Type Description
admin_email string the admin email address set in the WP admin panel
charset string
description string
id int the ID of a site in multisite
language string the language setting ex: en-US
language_attributes string of language attributes for usage in the tag
link string
multisite bool true if multisite, false if plain ole’ WordPress
name string
pingback_url string for people who like trackback spam
rdf string
theme TimberTheme
title string

__construct

__construct( mixed/string/int $site_name_or_id=null )

returns: void

Constructs a TimberSite object

Name Type Description
$site_name_or_id mixed/string/int
PHP
<?php
    //multisite setup
    $site = new TimberSite(1);
    $site_two = new TimberSite("My Cool Site");
    //non-multisite
    $site = new TimberSite();

__get

__get( mixed $field )

returns: mixed

Name Type Description
$field mixed

icon

icon( )

returns: void

link( )

returns: string

Returns the link to the site’s home.

Twig
    <a href="{{ site.link }}" title="Home">
          <img src="wp-content/uploads/logo.png" alt="Logo for some stupid thing" />
    </a>
HTML
    <a href="http://example.org" title="Home">
          <img src="wp-content/uploads/logo.png" alt="Logo for some stupid thing" />
    </a>

url

DEPRECATED 1.0.4

url( )

returns: string

icon_multisite

icon_multisite( mixed $site_id )

returns: void

Name Type Description
$site_id mixed

switch_to_blog

switch_to_blog( string/\Timber\integer/null $site_name_or_id )

returns: integer with the ID of the new blog

Switches to the blog requested in the request

Name Type Description
$site_name_or_id string/\Timber\integer/null

This class extends \Timber\Core

This class implements \Timber\CoreInterface

Timber\Term

Terms: WordPress has got ‘em, you want 'em. Categories. Tags. Custom Taxonomies. You don’t care, you’re a fiend. Well let’s get this under control:

PHP
<?php
//Get a term by its ID
$context['term'] = new TimberTerm(6);
//Get a term when on a term archive page
$context['term_page'] = new TimberTerm();
//Get a term with a slug
$context['team'] = new TimberTerm('patriots');
//Get a team with a slug from a specific taxonomy
$context['st_louis'] = new TimberTerm('cardinals', 'baseball');
Timber::render('index.twig', $context);
Twig
<h2>{{term_page.name}} Archives</h2>
<h3>Teams</h3>
<ul>
    <li>{{st_louis.name}} - {{st_louis.description}}</li>
    <li>{{team.name}} - {{team.description}}</li>
</ul>
HTML
<h2>Team Archives</h2>
<h3>Teams</h3>
<ul>
    <li>St. Louis Cardinals - Winner of 11 World Series</li>
    <li>New England Patriots - Winner of 4 Super Bowls</li>
</ul>
Name Type Description
children array
description string
edit_link string
link string
meta string
name string the human-friendly name of the term (ex: French Cuisine)
path string
posts array/bool/null
taxonomy string the WordPress taxonomy slug (ex: post_tag or actors)
title string

__construct

__construct( mixed/int $tid=null, string $tax="" )

returns: void

Name Type Description
$tid mixed/int
$tax string

__toString

__toString( )

returns: string

children

children( )

returns: array

description

description( )

returns: string

edit_link( )

returns: string

from

from( mixed $tid, mixed $taxonomy )

returns: \Timber\static

Name Type Description
$tid mixed
$taxonomy mixed

link( )

returns: string

Returns a full link to the term archive page like http://example.com/category/news

Twig
    See all posts in: <a href="{{ term.link }}">{{ term.name }}</a>

meta

meta( string $field_name )

returns: string

Retrieves and outputs meta information stored with a term. This will use both data stored under (old) ACF hacks and new (WP 4.6+) where term meta has its own table. If retrieving a special ACF field (repeater, etc.) you can use the output immediately in Twig — no further processing is required.

Name Type Description
$field_name string
Twig
    <div class="location-info">
      <h2>{{ term.name }}</h2>
      <p>{{ term.meta('address') }}</p>
    </div>

path

path( )

returns: string

Returns a relative link (path) to the term archive page like /category/news

Twig
    See all posts in: <a href="{{ term.path }}">{{ term.name }}</a>

posts

posts( mixed/int $numberposts_or_args=10, string $post_type_or_class="any", string $post_class="" )

returns: array/bool/null

Name Type Description
$numberposts_or_args mixed/int
$post_type_or_class string
$post_class string
Twig
    <h4>Recent posts in {{ term.name }}</h4>
    <ul>
    {% for post in term.posts(3, 'post') %}
        <li><a href="{{post.link}}">{{post.title}}</a></li>
    {% endfor %}
    </ul>

title

title( )

returns: string

update

update( mixed $key, mixed $value )

returns: void

Name Type Description
$key mixed
$value mixed

This class extends \Timber\Core

This class implements \Timber\CoreInterface

Timber\Theme

Need to display info about your theme? Well you’ve come to the right place. By default info on the current theme comes for free with what’s fetched by Timber::get_context() in which case you can access it your theme like so:

PHP
<?php
<?php
$context = Timber::get_context();
Timber::render('index.twig', $context);
?>
Twig
<script src="{{theme.link}}/static/js/all.js"></script>
HTML
<script src="http://example.org/wp-content/themes/my-theme/static/js/all.js"></script>
Name Type Description
link string the absolute path to the theme (ex: http://example.org/wp-content/themes/my-timber-theme)
name string the human-friendly name of the theme (ex: My Timber Starter Theme)
parent TimberTheme bool
parent_slug string the slug of the parent theme (ex: _s)
path string the relative path to the theme (ex: /wp-content/themes/my-timber-theme)
slug string the slug of the theme (ex: my-super-theme)

__construct

__construct( mixed/string $slug=null )

returns: void

Constructs a new TimberTheme object. NOTE the TimberTheme object of the current theme comes in the default Timber::get_context() call. You can access this in your twig template via `{{site.theme}}.

Name Type Description
$slug mixed/string
PHP
<?php
    <?php
        $theme = new TimberTheme("my-theme");
        $context['theme_stuff'] = $theme;
        Timber::render('single.twig', $context);
    ?>
Twig
    We are currently using the {{ theme_stuff.name }} theme.
HTML
    We are currently using the My Theme theme.

link( )

returns: string the absolute path to the theme (ex: http://example.org/wp-content/themes/my-timber-theme)

path

path( )

returns: string the relative path to the theme (ex: /wp-content/themes/my-timber-theme)

theme_mod

theme_mod( string $name, bool $default=false )

returns: string

Name Type Description
$name string
$default bool

theme_mods

theme_mods( )

returns: array

This class extends \Timber\Core

Timber\User

This is used in Timber to represent users retrived from WordPress. You can call $my_user = new Timber\User(123); directly, or access it through the {{ post.author }} method.

PHP
<?php
$context['current_user'] = new Timber\User();
$context['post'] = new Timber\Post();
Timber::render('single.twig', $context);
Twig
<p class="current-user-info">Your name is {{ current_user.name }}</p>
<p class="article-info">This article is called "{{ post.title }}" and it's by {{ post.author.name }}
HTML
<p class="current-user-info">Your name is Jesse Eisenberg</p>
<p class="article-info">This article is called "Consider the Lobster" and it's by David Foster Wallace
Name Type Description
avatar string Image
description string The description from WordPress
first_name string The first name of the user
id int The ID from WordPress
last_name string The last name of the user
link string http://example.org/author/lincoln
name string the human-friendly name of the user (ex: “Buster Bluth”)
path string ex: /author/lincoln
slug string ex baberaham-lincoln

__construct

__construct( bool/object/int/bool $uid=false )

returns: void

Name Type Description
$uid bool/object/int/bool

__toString

__toString( )

returns: string a fallback for TimberUser::name()

Twig
    This post is by {{ post.author }}
HTML
    This post is by Jared Novack

get_custom

get_custom( )

returns: array/null

get_meta_field

get_meta_field( string $field_name )

returns: mixed

Name Type Description
$field_name string

link( )

returns: string http://example.org/author/lincoln

meta

meta( string $field_name )

returns: mixed

Name Type Description
$field_name string

name

name( )

returns: string the human-friendly name of the user (ex: “Buster Bluth”)

path

path( )

returns: string ex: /author/lincoln

slug

slug( )

returns: string ex baberaham-lincoln

This class extends \Timber\Core

This class implements \Timber\CoreInterface

Timber\Helper

As the name suggests these are helpers for Timber (and you!) when developing. You can find additional (mainly internally-focused helpers) in TimberURLHelper

Name Type Description
ob_function string
start_timer \Timber\float
transient mixed

array_to_object

array_to_object( mixed $array )

returns: \stdClass

Name Type Description
$array mixed

array_truncate

array_truncate( mixed $array, mixed $len )

returns: array

Name Type Description
$array mixed
$len mixed

close_tags

DEPRECATED since 1.2.0

close_tags( mixed $html )

returns: string

Name Type Description
$html mixed

error_log

error_log( mixed $error )

returns: void

Name Type Description
$error mixed

function_wrapper

function_wrapper( mixed $function_name, array $defaults=array(), bool $return_output_buffer=false )

returns: \Timber\Timber\FunctionWrapper/mixed

Name Type Description
$function_name mixed or array( $class( string
$defaults array
$return_output_buffer bool

get_comment_form

DEPRECATED 0.21.8 use {{ function('comment_form') }} instead

get_comment_form( mixed $post_id=null, array $args=array() )

returns: string

Gets the comment form for use on a single article page

Name Type Description
$post_id mixed
$args array

get_current_url

get_current_url( )

returns: string

get_object_by_property

get_object_by_property( mixed $array, mixed $key, mixed $value )

returns: array/null

Name Type Description
$array mixed
$key mixed
$value mixed

get_object_index_by_property

get_object_index_by_property( mixed $array, mixed $key, mixed $value )

returns: bool/int

Name Type Description
$array mixed
$key mixed
$value mixed

get_wp_title

get_wp_title( string $separator=" ", string $seplocation="left" )

returns: string

Name Type Description
$separator string
$seplocation string

is_array_assoc

is_array_assoc( mixed $arr )

returns: bool

Name Type Description
$arr mixed

is_true

is_true( mixed $value )

returns: bool

Name Type Description
$value mixed

iseven

iseven( mixed $i )

returns: bool

Name Type Description
$i mixed

isodd

isodd( mixed $i )

returns: bool

Name Type Description
$i mixed

ob_function

ob_function( \Timber\callback $function, array $args=array() )

returns: string

Calls a function with an output buffer. This is useful if you have a function that outputs text that you want to capture and use within a twig template.

Name Type Description
$function \Timber\callback
$args array
PHP
<?php
    function the_form() {
        echo '<form action="form.php"><input type="text" /><input type="submit /></form>';
    }
        $context = Timber::get_context();
    $context['post'] = new TimberPost();
    $context['my_form'] = TimberHelper::ob_function('the_form');
    Timber::render('single-form.twig', $context);
Twig
    <h1>{{ post.title }}</h1>
    {{ my_form }}
HTML
    <h1>Apply to my contest!</h1>
    <form action="form.php"><input type="text" /><input type="submit /></form>

osort

osort( mixed $array, mixed $prop )

returns: void

Name Type Description
$array mixed
$prop mixed

DEPRECATED since 1.1.2

paginate_links( array $args=array() )

returns: array

Name Type Description
$args array

pluck

pluck( array $array, string $key )

returns: void

Plucks the values of a certain key from an array of objects

Name Type Description
$array array
$key string

start_timer

start_timer( )

returns: \Timber\float

For measuring time, this will start a timer

stop_timer

stop_timer( mixed $start )

returns: string

For stopping time and getting the data

Name Type Description
$start mixed
PHP
<?php
    $start = TimberHelper::start_timer();
    // do some stuff that takes awhile
    echo TimberHelper::stop_timer( $start );

transient

transient( mixed $slug, mixed $callback, mixed $transient_time, mixed $lock_timeout=5, bool $force=false )

returns: mixed

A utility for a one-stop shop for Transients

Name Type Description
$slug mixed
$callback mixed
$transient_time mixed
$lock_timeout mixed
$force bool
PHP
<?php
    $context = Timber::get_context();
    $context['favorites'] = Timber\Helper::transient('user-' .$uid. '-favorites', function() use ($uid) {
        //some expensive query here that's doing something you want to store to a transient
        return $favorites;
    }, 600);
    Timber::render('single.twig', $context);

trim_words

DEPRECATED since 1.2.0

trim_words( mixed $text, mixed $num_words=55, mixed $more=null, string $allowed_tags="p a span b i br blockquote" )

returns: string

Name Type Description
$text mixed
$num_words mixed
$more mixed
$allowed_tags string

warn

warn( string $message )

returns: boolean

Name Type Description
$message string that you want to output

handle_transient_locking

handle_transient_locking( mixed $slug, mixed $callback, mixed $transient_time, mixed $lock_timeout, mixed $force, mixed $enable_transients )

returns: void

Does the dirty work of locking the transient, running the callback and unlocking

Name Type Description
$slug mixed
$callback mixed
$transient_time mixed
$lock_timeout mixed
$force mixed
$enable_transients mixed

Timber\ImageHelper

Implements the Twig image filters: https://github.com/timber/timber/wiki/Image-cookbook#arbitrary-resizing-of-images - resize - retina - letterbox - tojpg Implementation: - public static functions provide the methods that are called by the filter - most of the work is common to all filters (URL analysis, directory gymnastics, file caching, error management) and done by private static functions - the specific part (actual image processing) is delegated to dedicated subclasses of TimberImageOperation

Name Type Description
resize string (ex: )

delete_generatedif_image

_delete_generated_if_image( mixed $post_id )

returns: void

Checks if attachment is an image before deleting generated files

Name Type Description
$post_id mixed

add_filters

add_filters( )

returns: void

adds a ‘relative’ key to wp_upload_dir() result. It will contain the relative url to upload dir.

analyze_url

analyze_url( mixed $url )

returns: array an array (see keys in code below)

Takes in an URL and breaks it into components, that will then be used in the different steps of image processing. The image is expected to be either part of a theme, plugin, or an upload.

Name Type Description
$url mixed

delete_generated_files

delete_generated_files( mixed $local_file )

returns: void

Deletes the auto-generated files for resize and letterboxing created by Timber or: http://example.org/wp-content/uploads/2015/my-pic.jpg

Name Type Description
$local_file mixed

get_letterbox_file_path

get_letterbox_file_path( mixed $url, mixed $w, mixed $h, mixed $color )

returns: mixed

Name Type Description
$url mixed
$w mixed
$h mixed
$color mixed

get_letterbox_file_url

get_letterbox_file_url( mixed $url, mixed $w, mixed $h, mixed $color )

returns: mixed

Name Type Description
$url mixed
$w mixed
$h mixed
$color mixed

get_resize_file_path

get_resize_file_path( mixed $url, mixed $w, mixed $h, mixed $crop )

returns: mixed

Name Type Description
$url mixed
$w mixed
$h mixed
$crop mixed

get_resize_file_url

get_resize_file_url( mixed $url, mixed $w, mixed $h, mixed $crop )

returns: mixed

Name Type Description
$url mixed
$w mixed
$h mixed
$crop mixed

get_server_location

get_server_location( mixed $url )

returns: string

Determines the filepath corresponding to a given URL

Name Type Description
$url mixed

get_sideloaded_file_loc

get_sideloaded_file_loc( mixed $file )

returns: string

Determines the filepath where a given external file will be stored.

Name Type Description
$file mixed

img_to_jpg

img_to_jpg( mixed $src, string $bghex="#FFFFFF", bool $force=false )

returns: string

Generates a new image by converting the source GIF or PNG into JPG

Name Type Description
$src mixed
$bghex string
$force bool

init

init( )

returns: void

is_animated_gif

is_animated_gif( mixed $file )

returns: boolean true if it’s an animated gif, false if not

checks to see if the given file is an aimated gif

Name Type Description
$file mixed

letterbox

letterbox( mixed $src, mixed $w, mixed $h, string $color="#000000", bool $force=false )

returns: string

Generate a new image with the specified dimensions. New dimensions are achieved by adding colored bands to maintain ratio.

Name Type Description
$src mixed
$w mixed
$h mixed
$color string
$force bool

resize

resize( mixed $src, mixed $w, mixed $h, string $crop="default", bool $force=false )

returns: string (ex: )

Generates a new image with the specified dimensions. New dimensions are achieved by cropping to maintain ratio.

Name Type Description
$src mixed
$w mixed
$h mixed
$crop string
$force bool
Twig
    <img src="{{ image.src | resize(300, 200, 'top') }}" />
HTML
    <img src="http://example.org/wp-content/uploads/pic-300x200-c-top.jpg" />

retina_resize

retina_resize( mixed $src, mixed $multiplier=2, bool/boolean $force=false )

returns: string url to the new image

Generates a new image with increased size, for display on Retina screens.

Name Type Description
$src mixed
$multiplier mixed
$force bool/boolean

sideload_image

sideload_image( mixed $file )

returns: string the URL to the downloaded file

downloads an external image to the server and stores it on the server

Name Type Description
$file mixed

theme_url_to_dir

theme_url_to_dir( mixed $src )

returns: string full path to the file in question

Converts a URL located in a theme directory into the raw file path

Name Type Description
$src mixed

add_actions

add_actions( )

returns: void

Deletes all resized versions of an image when the source is deleted or its meta data is regenerated

add_constants

add_constants( )

returns: void

Adds a constant defining the path to the content directory relative to the site for example /wp-content or /content

is_in_theme_dir

is_in_theme_dir( mixed $path )

returns: bool

Name Type Description
$path mixed

maybe_realpath

maybe_realpath( mixed $path )

returns: string the resolved path

Runs realpath to resolve symbolic links (../, etc). But only if it’s a path and not a URL

Name Type Description
$path mixed

process_delete_generated_files

process_delete_generated_files( mixed $filename, mixed $ext, mixed $dir, mixed $search_pattern, mixed $match_pattern=null )

returns: void

Deletes resized versions of the supplied file name. So if passed a value like my-pic.jpg, this function will delete my-pic-500x200-c-left.jpg, my-pic-400x400-c-default.jpg, etc. keeping these here so I know what the hell we’re matching $match = preg_match(“/\/srv\/www\/wordpress-develop\/src\/wp-content\/uploads\/2014\/05\/$filename-[0-9]x[0-9]-c-[a-z].jpg/”, $found_file); $match = preg_match(“/\/srv\/www\/wordpress-develop\/src\/wp-content\/uploads\/2014\/05\/arch-[0-9]x[0-9]-c-[a-z].jpg/”, $filename);

Name Type Description
$filename mixed
$ext mixed
$dir mixed
$search_pattern mixed
$match_pattern mixed

Timber\TextHelper

Class provides different text-related functions commonly used in WordPress development

Name Type Description

close_tags

close_tags( mixed $html )

returns: string

Name Type Description
$html mixed

remove_tags

remove_tags( mixed $string, array $tags=array() )

returns: void

Name Type Description
$string mixed
$tags array

starts_with

starts_with( string $haystack, string $needle )

returns: boolean

Name Type Description
$haystack string
$needle string

trim_characters

trim_characters( mixed $text, mixed $num_chars=60, mixed $more=null )

returns: string trimmed text.

Trims text to a certain number of characters. This function can be useful for excerpt of the post As opposed to wp_trim_words trims characters that makes text to take the same amount of space in each post for example

Name Type Description
$text mixed
$num_chars mixed
$more mixed

trim_words

trim_words( mixed $text, mixed $num_words=55, mixed $more=null, string $allowed_tags="p a span b i br blockquote" )

returns: string

Name Type Description
$text mixed
$num_words mixed
$more mixed
$allowed_tags string

Timber\URLHelper

Name Type Description

file_system_to_url

file_system_to_url( string $fs )

returns: void

Name Type Description
$fs string

get_current_url

get_current_url( )

returns: string

get_full_path

get_full_path( mixed $src )

returns: string

Name Type Description
$src mixed

get_host

get_host( )

returns: string the HTTP_HOST or SERVER_NAME

Some setups like HTTP_HOST, some like SERVER_NAME, it’s complicated

get_params

get_params( bool/int $i=false )

returns: array/string

Returns the url parameters, for example for url http://example.org/blog/post/news/2014/whatever this will return array(‘blog’, 'post’, 'news’, '2014’, 'whatever’); OR if sent an integer like: TimberUrlHelper::get_params(2); this will return 'news’;

Name Type Description
$i bool/int the position of the parameter to grab.

get_path_base

get_path_base( )

returns: string

get_rel_path

get_rel_path( mixed $src )

returns: string

Name Type Description
$src mixed

get_rel_url

get_rel_url( mixed $url, bool $force=false )

returns: string

Name Type Description
$url mixed
$force bool

get_scheme

get_scheme( )

returns: string

Get url scheme

is_absolute

is_absolute( string $path )

returns: boolean true if $path is an absolute url, false if relative.

This will evaluate wheter a URL is at an aboslute location (like http://example.org/whatever)

Name Type Description
$path string

is_external

is_external( mixed $url )

returns: bool true if $path is an external url, false if relative or local. true if it’s a subdomain (http://cdn.example.org = true)

Name Type Description
$url mixed

is_external_content

is_external_content( mixed $url )

returns: boolean if $url points to an external location returns true

This function is slightly different from the one below in the case of: an image hosted on the same domain BUT on a different site than the Wordpress install will be reported as external content.

Name Type Description
$url mixed

is_local

is_local( mixed $url )

returns: bool

Name Type Description
$url mixed

is_url

is_url( mixed $url )

returns: bool

Name Type Description
$url mixed

prepend_to_url

prepend_to_url( mixed $url, mixed $path )

returns: string

Name Type Description
$url mixed
$path mixed

preslashit

preslashit( mixed $path )

returns: string

Name Type Description
$path mixed

remove_double_slashes

remove_double_slashes( mixed $url )

returns: string

Name Type Description
$url mixed

remove_trailing_slash

remove_trailing_slash( mixed $link )

returns: string

Pass links through untrailingslashit unless they are a single /

Name Type Description
$link mixed

unpreslashit

unpreslashit( mixed $path )

returns: string

Name Type Description
$path mixed

url_to_file_system

url_to_file_system( mixed $url )

returns: string

Takes a url and figures out its place based in the file system based on path NOTE: Not fool-proof, makes a lot of assumptions about the file path matching the URL path

Name Type Description
$url mixed

user_trailingslashit

user_trailingslashit( string $link )

returns: string

Pass links through user_trailingslashit handling query strings properly

Name Type Description
$link string