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.meta('my_wysiwyg_field') }}

This will apply your expected paragraph breaks and other pre-processing to the text. In the past we used {{ post.get_field('my_wysiwyg_field') }}, but this is now deprecated. Use {{ post.meta('my_wysiwyg_field') }}.

Image field

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="{{ Image(post.meta('hero_image')).src }}" />

The long way (for some special situations)

This is where we’ll start in PHP.

/* single.php */
$post = new Timber\Post();
if (isset($post->hero_image) && strlen($post->hero_image)){
	$post->hero_image = new Timber\Image($post->hero_image);
$data = Timber::get_context();
$data['post'] = $post;
Timber::render('single.twig', $data);

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

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

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

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

Group field

{{ post.meta('group').first_field }}
{{ post.meta('group').second_field }}


{% set group = post.meta('group') %}
{{ group.first_field }}
{{ group.second_field }}

Repeater field

You can access repeater fields within twig files:

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

Nested Repeater fields

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

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

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

{% endfor %}

Troubleshooting Repeaters

A common problem in working with repeaters is that you should only call the meta 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 meta on the inner field). More:


{% for gear in post.meta('gear_items') %}
    <h3> {{ gear.brand_name }} </h3>
    {% for gear_feature in gear.meta('features') %}
        <li> {{ gear_feature }} </li>
    {% endfor %}
{% endfor %}

DO THIS: (Good)

{% for gear in post.meta('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.meta('media_set') %}
	{% if media_item.acf_fc_layout == 'image_set' %}
		<img src="{{ Image(media_item.image).src }}" />
		<p class="caption">{{ Image(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="{{ 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);

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['options'] = get_fields('option');
    return $context;

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

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

Getting ACF info

You can grab specific field label data like so:

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

Query by custom field value

This example that uses a WP_Query array 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);