You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
v2.0.9: keep the shortcode enabled on read, allow it outside the_content
Reviewed against ACF Pro 6.8.6 (current release, 2026-07-14) and WP 7.0.2. The
plugin worked, but two things were incomplete.
1. On a block theme the [acf] shortcode produced nothing outside the_content.
ACF gates that separately from enable_shortcode:
if ( wp_is_block_theme() ) {
if ( ! doing_filter( 'the_content' ) && ! apply_filters(
'acf/shortcode/allow_in_block_themes_outside_content', false ) ) {
return;
}
}
(acf_shortcode(), api-template.php:1025-1030). So a template part, a widget
or a block outside the content flow rendered empty, silently. Measured on
Twenty Twenty-Five, rendering from wp_footer: empty with the old behaviour,
field value now. Classic themes were never affected - ACF skips the branch.
This deliberately widens the scope to every rendering context, not just post
content; documented as such.
2. The setting was only written once, on acf/init. acf_get_setting() applies
acf/settings/{$name} on every read (api-helpers.php:101) and acf_shortcode()
consults it at render time (line 1017), so any later
acf_update_setting( 'enable_shortcode', false ) won silently. Verified with a
competing plugin writing false on acf/init priority 999: the shortcode
stopped rendering before, keeps working now. The write stays for readers of
acf_raw_setting().
Timing was ruled out rather than assumed: add_shortcode() runs unconditionally
at include time (line 1133) and the setting is read inside the callback, so
acf/init is early enough.
Docs gained what was missing: ACF only disables the shortcode by default for
installations first activated on 6.3+ (acf.php:239-244), so the plugin is a
no-op on older ones; the block-theme restriction and the consequence of lifting
it; and the further conditions ACF imposes - only registered fields, resolved
through the _fieldname reference meta, only bindings-capable types, only
publicly viewable posts, escaped output. Also recorded that
Requires Plugins: advanced-custom-fields-pro resolves against the installed
folder even though ACF Pro is not on wordpress.org.
tests/test-hooks.php: 10 cases, 2 red against 2.0.8.
Copy file name to clipboardExpand all lines: CLAUDE.md
+93-10Lines changed: 93 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,13 +2,34 @@
2
2
3
3
## Plugin Overview
4
4
5
-
Enables ACF (Pro)'s `[acf]` shortcode by switching the `enable_shortcode` setting to `true` on `acf/init`, so field values can be rendered inside WYSIWYG content.
5
+
Re-enables ACF (Pro)'s `[acf]` shortcode, which ACF turns off by default, and lifts the extra restriction ACF places on it under block themes.
6
6
7
-
-**Text Domain:**`jpkcom-acf-shortcode-enable` (no header declared, defaults to slug; only used by the shared updater)
7
+
-**Text Domain:**`jpkcom-acf-shortcode-enable` (no header declared, defaults to slug; the plugin has no translatable strings of its own)
-**Network:** not network-only (no `Network:` header)
11
11
12
+
> **Verified against ACF Pro 6.8.6** (current release at the time of writing, 2026-07-14) and WordPress 7.0.2. Every hook below carries the ACF file and line that consumes it — check those first when an ACF update lands.
13
+
14
+
---
15
+
16
+
## Why the shortcode is off in the first place
17
+
18
+
ACF does not simply ship `enable_shortcode => false`. The default is `true`, and ACF then flips it based on when ACF was *first activated* on the site (`acf.php:239-244`):
So on an installation whose ACF was first activated on **6.3 or later** the shortcode is off and this plugin is what turns it back on. On an older installation it is already on and **this plugin is a no-op**. Worth knowing before hunting for an effect that was never missing. On `posts.ddev.site`, `acf_get_version_when_first_activated()` returns `6.3.6`, so the plugin is load-bearing there — measured: `acf_get_setting( 'enable_shortcode' )` is `true` with the plugin and `false` without it, and the field value appears in the rendered page only with it.
30
+
31
+
Turning the shortcode on is, by design, taking back a hardening ACF introduced. That is the purpose of the plugin, not an oversight — but it should be a conscious decision per site.
32
+
12
33
---
13
34
14
35
## Architecture
@@ -19,19 +40,56 @@ Main file (jpkcom-acf-shortcode-enable.php)
|`acf/init`| — | Writes `enable_shortcode = true` so readers of `acf_raw_setting()` see it |
56
+
|`acf/settings/enable_shortcode` (`PHP_INT_MAX`) |`acf_get_setting()`, `includes/api/api-helpers.php:101`| Reports the shortcode as enabled on **every read**, which is the read `acf_shortcode()` performs |
57
+
|`acf/shortcode/allow_in_block_themes_outside_content` (`PHP_INT_MAX`) |`acf_shortcode()`, `includes/api/api-template.php:1025-1030`| Allows the shortcode outside `the_content` on block themes |
58
+
59
+
The `acf/init` callback is `function_exists()`-guarded and `jpkcom_`-prefixed (renamed from the previous generic `set_acf_settings`) to avoid global-namespace collisions.
60
+
61
+
### Why both the write and the filter
62
+
63
+
`acf_shortcode()` reads the setting at render time through `acf_get_setting()`, which applies `acf/settings/enable_shortcode` on every call. A one-shot `acf_update_setting()` on `acf/init` is therefore not authoritative: any later write wins. Measured against 6.8.6 with a competing plugin writing `false` on `acf/init` priority 999 — with only the write, the shortcode stopped rendering; with the filter it kept working. The write is kept because anything reading `acf_raw_setting( 'enable_shortcode' )` bypasses the filter.
64
+
65
+
Timing is not the problem, and was worth ruling out: `add_shortcode( 'acf', 'acf_shortcode' )` runs unconditionally at include time (`api-template.php:1133`) and the setting is only consulted inside the callback (line 1017), so `acf/init` is early enough.
66
+
67
+
### The block-theme restriction, and that we lift it
68
+
69
+
On a block theme `acf_shortcode()` returns nothing unless it is running inside the `the_content` filter, independently of `enable_shortcode`. Without the filter this plugin now sets, enabling the shortcode only covered post content on an FSE theme; a template part, a widget or a block outside the content flow rendered empty with no diagnostic.
33
70
34
-
The callback is `function_exists()`-guarded and `jpkcom_`-prefixed (renamed from the previous generic `set_acf_settings`) to avoid global-namespace collisions.
71
+
Measured on Twenty Twenty-Five, rendering `[acf]` from `wp_footer` (i.e. outside `the_content`):
72
+
73
+
| Theme | State | outside `the_content`| inside `the_content`|
74
+
|---|---|---|---|
75
+
| Twenty Twenty-Five (block) | 2.0.8 behaviour |**empty**| value |
76
+
| Twenty Twenty-Five (block) | 2.0.9 |**value**| value |
77
+
| bootscore-child (classic) | 2.0.9 | value | value |
78
+
79
+
Note the scope: this covers **every** rendering context, not only post content. It is a deliberate widening, chosen for this fleet; if a site should keep ACF's narrower default, remove that one filter.
80
+
81
+
---
82
+
83
+
## What "enabled" still does not get you
84
+
85
+
ACF places several further conditions on the shortcode. All of these bite silently — the shortcode returns an empty string, with no error and no log entry, except in previews where ACF substitutes an explanatory message:
86
+
87
+
-**Only registered fields.**`acf_shortcode()` forces `acf/prevent_access_to_unknown_fields` to `true` for the duration of the call, so a field name with no ACF definition yields nothing.
88
+
-**Resolved through the reference meta.**`acf_maybe_get_field( …, $strict = true )` looks the name up via `acf_get_meta_field()`, i.e. through the `_fieldname` → field-key meta. A value written with a bare `update_post_meta()` and no reference row is invisible to the shortcode, even when `get_field()` returns it.
89
+
-**Only bindings-capable field types** (`acf_field_type_supports( $type, 'bindings', true )`), and only fields whose `allow_in_bindings` is not false.
90
+
-**Only publicly viewable posts**, when a `post_id` other than the current one is passed (`acf/shortcode/prevent_access_to_fields_on_non_public_posts`).
91
+
-**Capability gates:** previews need `publish_posts` (`acf/shortcode/preview_capability`), AJAX requests need `edit_posts` (`acf/ajax/shortcode_capability`).
92
+
-**The value is escaped** unless `acf/shortcode/allow_unsafe_html` is filtered; when ACF strips something it fires `acf/removed_unsafe_html`.
35
93
36
94
---
37
95
@@ -85,12 +143,37 @@ Triggered by **pushing a `v*` tag**; the workflow creates the GitHub release aut
85
143
86
144
-`declare(strict_types=1)` in every PHP file
87
145
-`acf/init` callback prefixed + `function_exists()`-guarded (no collision)
146
+
- No input, no output, no database writes of its own
147
+
- Deliberately reverses two ACF hardening defaults — see the two sections above; that is the plugin's purpose, but it belongs in a per-site decision
148
+
- ACF's own output escaping is left intact: `acf/shortcode/allow_unsafe_html` is **not** filtered here
`Requires Plugins: advanced-custom-fields-pro` works even though ACF Pro is not on wordpress.org: WordPress resolves the slug against the installed plugin folder. Verified on WP 7.0.2 — `WP_Plugin_Dependencies::get_dependency_filepath()` returns `advanced-custom-fields-pro/acf.php`, `has_unmet_dependencies()` is `false`, and the name resolves to "Advanced Custom Fields PRO". Only `get_dependency_data()` returns `false`, which just means no "install dependency" link in the Plugins screen. The prerequisite is that ACF Pro sits in a folder with exactly that name.
154
+
155
+
---
156
+
157
+
## Tests
158
+
159
+
`tests/test-hooks.php` runs standalone — no WordPress and no ACF. It stubs the functions the main file touches at load time, requires the plugin, then asserts hook names and priorities and invokes each callback: both new filters and their return values, that the `acf/init` write still happens and touches only `enable_shortcode`, the updater bootstrap priority, and that the version constant matches the header. 10 cases; 2 of them fail against 2.0.8. CI runs it on every pull request and push to `main`.
160
+
161
+
```bash
162
+
php tests/test-hooks.php # exit 0 = green
163
+
```
164
+
165
+
What it cannot cover is whether ACF still honours these hooks, or the block-theme behaviour — both need a real instance with a block theme active. See the measured table above.
@@ -23,8 +23,20 @@ Shortcodes can be used within a WYSIWYG to display another field’s value.
23
23
24
24
Shortcodes can be used within a WYSIWYG to display another field’s value.
25
25
26
+
ACF disables the `[acf]` shortcode by default on any installation whose ACF was first activated on version 6.3 or later. This plugin turns it back on, and keeps it on: the setting is filtered on every read, so a later change from elsewhere cannot quietly switch it off again. On block themes it additionally lifts ACF's separate restriction that otherwise limits the shortcode to post content, so `[acf]` also works in template parts, widgets and blocks outside the content flow.
27
+
28
+
If your ACF installation predates 6.3, the shortcode is already enabled and this plugin changes nothing.
29
+
30
+
**Please note:** both of these are hardening defaults that ACF introduced deliberately. Enabling the shortcode is the point of this plugin — but it is worth deciding per site. ACF's output escaping is left untouched.
31
+
32
+
Verified against **ACF Pro 6.8.6**.
33
+
26
34
For more details visit: https://www.advancedcustomfields.com/resources/shortcode/
27
35
36
+
### Requirements for a shortcode to actually output something
37
+
38
+
Enabling the shortcode is necessary but not sufficient. ACF additionally requires that the field is **registered** (a bare post meta value is not enough — ACF resolves the name through its `_fieldname` reference row), that the field type supports bindings, and that the referenced post is publicly viewable. Previews need the `publish_posts` capability. When one of these is not met the shortcode outputs nothing, silently.
39
+
28
40
29
41
### Documentation
30
42
@@ -42,6 +54,13 @@ For more details visit: https://www.advancedcustomfields.com/resources/shortcode
42
54
43
55
## Changelog
44
56
57
+
### 2.0.9
58
+
* Fixed: on a block theme the `[acf]` shortcode produced nothing outside `the_content` — a template part, a widget or a block outside the content flow rendered empty, with no error and no log entry. ACF gates that separately from the shortcode setting (`acf_shortcode()`, api-template.php:1025-1030); the plugin now lifts it via `acf/shortcode/allow_in_block_themes_outside_content`. Measured on Twenty Twenty-Five: outside `the_content` empty before, the field value after. Classic themes were never affected — ACF skips the branch entirely
59
+
* Changed: the shortcode setting is now also filtered on read (`acf/settings/enable_shortcode`, at `PHP_INT_MAX`) instead of relying solely on the one-shot `acf_update_setting()` on `acf/init`. `acf_get_setting()` applies that filter on every read and `acf_shortcode()` consults it at render time, so any later `acf_update_setting( 'enable_shortcode', false )` — from another plugin, or from ACF itself in a future release — used to win silently. Verified against ACF Pro 6.8.6 with a competing plugin writing false on `acf/init` priority 999: the shortcode stopped rendering before, keeps working now. The `acf_update_setting()` call stays so that readers of the raw setting see the enabled state too
60
+
* Added: `tests/test-hooks.php` covers the hook surface and every callback; CI runs it on every pull request and push to `main`
61
+
* Docs: documented why the shortcode is off in the first place (ACF only disables it by default for installations first activated on 6.3 or later, `acf.php:239-244`, so this plugin is a no-op on older installs), the block-theme restriction and that lifting it widens the scope beyond post content, and the further conditions ACF places on the shortcode — only registered fields resolved through the `_fieldname` reference meta, only field types supporting bindings, only publicly viewable posts, and an escaped value unless `acf/shortcode/allow_unsafe_html` says otherwise
62
+
* Verified against ACF Pro **6.8.6** and WordPress 7.0.2. `Requires Plugins: advanced-custom-fields-pro` resolves against the installed plugin folder even though ACF Pro is not on wordpress.org — only the "install dependency" link is unavailable
63
+
45
64
### 2.0.8
46
65
* Fixed: the update manifest no longer reports `network: true` for this plugin. The generator defaulted a missing `Network:` header to true, while WordPress' own default for a missing header is "not network-only". Metadata only — WordPress derives network-only from the plugin header via `is_network_only_plugin()`, not from the update manifest
47
66
* CI: the lint and guard workflow now also runs on pushes to `main`. It only covered pull requests, so a direct push with bypass rights skipped every check
0 commit comments