Skip to content

Add render-time video SEO schema for block templates, parts, and patterns - #2031

Merged
KMchaudhary merged 3 commits into
developfrom
fix/seo-for-theme
Jul 22, 2026
Merged

Add render-time video SEO schema for block templates, parts, and patterns#2031
KMchaudhary merged 3 commits into
developfrom
fix/seo-for-theme

Conversation

@KMchaudhary

@KMchaudhary KMchaudhary commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Fixes: #2032

This pull request introduces a new mechanism to collect and output VideoObject schema for videos embedded in block-theme templates, template parts, and synced patterns, ensuring that all videos on a page are properly represented in the page's JSON-LD without duplication. The changes add a render-time collection and output process in addition to the existing cached wp_head schema output, with careful de-duplication between the two paths.

Video SEO schema improvements:

  • Added private properties to Seo for tracking which video content URLs and attachment IDs have already had schema emitted, and for collecting render-time video schema entries ($emitted_content_urls, $emitted_attachment_ids, $render_collected_schemas).
  • Added a render-time collector using the render_block filter and a new collect_render_time_video_seo method to capture video blocks rendered outside the queried post's content (e.g., in templates or patterns). [1] [2]
  • Added a new output_render_time_video_seo_schema method, hooked to wp_footer, to output JSON-LD for videos collected at render time, with de-duplication against videos already emitted in wp_head. [1] [2]
  • Refactored schema construction into a new build_video_object_schema method, ensuring both cached and render-time paths produce identical schema and fire the same filters.
  • Introduced an is_render_time_seo_context method to precisely determine when render-time schema collection/output should run (i.e., only on front-end HTML page renders, not in admin, REST, or feeds).

Screenshots (Self tested using https://validator.schema.org/ tool)

Frontpage template
Screenshot 2026-07-22 at 7 51 59 PM

Normal page with video and sync patterns
Screenshot 2026-07-22 at 7 54 32 PM

…erns

Add render-time video SEO schema for block templates, parts, and patterns
VideoObject JSON-LD was only emitted for godam/video blocks in a post's
own content. Videos placed in block-theme templates, template parts,
synced patterns, or on non-singular views (front page/archives) produced
no schema.
Collect such videos via the render_block filter and output them in
wp_footer, deduplicating against the cached wp_head output.
Copilot AI review requested due to automatic review settings July 22, 2026 09:25
@github-actions

Copy link
Copy Markdown

🔍 WordPress Plugin Check Report

⚠️ Status: Passed with warnings

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
23 0 23

⚠️ Warnings (23)

📁 composer.json (1 warning)
📍 Line 🔖 Check 💬 Message
0 missing_composer_json_file The "/vendor" directory using composer exists, but "composer.json" file is missing.
📁 readme.txt (2 warnings)
📍 Line 🔖 Check 💬 Message
0 mismatched_plugin_name Plugin name "GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more" is different from the name declared in plugin header "GoDAM".
0 trademarked_term The plugin name includes a restricted term. Your chosen plugin name - "GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more" - contains the restricted term "wordpress" which cannot be used at all in your plugin name.
📁 assets/build/blocks/godam-gallery-v2/render.php (2 warnings)
📍 Line 🔖 Check 💬 Message
15 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$inner_block_video_ids".
23 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$inner_block_video_ids".
📁 tests/bootstrap.php (7 warnings)
📍 Line 🔖 Check 💬 Message
28 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "wp_strip_all_tags".
48 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "wp_prepare_attachment_for_js".
63 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "get_post_meta".
77 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "get_post_mime_type".
88 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "wp_get_attachment_url".
99 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "get_the_title".
111 WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "date_i18n".
📁 assets/build/css/main.css (1 warning)
📍 Line 🔖 Check 💬 Message
0 EnqueuedStylesScope This style is being loaded in all contexts.
📁 assets/src/libs/analytics.min.js (5 warnings)
📍 Line 🔖 Check 💬 Message
0 EnqueuedScriptsScope This script is being loaded in all frontend contexts.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880 (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/2026/07/22/hello-world/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/sample-page/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/demo-attachment-post/ (with handle analytics-library) is loaded in the footer. Consider a defer or async script loading strategy instead.
📁 assets/build/js/main.min.js (5 warnings)
📍 Line 🔖 Check 💬 Message
0 EnqueuedScriptsScope This script is being loaded in all frontend contexts.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880 (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/2026/07/22/hello-world/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/sample-page/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.
0 NonBlockingScripts.NoStrategy This script on http://localhost:8880/demo-attachment-post/ (with handle rtgodam-script) is loaded in the footer. Consider a defer or async script loading strategy instead.

🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds render-time VideoObject JSON-LD generation so videos placed in block-theme templates/parts/patterns (and non-singular views) get SEO schema output, while de-duplicating against the existing cached wp_head output.

Changes:

  • Track emitted video identifiers (content URLs, attachment IDs) from the cached wp_head path for de-duplication.
  • Collect godam/video blocks at render time via render_block, and emit collected JSON-LD in wp_footer.
  • Refactor per-video schema building into a shared helper to keep both paths consistent and apply the same filters.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread inc/classes/class-seo.php
Comment thread inc/classes/class-seo.php
Comment thread inc/classes/class-seo.php
Comment thread inc/classes/class-seo.php
Comment thread inc/classes/class-seo.php
@subodhr258

Copy link
Copy Markdown
Collaborator

Code review

What this PR does

Extends video VideoObject JSON-LD so it also covers godam/video blocks that live outside the queried post's post_content (block-theme templates, template parts, synced patterns). The cached wp_head path only ever saw the post's own content; those composed-at-render videos were silently missing from the page schema.

The approach:

  • render_block collector (collect_render_time_video_seo) gathers godam/video blocks as they render.
  • wp_footer output (output_render_time_video_seo_schema) emits a single JSON-LD <script> for what was collected, de-duplicated against whatever wp_head already emitted.
  • Schema construction is factored into build_video_object_schema(), shared by both paths so they stay identical and fire the same filters.
  • is_render_time_seo_context() gates the whole thing to real front-end renders (excludes admin/REST/feed) and exposes a godam_video_seo_render_context filter.

Overall this is a clean, well-documented refactor. The shared builder, the two-phase collect/flush split, and deferring cross-path de-dup to wp_footer (where both state sets are guaranteed populated) are the right calls. Comments are genuinely helpful.

Correctness

  • De-dup identity gap (edge case). Flush-time de-dup vs wp_head (lines ~713–718) matches only on contentUrl and attachment_id, but collect-time de-dup falls back to a headline: key when both are absent. A godam/video with a headline but no contentUrl and no attachment_id (e.g. a seoOverride entry without a media id) that appears both in the queried post content and is render-collected would emit twice, because wp_head also can't have recorded it (nothing to key on). Low likelihood in practice (blocks almost always carry an id), but worth either handling (also record/compare a normalized headline) or noting as a known limitation.

  • VPG / godam/video-product-gallery blocks aren't collected at render time. The collector hard-filters to 'godam/video' only, whereas the cached path picks up Shoppable Video via godam_video_seo_extra_block_schemas. A VPG block placed in a template/pattern gets no render-time schema. This is probably acceptable scope for this PR, but it should be called out in the linked issue so it isn't assumed covered.

  • Order-independence is fine, but the inline comment overstates it. The comments (lines ~666–670, ~706–709) assert "the template — and therefore render_block — runs before wp_head*". In the block-theme canvas, wp_head() runs in <head> and the body's do_blocks() runs after, so that ordering is situational. It doesn't matter for correctness — wp_footer runs after both, which is exactly why deferring de-dup there is robust — but consider softening the comment so a future reader doesn't rely on the stated ordering.

  • Behavior change in the wp_head path (likely an improvement). Previously a godam_video_seo_schema filter returning empty still pushed the (empty) entry; now build_video_object_schema() returns it and the caller does if ( empty( $schema ) ) continue;. Skipping empties is better, just flagging it as an intentional-looking change.

Conventions

  • @since 2.0.0 on the new godam_video_seo_render_context filter (line ~616). Every other filter in this file uses a released number, but the project convention is to write @since n.e.x.t for unreleased additions and resolve it at release time. develop is already bumped to 2.0.0, so this is ambiguous — please confirm with the release owner whether new hooks should still use the n.e.x.t placeholder here.

Tests

  • No tests added. tests/php/ already exists (VideoEditorTest, OnboardingResponseTest), and this PR adds real branching logic (context gating, two-phase de-dup). A PHPUnit test covering: (a) render-time collection for a godam/video in a template/pattern, (b) no duplicate when the same video is in the queried post content, and (c) the godam_video_seo_render_context filter suppressing output — would lock in the behavior and guard the de-dup edge above.

Performance

  • render_block fires for every block on every front-end request. The blockName !== 'godam/video' early return is the correct cheapest-first guard, so overhead is negligible. Minor: is_render_time_seo_context() (which runs is_admin()/is_feed() + an apply_filters) is evaluated per godam/video block and again at flush; caching the result in a property would trim redundant work, though it's not material.

Security

  • The JSON-LD echo reuses the existing wp_json_encode(..., JSON_UNESCAPED_SLASHES | ...) pattern. Values are sanitized upstream (sanitize_text_field, wp_strip_all_tags, esc_url_raw) in build_video_object_schema(), so no new injection surface is introduced. No concern; just noting it matches the established pattern rather than adding one.

Verdict

Solid, low-risk change that fills a genuine SEO gap for block themes. Nothing blocking. Recommend: (1) confirm the @since convention, (2) add PHPUnit coverage for the collect/de-dup logic, and (3) note the headline-only de-dup edge and the VPG-block exclusion in the issue so they're tracked rather than assumed handled.

- Prefer attachment_id in render-collection dedupe key
- Close double-emit for headline-only entries (no contentUrl/id)
- Soften render_block/wp_head ordering comments
- Note  may be 0 on non-singular render-time views
- Tag all new symbols with @SInCE n.e.x.t
@KMchaudhary
KMchaudhary merged commit 50bcd43 into develop Jul 22, 2026
3 checks passed
@KMchaudhary
KMchaudhary deleted the fix/seo-for-theme branch July 22, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants