Advanced Custom Fields
Timber is designed to play nicely with the amazing Advanced Custom Fields plugin.
Getting data from ACF #
If you’ve worked with ACF before, you’re use to use get_field( 'my_acf_field' )
all the time. In Timber, getting data from ACF works the same way as getting WordPress’s native meta data in general:
Twig
{{ post.meta('my_acf_field') }}
PHP
$meta = $post->meta('my_acf_field');
Transform values to Timber/PHP objects #
Timber by default returns all field values as is based on the return type set in your ACF field setting.
But sometimes you might want to transform values directly into Timber/PHP objects. For example, if you have a relationship field, you might want to transform the values directly into Timber\Post
objects.
You can do so using the timber/meta/transform_value
filter:
functions.php
add_filter('timber/meta/transform_value', '__return_true');
Or you can use the transform_value
parameter to transform values on a field by field basis:
Twig
{{ post.meta('my_acf_field', { transform_value: true }) }}
PHP
$meta = $post->meta('my_meta_field', ['transform_value' => true]);
You can also use both the filter and parameter options at the same time to globally transform values and then opt-out on a field by field basis by setting transform_value
to false
.
The values of the following field types will be transformed into Timber/PHP objects when using transforms:
Field type | Returns |
---|---|
File | Timber\Attachment |
Image | Timber\Image |
Gallery | array of Timber\Post objects |
Date picker | DateTimeImmutable |
Date time picker | DateTimeImmutable |
Post object | array of Timber\Post objects |
Relationship | array of Timber\Post objects |
Taxonomy | array of Timber\Term objects |
User | array of Timber\User objects |
Unformatted values #
In ACF, all values are filtered. If you want to use unfiltered, raw values from the database, you’re probably used to using the third parameter for get_field()
, which is called $format_value
. This defaults to true. In Timber, you’d pass it like so:
Twig
{{ post.meta('my_acf_field', { format_value: false }) }}
PHP
$meta = $post->meta('my_acf_field', [
'format_value' => false,
]);
You can also use the faster raw_meta()
method, which accesses values directly from the database and bypasses any ACF filters:
Twig
{{ post.raw_meta('my_acf_field') }}
WYSIWYG Field (and other requiring text) #
<h3>{{ post.title }}</h3>
<div class="intro-text">
{{ post.meta('my_wysiwyg_field') }}
</div>
This will apply your expected paragraph breaks and other pre-processing to the text.
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 use Image ID
as the field's return value. The image object, URL, etc. should work, but it’s not as fool-proof.
The quick way (for most situations) #
<img src="{{ get_image(post.meta('hero_image')).src }}" />
The long way (for some special situations) #
This is where we’ll start in PHP.
single.php
$post = Timber::get_post();
$context = Timber::context([
'post' => $post,
]);
Timber::render('single.twig', $context);
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:
{% if post.meta('hero_image') %}
<img src="{{ get_image(post.meta('hero_image')).src | resize(500, 300) }}" />
{% endif %}
Gallery Field #
{% for image in post.meta('gallery') %}
<img src="{{ get_image(image) }}" />
{% endfor %}
Group Field #
{{ post.meta('group').first_field }}
{{ post.meta('group').second_field }}
or
{% set group = post.meta('group') %}
{{ group.first_field }}
{{ group.second_field }}
Relationship field #
The post data returned from a relationship field will not contain the Timber methods needed for easy handling inside of your Twig file. To get these, you’ll need to convert them into proper Timber\Post
objects using get_posts()
:
{% for item in get_posts(post.meta('relationship_field')) %}
{{ item.title }}
{# Do something with item #}
{% endfor %}
Repeater Field #
You can access repeater fields within Twig files:
single.twig
<h2>{{ post.title }}</h2>
<div class="my-list">
{% for item in post.meta('my_repeater') %}
<div class="item">
<h4>{{ item.name }}</h4>
<h6>{{ item.info }}</h6>
<img src="{{ get_image(item.picture).src }}" />
</div>
{% endfor %}
</div>
Nested Repeater fields #
When you run meta
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') %}
{{ item_outer.title }}
{% 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:
DON’T DO THIS: (Bad)
{% 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="{{ get_image(media_item.image).src }}" />
<p class="caption">{{ get_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="https://www.youtube.com/embed/{{media_item.youtube_id}}" frameborder="0" allowfullscreen></iframe>
<p class="caption">{{ media_item.caption }}</p>
{% endif %}
{% endfor %}
Repeater in Flexible Content Field #
Similar to nested repeaters, you should only call the meta
method once when you use a repeater field inside a flexible content field:
{% for media_item in post.meta('media_set') %}
{% if media_item.acf_fc_layout == 'image_set' %}
{% for image_item in media_item.image_set %}
<img src="{{ get_image(image_item.image).src }}" />
<p class="caption">{{ get_image(image_item.image).caption }}</p>
<aside class="notes">{{ image_item.notes }}</aside>
{% endfor %}
{% endif %}
{% endfor %}
Split Flexible Content Fields into includes / chunks #
We can break Flexible Content Fields into small blocks with include files utilizing the acf_fc_layout
value. This way you have more flexibility and reusability for included sections. For instance, this is a great way to build landing pages that re-use the same blocks in different configurations.
You could use a blocks subdirectory where you put all your Twig template files for your blocks. For example: wp-content/themes/example-theme/views/blocks.
{% for block in post.meta( 'blocks' ) %}
{{ include("blocks/#{ block.acf_fc_layout|sanitize ) }.twig", {
block: block
}) }}
{% endfor %}
The sanitize filter will slugify the block name.
Consider this example for a Flexible Content Field named Photo
with a text field named credit
. You would create a blocks/photo.twig file which is automatically included:
<p>Photo by: {{ block.credit }}</p>
To prevent errors with include files that can’t be found, you can optionally use the ignore_missing
parameter for include()
:
{% for block in post.meta( 'blocks' ) %}
{{ include("blocks/#{ block.acf_fc_layout|sanitize ) }.twig", {
block: block
}, ignore_missing = true) }}
{% endfor %}
Options Page #
PHP
$context['site_copyright_info'] = get_field('copyright_info', 'options');
Timber::render('index.twig', $context);
Twig
<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:
add_filter('timber/context', 'global_timber_context');
/**
* Filters global context.
*
* @param array $context An array of existing context variables.
* @return mixed
*/
function global_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 }}