Problem
Helpers::formatFields( 'option' ) eagerly formats every field on the options page. When one of them is a post_object pointing at a form (wpforms, contact-form-7), fieldFormatter() runs it through do_shortcode() and returns fully rendered HTML.
timber-kit.md § Override timber_context() documents Helpers::formatFields('option') as the canonical way to source global options. So the render happens on every single request, on every page — including pages that contain no form at all.
Measured on a production site (oekoplan, timber-kit v1.26.0):
$x = Helpers::formatFields("option");
gettype($x["career"]["form"]) → string
strlen(...) → 6050
substr(..., 0, 60) → <div class="wpforms-container wpforms-render-modern" id="wpforms-1991…
Two consequences:
- 6 kB of form HTML is rendered per request just to build the global context, then thrown away on every page that doesn't use it.
- WPForms registers its CSS/JS globally. Rendering the shortcode triggers the plugin's asset enqueue, so
wpforms-*.css, the layout stylesheets and jQuery land in <head> of every page on the site. Verified by diffing rendered HTML before/after the theme adopted StarterBase — 6 stylesheets + jQuery appeared on pages with no form.
There is also a correctness angle: a form rendered into the global context means duplicate id="wpforms-form-…" in the DOM if the same form is rendered again by the page itself.
Where it comes from
src/Helpers.php around line 1216:
} elseif ( $field['value']->post_type === 'wpforms' ) {
if ( $is_preview ) {
$field['value'] = '[wpforms id="' . $field['value']->ID . '"]';
} else {
$field['value'] = do_shortcode( '[wpforms id="' . $field['value']->ID . '"]' );
}
}
The field_formatter_* filter at line ~1303 fires after this, so a downstream filter can replace the output but cannot prevent the render or the asset enqueue.
Current workaround (and why it isn't the fix)
Passing the existing second parameter:
$global_fields = Helpers::formatFields( 'option', true ); // $is_preview
This works — verified that nothing else changes:
links + footer + announcement with preview=true vs without → byte-identical
career.form with preview=true → [wpforms id="1991"]
But $is_preview means "we are in the editor preview". Using it to mean "don't render heavy embeds" overloads a flag with unrelated semantics, and any future behaviour keyed on $is_preview would silently start applying to normal front-end requests.
Proposed solutions
Roughly in order of preference:
- Lazy
post_object formatting. Return a small value object that renders the shortcode on string conversion. Twig calling {{ content.form }} renders it; a context build that never touches it pays nothing. No API break, fixes every call site at once.
- A dedicated parameter/flag —
formatFields( $post, $is_preview, $render_embeds = true ), or an options array. Explicit, but every caller has to know to pass it.
- A filter that fires before the render, e.g.
field_formatter_pre_post_object, so a theme can opt out per field. Smallest change, but leaves the default behaviour surprising.
Happy to open a PR for whichever direction you prefer — (1) reads as the real fix to me.
Context
Found while migrating a theme to StarterBase. Reported from oekoplan (~/Sites/wordpress/oekoplan), timber-kit v1.26.0, ACF Pro 6.8.6, WPForms 2.0.0.2.
Problem
Helpers::formatFields( 'option' )eagerly formats every field on the options page. When one of them is apost_objectpointing at a form (wpforms,contact-form-7),fieldFormatter()runs it throughdo_shortcode()and returns fully rendered HTML.timber-kit.md§ Overridetimber_context()documentsHelpers::formatFields('option')as the canonical way to source global options. So the render happens on every single request, on every page — including pages that contain no form at all.Measured on a production site (oekoplan, timber-kit v1.26.0):
Two consequences:
wpforms-*.css, the layout stylesheets and jQuery land in<head>of every page on the site. Verified by diffing rendered HTML before/after the theme adopted StarterBase — 6 stylesheets + jQuery appeared on pages with no form.There is also a correctness angle: a form rendered into the global context means duplicate
id="wpforms-form-…"in the DOM if the same form is rendered again by the page itself.Where it comes from
src/Helpers.phparound line 1216:The
field_formatter_*filter at line ~1303 fires after this, so a downstream filter can replace the output but cannot prevent the render or the asset enqueue.Current workaround (and why it isn't the fix)
Passing the existing second parameter:
This works — verified that nothing else changes:
But
$is_previewmeans "we are in the editor preview". Using it to mean "don't render heavy embeds" overloads a flag with unrelated semantics, and any future behaviour keyed on$is_previewwould silently start applying to normal front-end requests.Proposed solutions
Roughly in order of preference:
post_objectformatting. Return a small value object that renders the shortcode on string conversion. Twig calling{{ content.form }}renders it; a context build that never touches it pays nothing. No API break, fixes every call site at once.formatFields( $post, $is_preview, $render_embeds = true ), or an options array. Explicit, but every caller has to know to pass it.field_formatter_pre_post_object, so a theme can opt out per field. Smallest change, but leaves the default behaviour surprising.Happy to open a PR for whichever direction you prefer — (1) reads as the real fix to me.
Context
Found while migrating a theme to StarterBase. Reported from oekoplan (
~/Sites/wordpress/oekoplan), timber-kit v1.26.0, ACF Pro 6.8.6, WPForms 2.0.0.2.