Skip to main content

Custom Fields

This page shows you how to use custom fields from ACF, MetaBox, JetEngine, and Pods in your Unblock templates.

Supported plugins​

Unblock natively integrates with these custom field plugins — no configuration needed:

When one of these plugins is active, meta() automatically resolves values through that plugin's API. You don't need to change your expressions.

Basic usage​

Use meta('key') on any provider that supports custom fields:

{{ post.meta('event_date') }}
{{ user.meta('company') }}
{{ term.meta('icon') }}

Nested values work with dot access:

{{ post.meta('address').city }}
{{ post.meta('address').zip }}

Check if a field exists before using it:

{{ post.has_field('subtitle') ? post.meta('subtitle') : '' }}

has_field checks that the key is stored in the WordPress meta table, whatever its value. To test that a field has a value, use post.meta('subtitle') is not empty.

meta('key') vs meta.key​

meta('key') resolves a single field — one integration hook call.

meta.key resolves all meta fields for the object, triggering the integration hook for each one. With plugins like ACF active, this adds up. It only lists the keys stored in the WordPress meta table, and meta or raw_meta without a key gives them all as a map.

Prefer meta('key') when you need specific fields.

{{ post.meta('event_date') }}      {# Recommended — single hook call #}
{{ post.meta.event_date }} {# Resolves all fields first #}

raw_meta('key') bypasses integrations entirely and reads the value WordPress stores. It is still escaped as text:

{{ post.raw_meta('field_name') }}

Options​

Options are passed as a hash, the second argument of meta(), as in Timber.

return_format​

Controls how choice fields return their value: select, checkbox, radio, button_group and true_false with ACF; select, select_advanced, radio, checkbox_list and button_group with Meta Box; select, radio and checkbox with JetEngine.

{{ post.meta('color') }}                                {# Stored value: "red" #}
{{ post.meta('color', {return_format: 'label'}) }} {# Display label: "Red" #}
{{ post.meta('color', {return_format: 'value'}) }} {# Stored value: "red" #}

separator​

For multi-value choice fields, controls the string joining selected values. Defaults to ', '. It only applies together with return_format.

{{ post.meta('tags', {return_format: 'label', separator: ' | '}) }}
{# Output: "Design | Code | Writing" #}

format_value​

ACF only. Controls whether ACF formats the value or returns the raw database value.

{{ post.meta('wysiwyg_field') }}                            {# Formatted HTML #}
{{ post.meta('wysiwyg_field', {format_value: false}) }} {# Raw content #}

Defaults to true. Set to false when you need the raw stored value — useful for fields where ACF's formatting gets in the way. With transform_value, it is ignored, as in Timber.

transform_value​

Converts relational fields to objects you can chain, and date fields to dates, as Timber's option of the same name. Defaults to false: see Relational fields.

Relational fields​

When a custom field stores a relationship to another object (post, user, term, or attachment), meta() returns what the field plugin returns, as in Timber: with ACF, the field's return format decides (an array, an ID, a URL, or a post object).

Pass {transform_value: true} to get objects you can chain instead, whatever the return format:

{{ post.meta('hero_image').url }}                                  {# ACF image returning an array #}
{{ post.meta('hero_image', {transform_value: true}).src('large') }} {# Image object #}

To convert everywhere on the site, as Timber's timber/meta/transform_value filter does:

add_filter( 'unblock/data/meta_transform_value', '__return_true' );

{transform_value: false} still turns the conversion off for one call. Without the option, Meta Box returns rwmb_meta()'s value, Pods its own field value, and JetEngine the stored IDs.

With transform_value, date fields also become dates, as Timber does with ACF: they print with the site's date format, take the date filters, and compare with date(), as in Twig:

{% if post.meta('event_date', {transform_value: true}) >= date() %}

Supported field types​

PluginField typeResolves to
ACFPost Object, RelationshipPost
ACFUserUser
ACFTaxonomyTerm
ACFImage, File, GalleryImage / Attachment
ACFDate Picker, Date Time PickerDate
MetaBoxPostPost
MetaBoxImage Advanced, File AdvancedImage / Attachment
MetaBoxDate, DatetimeDate
JetEnginePostsPost
JetEngineMedia, GalleryImage / Attachment
JetEngineDate, DatetimeDate
PodsRelationship (post type)Post
PodsRelationship (user)User
PodsRelationship (taxonomy)Term
PodsFile, AvatarImage / Attachment
PodsDate, Date / TimeDate

These are the types transform_value converts.

Single value​

When a field stores one object, transform_value makes it a full provider — chain any field it supports:

{{ post.meta('related_post', {transform_value: true}).title }}
{{ post.meta('related_post', {transform_value: true}).thumbnail.src('medium') }}
{{ post.meta('team_lead', {transform_value: true}).name }}
{{ post.meta('primary_category', {transform_value: true}).link }}
{{ post.meta('hero_image', {transform_value: true}).src('large') }}

An ACF Post Object field returning a post object chains without the option: post.meta('related_post').title.

Multiple values​

Fields configured for multiple values (ACF Relationship, Gallery, MetaBox Image Advanced, etc.) return an array. Use a Loop block to iterate:

{# In a Loop block with source: post.meta('gallery', {transform_value: true}) #}
{{ item.src('medium') }}
{{ item.alt }}

You can also access a single item by index:

{{ post.meta('gallery', {transform_value: true})[0].src('large') }}

A JetEngine Posts field returns one post, or a list when it allows multiple posts. JetEngine media and gallery fields saved as URLs keep their URLs.

ACF options pages​

ACF is the only integration that supports option meta. Fields registered on ACF options pages are available through site.option(), as in Timber:

{{ site.option('company_phone') }}
{{ site.option('footer_logo', {transform_value: true}).src }}
{{ site.option('social_links') }}

ACF options fields need no allowlist. Any other option name must be allowed first, see Site options.

Common mistake

site.option() only reads ACF options pages. MetaBox, Pods, and JetEngine fields are not available through site.option() — use post.meta(), user.meta(), or term.meta() for those plugins.

Resolution chain​

When multiple custom field plugins are active, Unblock queries them in a fixed priority order:

PriorityPluginScope
10ACFpost, user, term, option
20MetaBoxpost, user, term
30Podspost, user, term
40JetEnginepost, user, term

The first plugin that recognizes the field returns the value — the rest are skipped. If no plugin claims the field, Unblock falls back to WordPress get_metadata().

This means if the same meta key is registered in both ACF and MetaBox, ACF wins. In practice this rarely matters since most sites use a single custom field plugin.

Security​

Private meta keys (starting with _) are blocked by default, to read and to sort or filter a loop on. WooCommerce's product keys shown to every visitor are open: _price, _regular_price, _sale_price, _sku, _stock, _stock_status, _wc_average_rating, _wc_review_count, the sale dates and the size, so a product loop sorts by price. To allow other private keys, use the unblock/data/allowed_meta_keys filter.

On users, session_tokens, user_activation_key and user_pass are blocked the same way.

User meta is private: it shows nothing until its key is opened in Settings > Privacy, whoever wrote the expression, except the first name, last name, nickname, bio and the visitor's own meta.

A user or post returned by a field plugin keeps only what the visitor may see: a user field in ACF's « Array » format, or a Pods relation, loses its private details, and a user or post that is private is left out.

Next steps​