ACF Values
Use Timber's existing bridge for presentation fields, per call or through the opt-in setting in config/timber.php. PressGang adds no replacement bridge. Existing themes need no changes on upgrade. ACF remains optional; these ACF examples require it, while ordinary Timber metadata access still works without it.
$rows = $post->meta( 'flexible_content', [ 'transform_value' => true ] );ACF recursively invokes Timber's replacement formatters, including inside groups, repeaters and flexible content. Arrays retain their row/layout structure; object leaves use the existing Timber class maps. Opt in the whole root only when its consumers expect the transformed types.
Return types and boundaries
Verified against Timber 2.5.1 and ACF 6.8.9:
post_object
Timber Post, or PostArrayObject when multiple
relationship
Timber PostArrayObject
image
Timber Image
file
Timber Attachment (or attachment subclass)
gallery
PostArrayObject containing attachment/image posts
taxonomy
Timber Term for select/radio; array of terms otherwise
user
Timber User, or array of users when multiple
date_picker, date_time_picker
DateTimeImmutable
Object-like fields generally return false when empty. Collections are not native PHP arrays. Check empty/deleted selections and callers' return types when migrating; do not assume exact parity with the legacy mapper.
ACF's standard taxonomy type is taxonomy, not term. No custom term type was registered in the audited penarc site. The mapper's historical term case remains unchanged for other consumers.
The switch applies to Timber metadata access, not ordinary get_field(), get_sub_field() or options-page reads. Options and block contexts still use TimberMapper. The options cache remains theme_option_field_objects in group pressgang, invalidated on acf/save_post. It holds ACF-formatted, Timber-unmapped field objects; it is not an unformatted-ID cache. Nested options remain unchanged.
Known upstream limitations
Use one formatted mode per field/entity per request, across PHP, Twig and third-party ACF reads. Include nested sub-fields in that audit.
ACF 6.8.9 shares its formatted-value cache between normal ACF and transformed reads. In live probes, a normal nested-image read followed by a transformed read returned an ACF image array both times. Reversing the order returned a Timber Image both times. transform_value => false alone does not undo a transformed read. Normal WP_Post arrays may still become arrays of Timber posts through Timber's fallback conversion, so successful relationship conversion does not prove that all nested field types escaped the cache issue.
Timber 2.5.1 also lacks exception-safe restoration of its temporary ACF filters. A throwing formatter left the original ACF image formatter removed and Timber's replacement installed. Catching the exception does not make subsequent ACF reads safe. Avoid reentrant transformed reads inside formatting callbacks; PressGang does not provide scope management or vendor patches.
Keep the existing explicit mapper when a request needs normal ACF arrays/date strings as well as Timber objects. These version-specific limitations should be rechecked on dependency upgrades; this change does not fix them upstream.
Opting out and reading IDs
Transformation remains off by default. Opt in or out for a selected root at its meta() call, or enable transform_acf_values after auditing the child theme: query IDs, date strings and image/file arrays have existing consumers. There is no per-type or nested-path switch in this policy. Keep a mixed-use root untransformed and use explicit mapping where necessary.
For normal ACF formatting, use ordinary meta() or transform_value => false consistently from the start of the request. For IDs/storage-oriented input that must coexist with transformed reads, use one of these:
format_value => false alone loses to an enabled transform: Timber calls get_field(..., true) in that path. Storage values are not normal ACF-formatted values: a raw repeater root can be a row count, and unformatted ACF sub-fields can be keyed by field keys. Do not substitute either escape into a template expecting image arrays or formatted dates.
Set transform_acf_values to false to return to the parent default. Remove any local global-transform filter registration too; the setting does not override it.
Worked penarc migrations
These are opt-in edits for a consuming theme, not changes applied to penarc by this parent-theme documentation update. Audit all reads of each selected field first. In the inspected source, these four fields feed presentation getters; query builders using their names compare the current post ID rather than reading these field values. Request-wide plugin behaviour still needs a rendered-page check.
The existing shape is:
These replacements preserve the getters' native-array contracts. Timber performs the conversion; the remaining normalization materializes collections, removes null members for missing targets, and reindexes the result. This matters in the actual dataset: post 20166's other_arc_staff collection contains a null member. ?: [] handles empty fields, and the array branch also accepts Timber's fallback conversion of an already formatted WP_Post array. That branch does not fix the cache problem for other field types.
src/Traits/HasAuthors.php:
src/Traits/HasArticleRelationships.php:
src/Controllers/ResearchProjectController.php:
Remove the unused use PressGang\ACF\TimberMapper; import from each edited file. Do not pass the transformed collection back into to_timber_posts(): that helper accepts native arrays and otherwise returns an empty array. It remains supported, with its existing filtering and reindexing behaviour, for unmigrated consumers.
These examples were compared with the existing helper on live selections: staff_authors on post 23295, contact_staff_members on 10460, other_arc_staff on 20166 and arc_lead on 20169. All four preserved the selected IDs and order after normalization. The comparison isolated the ACF value cache between modes; it does not demonstrate safe mixed-mode reads.
If callers accept collections and selections cannot contain missing targets, a direct transformed read with ?: [] and an iterable return type can omit normalization. That is a deliberate contract change, not the compatibility example above. Keeping the existing mapper is also a supported choice.
For an audited nested presentation root, Twig can use the same upstream argument:
All supported descendants transform, including dates and files. Do not turn this on until the macros receiving those rows expect those types.
Evidence and performance
Live WP-CLI probes on penarc (PHP 8.3.8, Timber 2.5.1, ACF 6.8.9) demonstrated:
Post 17, flexible_content[2].people
PostArrayObject, 9 StaffMember objects
Post 65513, flexible_content[0].cards[0].image
Timber Image, ID 49660
Post 20184, lead_collaborators[0].partner_organisation
Timber Term, ID 3345
Post 66610, themes
false; stored fixture is empty
Existing themes schema with an in-memory row
PostArrayObject containing ResearchProject 20184
In-memory group containing relationship/date
PostArrayObject and DateTimeImmutable
Independent probes started with a fresh request-local ACF value store; the in-memory fixtures did not write content. Do not clear ACF's caches in application code as a workaround.
The database contained 1,524 published publications and 46 events. Three fields on 24 publications (72 calls) took a median 1.799 ms for warm normal ACF reads, versus 3.623 ms transformed. Across all publications (4,572 calls), warm times were 118.715 ms versus 225.346 ms. Both warm modes issued zero additional SQL. Timber performs 9 field-type lookups and 36 filter mutation calls for each transformed read, including scalar fields and cache hits.
A cold source-post read of a 27-item relationship used 2 queries with normal ACF versus 28 transformed; iterating the transformed collection added 6 queries. The numeric-ID path eagerly creates posts. Source-post metadata priming alone does not eliminate related-object N+1 queries. Transform only fields needed for the displayed page and measure rendered listings, not just warmed CLI loops.
See Timber's ACF documentation for the upstream API. The runtime findings above qualify its per-call opt-out advice for the versions tested.
Configuring an audited child theme
Keep existing Twig settings when adding this option to config/timber.php (child configuration replaces the parent file):
The setting defaults to false. Replace a local TransformAcfValues snippet and its registration with this configuration. Call PressGang\Bootstrap\Config::clear_cache() after changing configuration. Transformation uses Timber's existing bridge and class maps; ACF remains optional. Hook registration is harmless without ACF and no ACF functions are called by this configuration handler.
Missing references may still produce null collection entries. Keep presentation checks for missing values; this option does not add filtering or repair data.
Use raw_meta() for stored query IDs. Setting transform_value to false per call opts out of Timber transformation, but ACF's shared formatted cache still applies as described above. Disable the configuration option to stop registering its policy; false does not override filters installed by another plugin or theme.
Last updated