NAV
PHP Twig HTML

Getting Started

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:

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

$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

$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 %}

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)

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


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!

Continue ahead in Part 2

Getting Started: Themeing

Your first Timber project

Let’s start with your single post

Find this file: (btw if you have no idea what I’m talking about you should go to the setup article html 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: html <h1 class="article-h1"><a href="{{post.permalink}}">{{post.title}}</a></h1>

It gets better. Let’s explain some other concepts. html {% 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 Documentationn 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:

$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

$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

$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
    $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
    $args = 'post_type=movies&numberposts=8&orderby=rand';
    $context['posts'] = Timber::get_posts($args);
Use Post ID numbers
    $ids = array(14, 123, 234, 421, 811, 6);
    $context['posts'] = Timber::get_posts($ids);

Render

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.

Tools for Working with Twig

Text editor add-ons

Other

JavaScript

Guides

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.

/* 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)}}" />

Repeater field

You can access repeater fields within in twig files: handlebars {# 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 outer_item.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

    $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
    $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

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

function mytheme_timber_context( $context ) {
    $context['option'] = 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:

/* 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’.

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

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


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. html {{post|print_r}}

Extending Timber

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.

Extending Objects

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…

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:

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.


    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:


/* functions.php */

add_filter('get_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());
  /**
   * Deprecated: Twig_Filter_Function, use Twig_SimpleFilter
   * http://twig.sensiolabs.org/doc/deprecated.html#filters
   * $twig->addFilter('whatever', new Twig_Filter_Function('my_whatever'));
   */
  $twig->addFilter('myfoo', new Twig_SimpleFilter('myfoo', 'my_whatever'));
  return $twig;
}

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

Following is shortened version of Timber Starter Theme class definition using methods to add filters.


/* functions.php */

class StarterSite extends TimberSite {

  function __construct() {
    add_filter( 'timber_context', array( $this, 'add_to_context' ) );
    add_filter( 'get_twig', array( $this, 'add_to_twig' ) );
    parent::__construct();
  }

  function add_to_context( $context ) {
    $context['menu'] = new TimberMenu();
    $context['site'] = $this;
    return $context;
  }

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

  function add_to_twig( $twig ) {
    /* this is where you can add your own functions to twig */
    $twig->addExtension( new Twig_Extension_StringLoader() );
    /**
     * Deprecated: Twig_Filter_Function, use Twig_SimpleFilter
     * http://twig.sensiolabs.org/doc/deprecated.html#filters
     * $twig->addFilter( 'whatever', new Twig_Filter_Function( 'whatever' ) );
     */
    $twig->addFilter('whatever', new Twig_SimpleFilter('whatever', array($this, 'my_whatever')));
    return $twig;
  }

}

new StarterSite();

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

Twig offers a variety of filters to transform text and other information into the desired output. If you wish to create your own custom filters visit Extending Timber.

Timber includes following filters out-of-the box 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>

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):

    $context = Timber::get_context();
    $context['posts'] = Timber::get_posts();
    $context['pagination'] = Timber::get_pagination();
    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 pagination.prev %}
        <a href="{{pagination.prev.link}}" class="prev {{pagination.prev.link|length ? '' : 'invisible'}}">Prev</a>
    {% endif %}
    <ul class="pages">
        {% for page in 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 pagination.next %}
        <a href="{{pagination.next.link}}" class="next {{pagination.next.link|length ? '' : 'invisible'}}">Next</a>
    {% endif %}
</div>

What if I’m not using the default query?

So let’s say you want to paginate things on page-events.php where you list items from a custom post type of event. Because the default query is just the request for the page’s info pagination will not work. You need to make it the default query by using the dreaded query_posts like so:

    global $paged;
    if (!isset($paged) || !$paged){
        $paged = 1;
    }
    $context = Timber::get_context();
    $args = array(
        'post_type' => 'event',
        'posts_per_page' => 5,
        'paged' => $paged
    );
    /* THIS LINE IS CRUCIAL */
    /* in order for WordPress to know what to paginate */
    /* your args have to be the defualt query */
        query_posts($args);
    /* make sure you've got query_posts in your .php file */
    $context['posts'] = Timber::get_posts();
    $context['pagination'] = Timber::get_pagination();
    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'] = Timber::get_posts(); $context['pagination'] = Timber::get_pagination(); 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)

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:

$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:

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:

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:

/* 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:

/* 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…

/* 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 implements 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 Timber::add_route(‘blog/:name’, function($params){ $query = 'posts_per_page=3&post_type=’.$params['name’]; Timber::load_template('archive.php’, $query); });

Timber::add_route('blog/:name/page/:pg’, function($params){ $query = 'posts_per_page=3&post_type=’.$params['name’].’&paged=’.$params['pg’]; Timber::load_template('archive.php’, $query); }); ”`

add_route

add_route($pattern, $callback)
Usage:

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

Timber::add_route('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
    Timber::load_template('archive.php', $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_template

load_template($php_file, $query = null, $force_header = 200, $template_params)
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!)

$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

$force_header Send an optional header. Defaults to 200 for 'Success/OK’

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

/* functions.php */

Timber::add_route('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';
    Timber::load_template('archive.php', $query, 200, $params);
});
/* archive.php */

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

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)

# sidebar.php #
$context = array();
$context['widget'] = my_function_to_get_widget();
$context['ad'] = my_function_to_get_an_ad();
Timber::render('sidebar.twig', $context);
# 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 %}
# 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:

$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…

/* 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..

/* 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:
/* functions.php */
Timber::$dirname = 'twigs';
You can also send an array with fallbacks:
/* functions.php */
Timber::$dirname = array('templates', 'templates/shared/mods', 'twigs', 'views');

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

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

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

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:


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:

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:

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.

WP 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:

/* 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:

/**
 * @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:

/* 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:

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
}
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…

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…

$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 }}

Your magic number is: {{ number }}

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

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: “` // 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

TimberArchives

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

Class: TimberArchives

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

Example
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>

This class extends \TimberCore

TimberComment

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 \TimberUser
avatar bool/mixed/string
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

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: \TimberUser

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" />

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>

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>

Class: TimberComment

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.

Example
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>

This class extends \TimberCore

This class implements \TimberCoreInterface

TimberImage

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 \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 string $id the numeric WordPress id of a post
link void
parent bool/\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: \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 use src() instead

get_url( )

returns: 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/\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="" )

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 use src() instead

url( )

returns: 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

Class: TimberImage

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.

Example
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>

This class extends \TimberPost

This class implements \TimberCoreInterface

TimberMenu

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
<?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.get_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.get_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( int/string $slug )

returns: void

Name Type Description
$slug int/string

find_parent_item_in_menu

find_parent_item_in_menu( array $menu_items, int $parent_id )

returns: \TimberMenuItem/null

Name Type Description
$menu_items array
$parent_id int

get_items

get_items( )

returns: array

Class: TimberMenu

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

Example
PHP
<?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.get_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.get_link}}">{{child.title}}</a></li>
              {% endfor %}
             </ul>
          {% endif %}
          </li>
   {% endfor %}
   </ul>
</nav>

This class extends \TimberCore

TimberMenuItem

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( \TimberMenuItem $item )

returns: void

Name Type Description
$item \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>

## title
`title( )`

**returns:** `string` the public label like Foo

Gets the public label for the menu item

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

Class: TimberMenuItem

This class extends \TimberCore

This class implements \TimberCoreInterface

TimberPost

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
<?php
// single.php, see connected twig example
$context = Timber::get_context();
$context['post'] = new TimberPost(); // It's a new TimberPost 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 \TimberUser/bool A TimberUser object if found, false if not
categories array of TimberTerms
category \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/\TimberPost
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 \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 TimberPost();
    $other_post = new TimberPost($random_post_id);

__toString

__toString( )

returns: string

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

author

author( )

returns: \TimberUser/bool A TimberUser 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>

categories

categories( )

returns: array of TimberTerms

Get the categoires on a particular post

category

category( )

returns: \TimberTerm/null

Returns a category attached to a post

children

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

returns: array

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

Name Type Description
$post_type string 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
$childPostClass bool/string/bool optional a custom post class (ex: 'MyTimberPost’) to return the objects as. By default (false) it will use TimberPost::$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 %}

comments

comments( int $count, string $order="wp", string $type="comment", string $status="approve", string $CommentClass="TimberComment" )

returns: bool/array

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

Name Type Description
$count 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 )

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
Twig
    <div class="article">
        <h2>{{post.title}}</h2>
        <div class="content">{{ post.content }}</div>
    </div>

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

format

format( )

returns: mixed

get_comment_count

get_comment_count( )

returns: int the number of comments on a post

get_content

get_content( int $len, int $page )

returns: string

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

Name Type Description
$len 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_display_date

DEPRECATED since 0.20.0

get_display_date( string $use="post_date" )

returns: string

Get the human-friendly date that should actually display in a .twig template

Name Type Description
$use string

get_field

get_field( string $field_name )

returns: mixed

Name Type Description
$field_name string

get_image

get_image( string $field )

returns: \TimberImage

Name Type Description
$field string

get_method_values

get_method_values( )

returns: array

get_paged_content

get_paged_content( )

returns: string

get_pagination

get_pagination( )

returns: array

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

get_post_type

get_post_type( )

returns: mixed

Here is my summary

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 $strip=true )

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 Strip tags? yes or no. tell me!
Twig
    <p>{{post.get_preview(50)}}</p>

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: \TimberUser/bool A TimberUser 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_cat=false )

returns: mixed

Name Type Description
$in_same_cat bool

paged_content

paged_content( )

returns: string

pagination

pagination( )

returns: array

parent

parent( )

returns: bool/[\TimberPost](#class-timberpost)

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

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

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_cat=false )

returns: mixed

Get the previous post in a set

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

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 )

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)?

thumbnail

thumbnail( )

returns: \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>

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_wp_link_page( int $i )

returns: string

Name Type Description
$i int

Class: TimberPost

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.

Example
PHP
<?php
<?php
// single.php, see connected twig example
$context = Timber::get_context();
$context['post'] = new TimberPost(); // It's a new TimberPost 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>

This class extends \TimberCore

This class implements \TimberCoreInterface

TimberSite

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
url 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

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

url( )

returns: string

Class: TimberSite

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

Example
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

This class extends \TimberCore

This class implements \TimberCoreInterface

TimberTerm

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 strng 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: \static

Name Type Description
$tid mixed
$taxonomy mixed

get_page

DEPRECATED 0.20.0 this was a dumb idea

get_page( int $i )

returns: string

Name Type Description
$i int

link( )

returns: string

meta

meta( string $field_name )

returns: string

Name Type Description
$field_name string

path

path( )

returns: string

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

url

DEPRECATED 0.21.9 use TimberTerm::link() instead

url( )

returns: string

Class: TimberTerm

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

Example
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>

This class extends \TimberCore

This class implements \TimberCoreInterface

TimberTheme

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

Class: TimberTheme

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:

Example
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>

This class extends \TimberCore

TimberUser

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

PHP
<?php
$context['current_user'] = new TimberUser();
$context['post'] = new TimberPost();
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
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/int/bool $uid=false )

returns: void

Name Type Description
$uid bool/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

DEPRECATED 0.21.9

get_link( )

returns: string The link to a user’s profile page

get_meta_field

get_meta_field( string $field_name )

returns: mixed

Name Type Description
$field_name string

get_path

DEPRECATED 0.21.8

get_path( )

returns: string ex: /author/lincoln

DEPRECATED 0.21.8

get_permalink( )

returns: 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

DEPRECATED 0.21.8

permalink( )

returns: string

slug

slug( )

returns: string ex baberaham-lincoln

Class: TimberUser

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

Example
PHP
<?php
$context['current_user'] = new TimberUser();
$context['post'] = new TimberPost();
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

This class extends \TimberCore

This class implements \TimberCoreInterface

TimberHelper

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 \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

close_tags( mixed $html )

returns: string

Name Type Description
$html mixed

download_url

DEPRECATED 0.18.0

download_url( mixed $url, mixed $timeout=300 )

returns: void

Name Type Description
$url mixed
$timeout mixed

error_log

error_log( mixed $arg )

returns: void

Name Type Description
$arg mixed that you want to error_log

function_wrapper

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

returns: \TimberFunctionWrapper

Name Type Description
$function_name mixed
$defaults array
$return_output_buffer bool

get_comment_form

DEPRECATED 0.21.8 use `{{ function(‘commentform’) }}` 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

DEPRECATED 0.18.0

get_current_url( )

returns: mixed

get_full_path

DEPRECATED 0.18.0

get_full_path( mixed $src )

returns: mixed

Name Type Description
$src mixed

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_params

DEPRECATED 0.18.0

get_params( mixed $i=-1 )

returns: mixed

Name Type Description
$i mixed

get_path_base

DEPRECATED 0.18.0

get_path_base( )

returns: mixed

get_post_by_meta

DEPRECATED 0.20.0

get_post_by_meta( mixed $key, mixed $value )

returns: int

Name Type Description
$key mixed
$value mixed

get_posts_by_meta

DEPRECATED 0.20.0

get_posts_by_meta( mixed $key, mixed $value )

returns: array/int

Name Type Description
$key mixed
$value mixed

get_rel_path

DEPRECATED 0.18.0

get_rel_path( mixed $src )

returns: mixed

Name Type Description
$src mixed

get_rel_url

DEPRECATED 0.18.0

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

returns: mixed

Name Type Description
$url mixed
$force bool

get_term_id_by_term_taxonomy_id

DEPRECATED 0.21.8

get_term_id_by_term_taxonomy_id( mixed $ttid )

returns: mixed

Name Type Description
$ttid 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_external

DEPRECATED 0.18.0

is_external( mixed $url )

returns: bool

Name Type Description
$url mixed

is_local

DEPRECATED 0.18.0

is_local( mixed $url )

returns: bool

Name Type Description
$url mixed

is_true

is_true( mixed $value )

returns: bool

Name Type Description
$value mixed

is_url

DEPRECATED 0.18.0

is_url( mixed $url )

returns: bool

Name Type Description
$url 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( \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 \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

paginate_links( string $args="" )

returns: array

Name Type Description
$args string

prepend_to_url

DEPRECATED 0.18.0

prepend_to_url( mixed $url, mixed $path )

returns: void

Name Type Description
$url mixed
$path mixed

preslashit

DEPRECATED 0.18.0

preslashit( mixed $path )

returns: void

Name Type Description
$path mixed

remove_double_slashes

DEPRECATED 0.18.0

remove_double_slashes( mixed $url )

returns: void

Name Type Description
$url mixed

start_timer

start_timer( )

returns: \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, \callable $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 \callable Callback that generates the data that’s to be cached
$transient_time mixed
$lock_timeout mixed
$force bool
PHP
<?php
    $favorites = Timber::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::context['favorites'] = $favorites;
    Timber::render('single.twig', $context);

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

Class: TimberHelper

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