Description
AMP WP is the most feature-rich, developer-friendly, and beginner-ready Google AMP plugin available for WordPress, completely free.
Originally built by Pixelative, a professional web development agency, AMP WP is now independently developed and maintained by Mohsin Rafique, the lead engineer behind the plugin since its inception. AMP WP has helped thousands of website owners dramatically improve mobile page speed, search rankings, and user experience.
Online Demo | CF7 Premium Extension | Support
Why AMP WP?
Google’s Accelerated Mobile Pages (AMP) technology delivers pages up to 5x faster on mobile devices. Faster pages mean lower bounce rates, higher engagement, and better rankings in Google Search. AMP WP makes enabling AMP on your WordPress site effortless, from a single settings panel with no theme modifications needed.
Free Features
Performance & Compatibility
* Full Google AMP specification compliance (100% valid AMP output)
* PHP 8.x compatible (tested through PHP 8.4)
* Compatible with all major caching plugins (WP Rocket, SG Optimizer, W3 Total Cache, and more)
* Compatible with Jetpack
* Compatible with Yoast SEO (100%)
* Compatible with “Automattic AMP” plugin
* Gutenberg block editor support
* RTL language support (100%)
* GDPR compliant
Content & Embeds
* Automatic AMP conversion for Posts, Pages, and Custom Post Types
* Embed Images, Videos, Audios, and iFrames: YouTube, Vimeo, Twitter, Facebook, SoundCloud, Instagram (Posts, Reels & TV)
* Lightbox for Images
* Slider Support
* Internal AMP Linking
* Native AMP Search
Design & Customization
* Two listing layouts: Classic View and List View
* Customizable color scheme
* Custom CSS options
* Sticky Header
* AMP WordPress Navigation Menu
* Custom AMP Front Page
* Social Icons
Control
* Show/Hide AMP for individual Posts, Pages, and Custom Post Types
* Show/Hide AMP for Taxonomies (Categories, Tags, Custom)
* Show/Hide AMP mobile redirections
* Show/Hide AMP on Search pages
* Show/Hide Date, Author, and Thumbnail across Archive, Single Post, Related Posts, and Slider
* Show/Hide Tags on Single Post pages
* Enable/Disable Structured Data (JSON-LD Schema)
Engagement & Analytics
* Related Posts (with thumbnail, date, and author controls)
* Recent Comments
* Notice Bar
* Sidebar
* Social Sharing Buttons
* OneSignal Push Notifications integration
* Third-party analytics support: Google Analytics, Facebook Pixel, Segment, Quantcast, Alexa Metrics, Chartbeat, comScore, Yandex Metrica, AFS Analytics, Adobe Analytics
Ads
* Google AdSense Auto Ads for AMP support
Multilingual
* Translation Panel included
* Contact us at [email protected] to contribute translations
Maintained by Mohsin Rafique
AMP WP was originally created by Pixelative, a full-service digital agency specializing in high-performance WordPress solutions, and remains under Pixelative’s ownership. The plugin is currently developed, updated, and supported independently by Mohsin Rafique, a seasoned WordPress engineer with deep expertise in AMP specification compliance, PHP performance, and modern web standards.
For support, custom AMP theme development, or white-label licensing, contact Mohsin Rafique at mohsinrafique.com or [email protected].
Credits
- WordPress Plugin Boilerplate Generator by Tom McFarlin, License: GPLv2
- normalize.css, License: MIT
- Karla by Jonathan Pinhorn, License: SIL OFL
- Noto Sans by Google, License: SIL OFL
- Overpass Mono by Delve Withrington, License: SIL OFL
- Font Awesome Free 6.7.2 by Fonticons, Inc., Font: SIL OFL 1.1 / Code: MIT
Minimum Requirements
- WordPress 5.0 or higher
- PHP 7.4 or higher
- MySQL 8.0+ or MariaDB 10.5+
Tip: Install Regenerate Thumbnails and regenerate your thumbnails after activation to ensure the post listing layout renders correctly.
Automatic Installation (Recommended)
- Log in to your WordPress admin panel.
- Navigate to Plugins -> Add New.
- Search for AMP WP.
- Click Install Now, then Activate.
- Go to AMP WP -> Customize AMP to configure your settings.
Manual Installation
- Download the plugin ZIP from the WordPress plugin repository.
- Extract the ZIP and upload the
amp-wpfolder to/wp-content/plugins/via FTP or your hosting file manager. - Activate the plugin from Plugins in your WordPress admin panel.
- Navigate to AMP WP -> Customize AMP to begin setup.
Developer Hooks
AMP WP exposes WordPress-standard actions and filters throughout its template system, URL generator, structured data engine, and admin panel. All hooks listed here are safe to use in a child theme’s functions.php or in a standalone plugin. No plugin file edits are needed.
Template Actions
These actions fire inside AMP WP’s own template system. They do not use wp_head or wp_footer. Output must be valid AMP markup.
amp_wp_template_head
Fires inside the AMP <head> element. Use for custom meta tags or other head markup. AMP WP reserves priority 0 for component scripts.
amp_wp_template_head_deferred
Fires in <head> after the AMP boilerplate styles have been written. Required injection point for <amp-auto-ads> and any snippet that must follow the boilerplate. AMP WP’s own styles and scripts output here.
amp_wp_template_body_start
Fires immediately after the opening <body> tag.
amp_wp_body_beginning
Fires at the start of visible body content, before the site header renders.
amp_wp_post_content_below
Fires directly below the post content on single post pages.
amp_wp_template_footer
Fires before the closing </body> tag. Priority 999 is reserved for AMP WP’s output sanitizer.
amp_wp_template_enqueue_scripts
Use to register additional AMP component scripts. Call amp_wp_enqueue_script( $handle, $src ) inside your callback.
amp_wp_after_comment_list
Fires after the comment list on single post pages.
amp_wp_notifications_bar
Fires inside the notification/notice bar template slot.
amp_wp_gdpr_compliance
Fires inside the GDPR banner template slot.
Analytics Actions
These actions fire in the AMP page footer. Each corresponds to one analytics provider already supported by AMP WP. Hook into any of them to append a custom <amp-analytics> block for a provider not yet built in, or to modify the existing output before it renders.
amp_wp_analytics_ga(Google Analytics)amp_wp_analytics_fbp(Facebook Pixel)amp_wp_analytics_sa(Simple Analytics)amp_wp_analytics_qc(Quantcast)amp_wp_analytics_acm(Adobe Campaign Manager)amp_wp_analytics_cb(Chartbeat)amp_wp_analytics_comscore(comScore)amp_wp_analytics_yandex_metrica(Yandex Metrica)amp_wp_analytics_afs(AFS Analytics)amp_wp_analytics_adobe(Adobe Analytics)
AMP Version Control Filters
amp_wp_amp_version_exists
Control whether an AMP version is served for the current request. Return false to disable AMP for that page.
Parameter: (bool) $exists
add_filter( 'amp_wp_amp_version_exists', '__return_false' );
amp_wp_template_auto_redirect
Enable automatic redirection of all visitors from the non-AMP URL to the AMP URL. Default false.
Parameter: (bool) $redirect
amp_wp_filter_config_list
Extend the list of post types, taxonomies, and conditions where AMP is disabled site-wide.
Parameter: (array) $filters
URL and Permalink Filters
amp_wp_pre_get_permalink
Short-circuit AMP permalink generation. Return any non-false value to bypass the core URL builder.
Parameters: (mixed) $pre (default false), (int) $post_id
amp_wp_get_permalink
Filter the final AMP URL after it has been built.
Parameters: (string) $amp_url, (int) $post_id
amp_wp_url_format_filter
Change the AMP URL format. Accepted values: start-point (prefix, e.g. /amp/slug) or end-point (suffix, e.g. /slug/amp).
Parameter: (string) $format
amp_wp_url_excluded
Add URL paths that must never serve an AMP version.
Parameter: (array) $excluded_urls
amp_wp_transformer_exclude_subdir
Add path segments that the AMP URL transformer should skip when rewriting internal links. Used internally by AMP WP for WPML and Polylang language prefixes.
Parameter: (array) $exclude_dirs
Structured Data Filters (JSON-LD)
All JSON-LD filters are active only when “Enable Structured Data on Site” is on in AMP WP Settings. The three schema-data filters carry a trailing underscore. This is generated internally via sprintf( 'amp_wp_json_ld_%s_', $type ) and the underscore must be part of the hook name you register.
amp_wp_json_ld_config
Filters the generator configuration before any schema is built.
Parameter: (array) $config with keys:
* active (bool): set false to disable the entire JSON-LD generator for the current request.
* logo (string): Organization logo URL. When non-empty, an Organization block is emitted on every page and all Article-type schemas reference it by @id.
* posts_type (string): Fallback schema type when schema_type_for_post is empty. Default BlogPosting.
* media_field_id (string): Post meta key for audio/video URL in format posts. Default _featured_embed_code.
AMP WP core attaches at priority 15 to inject the branding logo. Use priority 20 or higher to override it.
add_filter( 'amp_wp_json_ld_config', function( $config ) { ... }, 20 );
amp_wp_json_ld_organization_
Filters the Organization schema array before output. Fires only when a logo is configured. Return false or an empty array to suppress the block.
Parameter: (array) $data
add_filter( 'amp_wp_json_ld_organization_', function( $data ) { ... } );
amp_wp_json_ld_website_
Filters the WebSite schema array before output. Fires only on the homepage and front page.
Parameter: (array) $data
add_filter( 'amp_wp_json_ld_website_', function( $data ) { ... } );
amp_wp_json_ld_single_
Filters the singular-content schema array before output. Fires on all singular pages: standard posts (Article, NewsArticle, BlogPosting), static pages (WebPage), WooCommerce products (Product), post format overrides (AudioObject, VideoObject, ImageObject), and custom post types.
Parameter: (array) $data
add_filter( 'amp_wp_json_ld_single_', function( $data ) { ... } );
Content and Theme Filters
amp_wp_template_page_on_front
Specify the page ID to use as the AMP front page when a static homepage is configured.
Parameter: (int) $page_id, default 0.
amp_wp_template_show_on_front
Control what displays on the AMP homepage. Accepted values: posts or page.
Parameter: (string) $show_on_front
amp_wp_template_active_template
Override the active template metadata array.
Parameter: (array) $template_info
amp_wp_template_set_menu_walker
Enable or disable the custom sidebar menu walker for a specific nav menu location.
Parameters: (bool) $use_walker, (array) $args
amp-wp-template-default-theme-mod
Set default values for AMP WP Customizer options.
Parameters: (mixed) $default_value, (string) $option_key
amp_wp_home_featured
Customize the WP_Query arguments used to fetch posts for the homepage featured slider.
Parameter: (array) $query_args
amp_wp_get_template
Override the file path used to locate a template part.
Parameters: (string) $located, (string) $file, (array) $args, (string) $template_path, (string) $default_path
amp_wp_html_dom_filter_attributes
Filter an HTML element’s attributes during the DOM processing pipeline.
Parameters: (array) $attributes, (string) $tag_name, (array) $valid_attributes
amp_wp_style_files_{$file}
Filter CSS file content before it is added to the inline <style amp-custom> block. {$file} is the stylesheet handle.
Parameter: (string) $css
amp_wp_social_share_cache_time
Set the cache duration in seconds for social share counts. Default 7200 (120 minutes).
Parameters: (int) $seconds, (int) $post_id
amp_wp_social_share_count
Filter social share count results after they are fetched.
Parameter: (array) $results
amp_wp_gdpr_country_list
Customize the list of countries considered GDPR-compliant.
Parameter: (array) $countries
amp_wp_translation_std
Provide or override fallback translation strings.
Parameter: (array) $translations
Admin and Settings Hooks
amp_wp_settings_tab_menus (filter)
Add a custom tab to the AMP WP settings panel. Key is the tab slug, value is the tab label HTML.
Parameter: (array) $tabs
add_filter( 'amp_wp_settings_tab_menus', function( $tabs ) { $tabs['my-tab'] = '<span>My Tab</span>'; return $tabs; } );
amp_wp_settings_tab_section (action)
Render the HTML content for your custom settings tab. Check $_GET['tab'] to target a specific tab.
amp_wp_save_setting_sections (action)
Fires when the settings form is submitted. Hook here to read and save data for a custom tab.
amp_wp_save_setting_notice (filter)
Customize the “Settings saved.” notice text.
Parameter: (string) $notice_text
amp_wp_welcome_tab_menus (filter)
Add a custom tab to the AMP WP welcome/dashboard page.
Parameter: (array) $tabs
amp_wp_welcome_tab_section (action)
Render HTML content for a custom welcome page tab.
amp_wp_default_configurations (action)
Fires on plugin activation. Hook here to set default options for add-on plugins.
Screenshots

Home Page 
Single Post Page 
Tags, Social Icons, Related Posts, Comments, and Footer 
Search & Archive Page 
100% Valid AMP Content 
AMP WP Options Panel 
AMP Auto Ads Support 
Compatible with Major Cache Plugins 
Embed Images, Videos, Audios & iFrames 
Toggle Search & Header 
Recent Comments 
Sidebar, Social Icons & Related Posts 
Core WordPress Customizer Integration 
AMP WP Settings Panel
FAQ
-
What is the URL structure for AMP pages?
-
AMP WP supports two URL formats:
- Prefix format:
https://yoursite.com/amp/page-name - Suffix format:
https://yoursite.com/page-name/amp
You can choose your preferred format from AMP WP -> Settings -> General.
- Prefix format:
-
How do I add analytics tracking?
-
Go to AMP WP -> Settings -> Analytics in your WordPress admin dashboard. AMP WP supports Google Analytics, Facebook Pixel, Segment, Quantcast, Alexa Metrics, Chartbeat, comScore, Yandex Metrica, AFS Analytics, and Adobe Analytics.
-
The post listing layout looks broken: how do I fix it?
-
This is usually caused by inconsistent thumbnail sizes. Install and run Regenerate Thumbnails to normalize all image sizes.
-
Does AMP WP support Instagram Reels and TV posts?
-
Yes. Since version 1.6.0, AMP WP supports Instagram
/p/,/reel/, and/tv/post types natively viaamp-instagram. -
What prebuilt listing layouts are available?
-
Two layouts are included: Classic View and List View. Switch between them from AMP WP -> Options Panel.
-
How do I inject custom HTML (e.g., ad codes, analytics snippets)?
-
Go to Dashboard -> AMP WP -> Customize AMP. You can inject valid AMP snippets:
* Between<head></head>tags
* Right after the<body>opening tag
* Right before the</body>closing tagOnly valid, AMP-compatible code will function on AMP pages.
-
How do I enable AdSense Auto Ads for AMP?
-
Recommended path (AMP WP 1.7.7 and later):
- Open Dashboard -> AMP WP -> Settings -> General.
- Scroll to the Google Auto Ads section.
- Tick the Enable Google Auto Ads checkbox and click Save Changes.
When the toggle is on, AMP WP automatically takes care of both halves that AdSense Auto Ads needs on every AMP page:
- The
amp-auto-adsruntime loader (<script async custom-element="amp-auto-ads" src="https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js"></script>) is injected inside<head>via the canonical AMP component pipeline. - The matching
<amp-auto-ads type="adsense" data-ad-client="ca-pub-XXXXXXXXXXXXXXXX">element is emitted right after<body>so the AMP validator sees the extension as “used” and AdSense can place anchor, vignette, and in-page ads.
Where does the publisher ID come from?
- With Google Site Kit installed and AdSense connected: the publisher ID is read automatically from Site Kit’s stored AdSense module setting (no extra typing needed).
-
Without Site Kit: supply your
ca-pub-...value via theamp_wp_adsense_publisher_idfilter. Drop this into your theme’sfunctions.phpor a small mu-plugin:add_filter( ‘amp_wp_adsense_publisher_id’, function() { return ‘ca-pub-XXXXXXXXXXXXXXXX’; } );
The filter accepts any of the three plausible input shapes –
ca-pub-XXXX,pub-XXXX, or the bare numeric ID – and AMP WP normalises the value before output. If no publisher ID is resolvable, AMP WP silently skips the body-side element so the page stays validator-clean.Manual fallback (non-AdSense ad networks, custom snippets):
Navigate to Dashboard -> AMP WP -> Customize AMP. Paste the runtime loader script in the “Codes between
<head>and</head>tags” field, and your<amp-auto-ads>(or other AMP-valid ad markup) in the “Codes right after<body>tag” field. When you later enable the native Google Auto Ads toggle, AMP WP auto-cleans any manually-pastedamp-auto-adsloader from the head-code field and surfaces a one-shot admin notice confirming the cleanup, so the native injection path remains the single source of truth. -
How do I enable or disable AMP for a specific post?
-
Edit the post in WordPress, scroll to the AMP WP meta box in the sidebar, and toggle the AMP option on or off for that individual post.
-
How do I report a bug or request a feature?
-
Please email
[email protected]or open a thread on the WordPress support forum.
Reviews
Contributors & Developers
“AMP WP – Google AMP For WordPress” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “AMP WP – Google AMP For WordPress” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.7.11 – 2026-05-25
- AMP-Validity: three-layer defense for invalid CSS at-rule stripping, resolving the
@view-transitionAMP validation error on sites running WordPress 7.0 with View Transitions enabled in global styles. (1) The content sanitizer now extracts<style>tags from<head>(not just<body>), feeding their CSS throughamp_wp_add_inline_style()— this catches WordPress global-styles that previously bypassed the body-only extraction and appeared unsanitized in the AMP output. (2) Centralized the at-rule stripping regex intoAmp_WP_Styles::strip_invalid_atrules()static method, called on both input (amp_wp_add_inline_style()) and output (Amp_WP_Styles::print_inline_styles()). (3) The static method returns empty string on PCRE backtrack-limit failure rather than passing through unsanitized CSS.
1.7.10 – 2026-05-25
- AMP-Validity: strip CSS at-rules not allowed in AMP
<style amp-custom>(e.g.@view-transition,@import,@charset,@namespace,@layer), including when nested inside@mediaor@supportsblocks. WordPress 7.0 adds@media (prefers-reduced-motion:no-preference){@view-transition{navigation:auto}...}to its global-styles-inline-css output; the initial top-level-only regex missed@view-transitioninside the@mediawrapper. The sanitizer now runs a second pass withpreg_replace_callbackthat opens allowed container at-rules and strips invalid nested at-rules from their content, collapsing the container entirely if it becomes empty. - Fix: six IDE diagnostics in
includes/functions/amp-wp-theme-functions.php. (1) Splitamp_wp_direction()(void/echo) intoamp_wp_get_direction()(returns string) + a thin echo wrapper so the concatenation at line 1907 no longer uses a void return value; the existing template caller insidebar.phpcontinues to work unchanged. (2) Replaced the brokenAmp_WP_Public::get_instance()->call_components_method()call inamp_wp_do_shortcode()—Amp_WP_Publichas noget_instance()method — with a direct iteration over the$amp_wp_registered_componentsglobal, matching the same logic the instance method uses. (3) Fixed possibly-undefined$area_labeland$paraminamp_wp_social_share_get_li(): thefacebookswitch case only set these variables whenfacebook_app_idwas present; when missing, execution fell through to code that used them. Inverted the guard to return early when the app ID is absent. (4) Fixedamp_wp_comment_reply_link()assigning the void return ofcomment_reply_link(): set$args['echo'] = falseso WordPress returns the markup as a string instead of echoing it directly, then echo + return the result. Remaining P1125 is an Intelephense limitation — it cannot narrowvoid|stringbased on the runtimeechokey. - Refactor: WPCS cleanup of
includes/functions/amp-wp-core-functions.php. Fixed all 36 errors and 4 warnings: added missing@paramdescriptions across 6 functions (amp_wp_enqueue_style,amp_wp_enqueue_script,amp_wp_register_component,amp_wp_guess_non_amp_url,amp_wp_translation_get,amp_wp_translation_echo), corrected swapped@paramnames onamp_wp_add_inline_styleandamp_wp_enqueue_inline_style, fixed@return boolon void function, added@throwstag, added translators comment, removed structured-array hash notation that trippedParamCommentFullStop, refactored two assignment-in-return statements, added param types for three untyped parameters ($key,$key,$parsed_url), renamed reserved-keyword parameters ($var->$found,$echo->$should_echo), fixed inline@varannotation format, and terminated all param comments with full stops. Removed deadmysql_get_server_info()branch inamp_wp_get_server_database_version()(themysql_*extension was removed in PHP 7.0; WordPress itself dropped this codepath in 5.x). Fixed@param null->@param WP_Query|nullonis_amp_wp(). No behaviour change. - Refactor: WPCS cleanup of
includes/functions/amp-wp-formatting-functions.php. Fixed 1 error + 2 warnings: added missing file docblock, renamed reserved-keyword parameter$varto$tooltipinamp_wp_sanitize_tooltip()and to$valueinamp_wp_clean(). No behaviour change. - Refactor: WPCS cleanup of
includes/functions/amp-wp-utility-functions.php. Fixed 4 errors + 3 warnings: renamed reserved-keyword parameter$stringto$urlinamp_wp_remove_query_string()and added missing description, movedCopyright:URL lines to proper@linktags so PHPCS no longer treats them as unterminated param comments, added missingreturnstatement toamp_wp_remove_class_action()(was silently discarding the boolean fromamp_wp_remove_class_filter()), switched loose==to strict===inamp_wp_transpile_text_to_pattern(), replacedurlencode()withrawurlencode()inamp_wp_parse_url(). No behaviour change. - Refactor: WPCS cleanup of
includes/functions/amp-wp-ad-functions.php. Fixed 3 errors: added missing@paramdescriptions onamp_wp_get_ad_location_data()andamp_wp_show_ad_location(), corrected@return arrayto@return voidonamp_wp_show_ad_location(). No behaviour change.
1.7.9 – 2026-05-21
- Compat: verified and declared WordPress 7.0 compatibility. Tested up to WordPress 7.0.
- Refactor: PHPCS cleanup of
includes/components/class-amp-wp-img-component.php. Fixed all PHPDoc issues (missing param descriptions, missing return tags, missing summary periods, incorrect tag ordering), removed assignment-in-condition, fixed closing braces on wrong lines, removed commented-out code, fixed inline comment formatting. Moved the standaloneamp_wp_create_image()function toincludes/functions/amp-wp-core-functions.phpto resolveUniversal.Files.SeparateFunctionsFromOO.Mixed(a file must not contain both class and function declarations). No public-API change; function remains available at the same name. - Refactor: PHPCS cleanup of
includes/components/class-amp-wp-instagram-component.php. Added separate file and class docblocks (resolvesSquiz.Commenting.FileComment.MissingandSquiz.Commenting.ClassComment.SpacingAfter), fixed PHPDoc tag ordering and added missing param/return descriptions, removed inline@vartype hint flagged as commented-out code, added missing summary periods. Standardized file docblock tags to@package/@subpackage/@author/@copyright/@sinceorder. - Refactor: PHPCS cleanup of
includes/components/class-amp-wp-playbuzz-component.php. Added separate file and class docblocks, fixed assignment-in-condition, removed inline@vartype hint, added missing PHPDoc summaries and param/return descriptions, standardized file docblock tag ordering, added@subpackagetag. No behaviour change. - Refactor: cleared all 102 WPCS errors and 40 warnings from
includes/functions/amp-wp-theme-functions.php(the 2677-line template / globals / properties / queries / social-share / comments helper library). No public-API surface change; file now passesphpcs --standard=WordPresswith exit 0. PHP syntax check clean. Highlights: replaced two deprecated constants (STYLESHEETPATH->get_stylesheet_directory(),TEMPLATEPATH->get_template_directory()) inside the template-resolution scan loop inamp_wp_locate_template(); backfilled@paramdescriptions on more than thirty docblocks across the file (every helper fromamp_wp_load_template()andamp_wp_hw_attr()through theamp_wp_set_global/amp_wp_get_propcluster,amp_wp_get_branding_info(),amp_wp_get_theme_mod(),amp_wp_get_option(), the query helpers, the comment helpers,amp_wp_min_suffix(),amp_wp_collect_post_type_slugs(), and the social-share trio); flipped six Yoda violations and four loose-comparison!=/==to strict!==/===(including the localhost check, the customizer-direction toggle, the menu-walker theme-location compare, the social-share format compare and the taxonomy slug compare); refactored eight assignment-in-condition patterns into explicit fetch-then-test ladders (front-page resolver, branding info, rel-canonical print, locate_template return scan, site-url permalink-prefix block); flagged sixphpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNameson the public signatures foramp_wp_locate_template($require_once),amp_wp_load_template($require_once),amp_wp_body_class($class), and four$defaultparameters across the global / prop accessors – renaming would have broken sites that call these by name; tagged the historicalamp-wp-template-default-theme-modfilter atamp_wp_get_option()with aphpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscoresplus a justification explaining why renaming to underscores would break any site filtering this 1.0.0-era public hook; added a singlephpcs:ignoreon thewp_reset_query()call insideamp_wp_clear_query()with a paragraph-long justification about whywp_reset_postdata()would not undo a customamp_wp_set_query()swap; replacedrand()withwp_rand()inamp_wp_element_unique_id(); removed the@error-silencing onnumber_format()insideamp_wp_human_number_format()(cast to float instead, which is the actual fix – non-numeric inputs are already rejected by the early return above); added the missing/* translators: */comment on the taxonomy archive titlesprintf( __( '%1$s: %2$s' ) )inamp_wp_get_archive_title_fields()and corrected its textdomain from the placeholderbeetter-amptoamp-wp; collapsed twelve inline comments to proper full-stop terminators; corrected the bogus@return intonamp_wp_comment_link()(the function only echoes) to@return void; tightened the$_GETwhitelist intersect inamp_wp_get_canonical_url()so the existing nonce-recommended ignore lands on the exact$_GETtoken rather than the line above; and scoped aphpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameteronamp_wp_comment_item()whose$comment/$args/$depthparameters are required by theWalker_Commentcallback signature and are read inside the includedcomment-item.phptemplate via scope inheritance. Removed dead code: the long commented-out Google Plus social-share block insideamp_wp_social_share_fetch_count()(the service was retired by Google in 2019; thecase 'google_plus':body had been mid-deletion for years, leaving unreachableif ( ! is_wp_error( $remote ) )debris after the precedingbreak;), plus the dangling'google_plus'entry in theamp_wp_social_shares_count()valid-sites whitelist that was the only remaining live reference to it. - Refactor: cleared all 8 WPCS errors and 2 warnings from
includes/components/class-amp-wp-carousel-component.php(the carousel component that renders WordPress galleries and[amp-wp-slider]post sliders as<amp-carousel>). No behaviour change; file now passesphpcs --standard=WordPresswith exit 0. Added the missing file-level docblock; gavetransform(),handle_gallery(),handle_slider(),slider_posts(), andgallery_attachments()proper@paramdescriptions; added short descriptions to the twoprotectedrenderers that had@param-only blocks. Theslider_posts()docblock now carries a multi-paragraph “Uses query_posts() intentionally because…” explanation mirroring the existingAmp_WP_Public::amp_wp_set_page_query()docblock that documents the equivalent front-page override: the slider template iterates viaamp_wp_have_posts()/amp_wp_the_post()(inincludes/functions/amp-wp-theme-functions.php) which fall back to the global$wp_query/$wp_the_querywhen no custom query has been staged viaamp_wp_set_query(), so a freshWP_Querywould silently iterate the page’s original main query and awp_reset_postdata()paired with it would only rewind the post pointer and leave the slider’s query active for the rest of the request.query_posts()andwp_reset_query()are bothphpcs:ignored with short-- see method docblockreferences. Also removed a dead local variable inconfig()($array_mohsin) that built an array identical to the one already being returned literally below it; safe deletion (zero references, return value unchanged). - Refactor: cleared all 40 WPCS errors and 4 warnings from
includes/components/class-amp-wp-iframe-component.php(the iframe component that bridges YouTube / Twitter / Facebook / Vimeo / SoundCloud / Instagram onto AMP component tags). No behaviour change; the file now passesphpcs --standard=WordPresswith exit 0. Highlights: added the missing file-level docblock; backfilled missing@paramdescriptions on 16 methods; renamed the$stringparameter onget_iframe_dimension()to$htmlto drop the reserved-keyword conflict (all four call sites within the same file are positional, no external caller risk); added the missing short description on$enable_enqueue_scripts; switchedin_array( $provider, $this->support_sites )to strict mode; refactored the two assignment-in-condition patterns insidetransform()into explicit fetch-then-test ladders; gaveget_frame_height()a typed@param string $url; ended the inline// Fix: remove aparat iframe extra tagscomment with a full stop; and scoped a singlephpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCaseblock around the four$element->parentNode/$element->previousSiblingDOM-API reads insanitize(). - Fix:
Amp_WP_Html_Util::set_outer_HTML()now actually replaces the target element with the parsed HTML in-place. The previous implementation appended the parsed fragment to the end of the parent’s children, then ran awhileloop that wiped every child of the parent (including the fragment it had just added and every sibling of the target element). In practice the only caller, the iframe component’s SoundCloud rewrite branch (includes/components/class-amp-wp-iframe-component.php:325), only landed correct output when the iframe happened to be the sole child of its container; in any other layout, surrounding markup was silently destroyed. New implementation guards on$element->parentNode, builds aDOMDocumentFragment, parses the HTML under suppressed libxml errors (clearing the queue afterwards so noisy embed markup does not pollute downstream handlers), and usesreplaceChild( $fragment, $element )so the fragment dissolves into its child nodes at the original element’s slot in the parent’s child list. Sibling elements are preserved.
1.7.8 – 2026-05-14
- AMP-Validity: emit the
<amp-auto-ads type="adsense" data-ad-client="ca-pub-XXXX">body-side element whenever the General Settings “Google Auto Ads” toggle is on and an AdSense publisher ID is resolvable. Closes the validator error “The extension ‘amp-auto-ads’ was found on this page, but is unused. Please remove this extension.” that surfaced when the toggle was enabled alongside Google Site Kit: Site Kit’s AdSense AMP integration only fires for the official Automattic AMP plugin (it detectsamp_is_request()/AMP__VERSION/ theamp_post_template_footerhook), so on AMP WP routes Site Kit never injected the body element that the runtime loader expects to consume. AMP WP now emits the element itself. Publisher ID resolution chain: (1)amp_wp_adsense_publisher_idfilter for theme/plugin overrides, (2) Site Kit’s stored AdSense module settinggooglesitekit_adsense_settings['clientID']– the same key already inspected byamp_wp_is_site_kit_adsense_active(). Returned value is normalised toca-pub-XXXXXXXXXXXXXXXX, accepting any of the three plausible input shapes (ca-pub-XXXX,pub-XXXX, or the bare numeric ID). When no publisher ID is configured anywhere the element is silently skipped so the page stays validator-clean, and when the toggle is off neither the loader nor the element is emitted. New public API: functionamp_wp_get_adsense_publisher_id(), functionamp_wp_render_auto_ads_element(), filteramp_wp_adsense_publisher_id. Runtime path:amp_wp_render_auto_ads_element()hooksamp_wp_template_body_startat priority 5 (runs beforeamp_wp_custom_code_body_startso a manual snippet in the body-start Customizer field continues to layer on top), gates onamp_wp_is_auto_ads_enabled(), and usesprintfwithesc_attr()on thedata-ad-clientvalue. No new option storage and no UI field added in this round (publisher ID is auto-resolved from Site Kit, or a filter override): cleanest possible diff that fixes the validator error without scope-creep into an Ads-Manager-style settings UI, which belongs in theamp-wp-ads-manageradd-on.
1.7.7 – 2026-05-14
- Feature: native “Google Auto Ads” toggle on the AMP WP > Settings > General page. When enabled, AMP WP enqueues the canonical
amp-auto-adsruntime loader (https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js) on every AMP page via the existingamp_wp_enqueue_script()registry, replacing the long-standing workaround of pasting the script into the Customizer’s “Codes between and tags” field. Default off; opt-in. Runtime path:amp_wp_enqueue_auto_ads_script()is hooked on the existingamp_wp_template_enqueue_scriptsaction and callsamp_wp_enqueue_script( 'amp-auto-ads', 'https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js' )– identical pattern to how every other AMP component loader is registered (amp-form,amp-consent,amp-geo, etc.), so the resulting<script async custom-element="amp-auto-ads" src="..."></script>tag is rendered by the sameamp_wp_print_scripts()callback that emits every other component and benefits from automatic dedupe against any sibling caller that registers the same handle. Backward compatibility: when the head-code Customizer field already contains anamp-auto-adsloader, the enqueue is skipped (duplicate guard viaamp_wp_head_field_has_auto_ads_loader()); on save, AMP WP also strips that manually-pasted loader from the head-code field viaamp_wp_auto_ads_strip_manual_loader()so the native path becomes the single source of truth, surfacing a one-shot dismissible admin notice confirming the cleanup. The settings UI shows two contextual notices: an amber heads-up when the head-code field still contains a loader (preview of what enabling the toggle will clean up) and a blue recommendation when Google Site Kit is detected active with AdSense connected (amp_wp_is_site_kit_adsense_active()checksis_plugin_active( 'google-site-kit/google-site-kit.php' )plus the AdSense module’s storedclientIDingooglesitekit_adsense_settings). New helpers:amp_wp_is_auto_ads_enabled(),amp_wp_is_site_kit_adsense_active(),amp_wp_head_field_has_auto_ads_loader(),amp_wp_enqueue_auto_ads_script(),amp_wp_auto_ads_strip_manual_loader(). Migration logic hooks bothupdate_option_amp_wp_general_settings(subsequent saves) andadd_option_amp_wp_general_settings(first-ever save where the option does not yet exist – WordPress firesadd_option_<name>instead ofupdate_option_<name>in that branch). New option keyamp_wp_general_settings['google_auto_ads'](boolean 0/1); save handler always writes a 0/1 explicitly even when the checkbox is unchecked, so the off-transition is recorded in$new_valuefor the migration diff. No public-API breakage; existing manually-pasted loaders in the head-code Customizer field continue to work unchanged when the toggle is left off. - Fix: amp-geo “country … not valid, will be ignored” runtime error on every page with the GDPR consent banner enabled. The
ISOCountryGroups.euarray inside the<amp-geo>JSON config was being emitted throughesc_js(), which is the wrong tool for JSON content: it HTML-encodes"as", so amp-geo received a single corrupted string likePK","AT","BE",...,"CH"instead of an array of ISO country codes, and silently rejected the whole config (breaking geo-gated consent behaviour). Replaced theesc_js( implode( '","', ... ) )pattern atpublic/partials/tez/components/gdpr/gdpr.php:22withwp_json_encode( array_values( array_map( 'sanitize_text_field', $gdpr_countries ) ) )so the country list is emitted as a valid JSON array. Theamp_wp_gdpr_country_listfilter contract is unchanged. - Refactor: WordPress Coding Standards (WPCS) cleanup of the Tez theme directory (
public/partials/tez/). All 37 template files now passphpcs --standard=WordPresswith zero errors and zero warnings, down from 49 errors and 31 warnings across 28 files. Highlights: every inline//comment now ends with proper punctuation; all loose-comparison toggle checks like'1' == $foo_switchare replaced with! empty( $foo_switch )(handles both string'1'and integer1saved values uniformly without behaviour change); the file-level docblock is present and correctly spaced on every template (added toheader.php,footer.php,shortcodes/amp-slider.php,shortcodes/gallery.php, andshortcodes/index.php; corrected spacing on seven existing docblocks that were flush against the next code line); template-local variables that shadowed WP globals were renamed ($id->$slider_id/$thumbnail_wrapper_id/$related_item_id,$link->$comment_link,$title->$archive_title); Yoda conditions applied incomponents/social-list/social-share.php; assignments lifted out ofifconditions inattachment.phpandsocial-share.php; dead commented-out Google Plus social-share scaffolding (a service Google retired in 2019) removed fromcomponents/social-list/social-links.php; the slider’smeta_queryfor featured-image gating is annotated with// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query+ justification rather than restructured (no equivalent rewrite exists without changing semantics). All changes are sniff-driven cleanups with no rendering behaviour change. - Refactor: removed
public/partials/tez/amp-wp-ads-manager/ads.phpand its parent directory from core. The file (a half-completed placement-manager UI sketch from an earlier design – neverrequire-d anywhere, hooks two filters that are never fired, calls two helper functions that are not defined) was relocated verbatim to theamp-wp-ads-manageradd-on atincludes/placement-manager/ads.phpto preserve its design intent for future completion in the right plugin. Reinforces the family architecture rule that core no longer references any add-on slug in any directory path; no public-API change, no add-on consumed the file from this location.
1.7.6 – 2026-05-12
Security
- Comprehensive WordPress Coding Standards security sweep across the entire plugin. Output escaping (
esc_html,esc_attr,esc_url,wp_kses,wp_kses_post) applied to every flaggedecho/printfsite in admin partials, settings forms, public templates, customizer controls, and core functions. Superglobal reads ($_SERVER['REQUEST_URI'],$_SERVER['SCRIPT_FILENAME'],$_SERVER['PHP_SELF'],$_SERVER['HTTP_USER_AGENT'],$_SERVER['SERVER_SOFTWARE'],$_SERVER['LOCAL_ADDR'],$_SERVER['SERVER_ADDR']) now consistently guarded withisset()and run throughesc_url_raw( wp_unslash() )orsanitize_text_field( wp_unslash() )as appropriate per use site. Translation strings with intentional HTML (e.g.<strong>for inline emphasis) now route throughwp_kses_post( __() )instead of bare_e()so the markup is preserved while bot-injected HTML is filtered out. Legacy// WPCS: XSS ok.comments replaced with modern// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped --annotations with a one-line justification each. Net effect: theWordPress.Security.EscapeOutput,WordPress.Security.NonceVerification, andWordPress.Security.ValidatedSanitizedInputsniffs report 0 errors and 0 warnings across the plugin (down from 311 errors before the sweep). Total WPCS reduction: 1,448 -> 632 (56%). No behavioural change – all edits are escaping/sanitizing additions orphpcs:ignoreannotations on canonical safe-output patterns.
Added
- New “Premium Extensions” tab on the AMP WP Settings page (
admin.php?page=amp-wp-settings#settings-premium-extensions). Central admin home for AMP WP family add-ons that need configurable settings without forking a full settings tab. Add-ons populate the tab via the newamp_wp_premium_extensions_sectionaction – callbacks echo a card (heading +form-tablerow group) inside the tab body, and the existingamp_wp_save_setting_sectionsaction persists each add-on’s option independently. New files:includes/admin/settings/class-amp-wp-premium-extensions.php(registers the tab) andadmin/partials/settings/amp-wp-admin-premium-extensions.php(the form scaffold). - Premium-extension registration API:
amp_wp_register_premium_extension( string $slug, array $args = [] ): void,amp_wp_get_registered_premium_extensions(): array<string,array<string,string>>, andamp_wp_has_active_premium_extension(): bool(all inincludes/functions/amp-wp-core-functions.php). Each AMP WP family add-on self-registers once on bootstrap with a slug + display metadata (name,description,version,url). Idempotent: registering the same slug twice is a no-op. New companion filteramp_wp_premium_extensionsfor advanced reordering, suppression, or unit-test snapshot/restore. The empty-state partial reads the registry to render the active-extensions list (instead of hard-coding “AMP WP – Contact Form 7”). - Template-path registration: new public functions
amp_wp_register_template_path( string $absolute_path ): voidandamp_wp_get_registered_template_paths(): array(inincludes/functions/amp-wp-theme-functions.php) let AMP WP family add-ons ship their own AMP templates instead of having them live inside core’s “Tez” theme directory. Resolution order is<wp-theme>/amp-wp/<template>(site override – unchanged) -> registered add-on paths in registration order -> core’s Tez directory as the catch-all fallback. End-user override convention preserved verbatim. New filteramp_wp_template_pathslets advanced users reorder or suppress paths per-request. Companion change inamp-wp-woocommercemigrates eight WC-specific AMP partials out of core to restore the family architecture rule “Core never references any add-on by name”. - New extension hook
amp_wp_layout_setting_after_show_commentsfires inside the Layout Settings -> Single Post Page table, immediately after the “Show Comments”<tr>. Designed for AMP WP family add-ons (e.g. AMP WP – Comments uses it to print a “Show Comment Replies” toggle row) to extend the comment-related settings block without forkingadmin/partials/settings/amp-wp-admin-layout.php. Callbacks must echo a complete<tr>...</tr>matching the surrounding form-table markup; inputs may piggyback on the existingamp_wp_layout_settings[...]name prefix to persist via the existing save handler atAmp_WP_Layout::amp_wp_save_layout_settings()– no separate option, no separate save flow.
Changed
- The “Premium Extensions” tab is now hidden by default – it only registers when at least one AMP WP family add-on (CF7, Comments, WooCommerce, WPForms, Gravity Forms, Ads Manager) has called the new
amp_wp_register_premium_extension()function on its bootstrap. Previously the tab appeared on every install, even on a fresh one with no add-ons active, where it rendered an empty-state hint. Back-compat fallback: if an older add-on hooksamp_wp_premium_extensions_sectiondirectly without registering, the action presence still triggers the tab. Both the tab definition and the partial loader gate on the same check, so a stale anchor link to#settings-premium-extensionsno longer paints orphaned form markup either.
Fixed
- Browsers fetching
/favicon.ico(and any other root-level static-file URL) on mobile no longer trigger a 404.Amp_WP_Public::amp_wp_auto_redirect_to_amp()fed$_SERVER['REQUEST_URI']throughAmp_WP_Content_Sanitizer::transform_to_amp_url(), which blindly appended/ampto any internal URL it didn’t already recognise as an AMP path or wp-content asset. When a site has no staticfavicon.icofile at the document root, the browser-issued favicon request fell through toindex.php, hittemplate_redirecton mobile, and the auto-redirect bounced the browser to/favicon.ico/amp– which never matches a rewrite rule and 404s. Same shape would fire for any root-level static file (sitemap.xml,robots.txt,ads.txt,manifest.json, etc.) when no physical file exists. Fix:transform_to_amp_url()now short-circuits and returns the URL unchanged when the path’s basename has a static-file extension (35-entry list covering icons, images, scripts, styles, fonts, archives, media, metadata). List is filterable via the newamp_wp_non_amp_url_extensionsfilter so unusual extensions can be added per-site. Case-insensitive match. Side benefit: every other call site oftransform_to_amp_url()– filters onterm_link,author_link,attachment_link,post_type_link, the nav-menuhreffilter, the canonical-URL helper – is now also protected from inadvertently rewriting static-file URLs that themes or third-party plugins happen to pass through them. - Search-engine crawlers no longer get caught by the mobile auto-redirect to AMP.
Amp_WP_Public::amp_wp_auto_redirect_to_amp()previously checked onlywp_is_mobile(), which returns true for Googlebot-Mobile, Bingbot, YandexBot, DuckDuckBot, AppleBot, FacebookExternalHit, Twitterbot, LinkedInBot, Slurp, AhrefsBot, and SemrushBot. Each was being 302-redirected fromhttps://yoursite.com/tohttps://yoursite.com/amp/; the AMP page’s<link rel="canonical">then pointed back at the original, and Google Search Console flagged the canonical URL as “Redirect error” in Page Indexing because the URL it was trying to index kept redirecting. Google’s AMP guidelines explicitly require the canonical to be served directly to bots; AMP discovery happens via the head-tag<link rel="amphtml">, not a UA redirect. Fix: new public helperamp_wp_is_search_engine_bot(): boolinincludes/functions/amp-wp-core-functions.phpchecksHTTP_USER_AGENTagainst a 12-entry list (substring match, case-insensitive); the redirect path bails early when it returns true. The cache-plugin desktop fallback (which injects the UA-sniffing JS redirect) is implicitly safe because the server-side branch already short-circuited for bots. Filterable viaamp_wp_search_engine_bot_user_agents(UA fragment list) andamp_wp_is_search_engine_bot(final boolean). Unit-tested against 10 real-world UA strings (7 bots + 3 real users) – 10/10 pass. After the fix ships, click “Validate fix” inside Search Console; Google re-crawls the affected URLs in 1-7 days and the “Redirect error” count drops to 0. - Pagination on AMP archives was broken for any URL of the form
/<archive>/amp/page/N/. The URL-based branch ofis_amp_wp()(used duringpre_get_postsbeforetemplate_redirectfires, when theampquery var is not yet set on$wp_query) only matched the AMP token at the very start or very end of the request URI – so/shop/amp/page/2/,/product-category/<slug>/amp/page/2/,/category/<slug>/amp/page/2/,/tag/<slug>/amp/page/2/, and/<page>/amp/comment-page-N/all returnedfalse. The cascade was severe: any third-partypre_get_postscallback gated onis_amp_wp()(notably the AMP WP – WooCommerce add-on’s priority-0 bridge that re-registersWC_Query::pre_get_postsat priority 101) bailed early, AMP WP’s query isolation between priorities 1 and 100 wiped WooCommerce’s product-archive setup, and/shop/amp/page/2/rendered the shop page (post typepage, ID 588) withpaged=2instead of the products grid. The old second regex also had a separate false-positive bug whereamp/*allowed zero trailing characters and matched URIs like/ampersand/and/amp-tree/. Both regexes replaced with a single boundary-anchored pattern#(?:^|/)<amp>(?:/|$)#that detects AMP as a whole path segment anywhere in the URI; verified against 20 URL patterns (20/20 pass) including paginated archives, sub-directory installs, and the previously-broken false positives. Live verification on/shop/amp/page/2/:post_count=8 found_posts=50 max_num_pages=7 paged=2 post_type=product is_archive=1 is_post_type_archive=1 is_amp_wp=1 is_shop=1 is_woocommerce=1(waspost_count=1 paged=2 post_type="" is_archive=0 is_page=1 is_amp_wp=0before the fix). - The “Premium Extensions” tab was rendering an empty
<i>tag with no visible icon next to the label. The originalamp-wp-admin-icon-starclass did not exist inadmin/css/amp-wp-admin.cssor in the icon font atadmin/fonts/amp-wp-admin-icons/. Switched toamp-wp-admin-icon-feather, which is defined and not already in use by another tab (every star / puzzle / rocket / crown glyph variant is missing or taken). CSS and font are unchanged.
Docs
- Inline help text added to five settings tabs: Structured Data, GDPR Compliance, Notice Bar, Layout Settings, and General Settings. Around 30 new
<p class="description">blocks under every previously-undocumented option so admins do not need to consult external docs to know what each toggle / select / textarea does. Highlights: the GDPR master toggle now explains the EU/EEA IP gate and the third-party-script suppression behaviour; Layout’s “Show Slider on Home Page”, “Show Share Box In Posts”, and “Show Related Posts” parent toggles each get a description plus descriptions on every sub-control; General’s “Enable AMP on Home Page”, “Enable AMP on Search Page”, “Disable AMP on Post Types”, and “Disable AMP on Taxonomies” finally have inline guidance. All strings wrapped inesc_html_e( ..., 'amp-wp' )using the same<p class="description">pattern already established by the GDPR partial. No PHP / behaviour changes – pure documentation in the admin UI. - Inline help text added under the OneSignal options on the 3rd Party Plugins tab (
admin.php?page=amp-wp-settings#settings-third-party-plugins-support). Three new<p class="description">blocks: under “App ID” (UUID format and where to find it – OneSignal dashboard -> Settings -> Keys & IDs), under “Position” (subscription bell placement and when to override the default), and under “Subdomain” in the HTTP Site row (OneSignal-hosted HTTPS subdomain likeyoursite.os.tcthat non-HTTPS sites need for the push subscription frame; HTTPS sites should leave the field blank).
1.7.5 – 2026-05-07
- Fixed: Mobile browsers no longer get stuck in a “too many redirects” loop on AMP URLs after an admin changes “Exclude AMP by URL” or “Exclude URLs From Auto Converting”. Plugin-issued redirects (AMP-router and mobile auto-redirect) now downgrade the AMP-excluded redirect from
301 Moved Permanentlyto302 Foundand emitnocache_headers(), so neither browsers, proxies, nor CDNs can pin a stale redirect direction once the underlying setting flips. The desktop-side fallback that injects a JavaScript redirect to the AMP URL when a caching plugin is detected now also definesDONOTCACHEPAGEand emits no-cache headers, preventing cache plugins that do not separate cache by device from serving a desktop response (which contains the mobile-UA-sniffing redirect script) to mobile visitors and triggering an infinite loop in the mobile browser. Existing affected clients are auto-recovered on their next request to a now-AMP-excluded URL: the AMP-router redirect response carries a one-shotClear-Site-Data: "cache"header (HTTPS-only, gated by anamp_wp_cache_clearedcookie marker so normal HTTP caching is preserved on subsequent redirects), which evicts any stale301pinned in the mobile browser before it follows the redirect. - Fixed:
amp-stateelements are no longer stripped from AMP pages when the AMP WP Comments plugin is active. The content sanitizer’s allowed-tag list (tags-list.php) did not includeamp-state, causing the element to be silently removed during the HTML clean-up pass, which broke the AMP threading state used by the comment reply form. - Fixed: Per-comment Reply links now appear in threaded comment lists on AMP pages.
amp_wp_comment_item()was not forwarding the$depthargument fromwp_list_comments()to thecomment-item.phptemplate. WordPress 6.8 updatedget_comment_reply_link()to return null when depth is 0, so every reply link resolved to nothing. The function now accepts and passes$depth(defaulting to 1). - Fixed: Per-comment Reply button now shows the label “Reply” instead of the comment form title “Leave a Reply”. The template was pulling from
amp_wp_translation_get('comments_reply'), which returns the form heading. The button now uses__('Reply', 'amp-wp')directly. - Fixed: AFS Analytics configuration JSON was malformed. Missing commas after
websiteidandtitleproperties caused a JSON parse error in the AMP runtime, silently disabling analytics on AMP pages when AFS Analytics was configured. - Improved:
includes/class-amp-wp-redirect-router.phpbrought into WordPress Coding Standards compliance. Added a proper file-level docblock (the previous one only documented the class). Sanitized$_SERVER['REQUEST_URI']viaisset()+esc_url_raw( wp_unslash() )inredirect_to_amp_url(). Renamed the singleton accessorRun()torun()to satisfy WPCS snake_case naming; the class is documented as not-public in the family’s public API contract, so this rename is internal and the single caller inAmp_WP_Public::define_public_hooks()was updated. None of the six add-ons (cf7, comments, woocommerce, wpforms, gf, ads-manager) reference the class. - Improved:
public/class-amp-wp-public.phpbrought into WordPress Coding Standards compliance. Sanitized and unslashed all$_SERVER['REQUEST_URI']and$_SERVER['HTTP_HOST']reads viaesc_url_raw( wp_unslash() )/sanitize_text_field( wp_unslash() )withisset()guards inredirect_to_start_point_amp(),redirect_to_end_point_amp(), andamp_wp_get_requested_page_url(). Switched all internal same-origin redirects fromwp_redirect()towp_safe_redirect(). Replaced loose==comparisons with strict===inamp_wp_transform_post_link_to_amp(),amp_wp_init_json_ld(), andis_amp_excluded_by_url(); addedtruestrict flag to allin_array()calls inamp_version_exists(). Refactored the empty-body if/elseif assignment-in-condition chain inamp_wp_template_loader()into an explicit early-return ladder that preserves the “first matching template wins” semantics including the static-home-page query override and the attachment-templateprepend_attachmentfilter removal. Renamed the private helper_amp_wp_can_call_component_method()to drop the leading underscore (visibility is already encoded in theprivatekeyword) and removed its unused&$argsreference parameter; the single internal caller was updated to match. Renamed the$defaultparameter onamp_wp_get_option()to$default_valueto avoid the PHP reserved-keyword shadow. Filled in 19 missing or incomplete@paramdoc-comment lines across 13 methods. Removed three blocks of dead commented-out code that the surrounding live code superseded. No public-API behaviour change: every method signature exposed to add-ons is preserved. - Added:
tools/test-redirects.sh– a curl-based smoke harness that asserts every plugin-issued redirect on a target site behaves the way the no-cache + Clear-Site-Data + 302 fix promises. Run it against staging or production before pushing a release:BASE_URL=https://your-site.com ./tools/test-redirects.sh. Covers eight scenarios (fresh AMP-excluded redirect, cookie-gated re-request, desktop UA, full chain termination, AMP-included single post, mobile auto-redirect cache headers, query-string preservation, paginated AMP URL) with 29 individual assertions including 302 status,Cache-Control: no-cache+no-store,Clear-Site-Data: "cache"presence/absence per cookie state, hardenedSet-Cookieflags (Secure / HttpOnly / SameSite=Lax), Location target, and AMP markers in served bodies. Configurable per site via env vars (BASE_URL, AMP_EXCLUDED_URL, AMP_INCLUDED_URL, NON_AMP_URL, PAGINATED_AMP_URL). Exits 0 on green, 1 on any failure with a list of failed assertions.
1.7.4 – 2026-05-01
- Fixed: Header and sidebar logo image no longer disappears when a logo is set in AMP WP branding settings. The v1.7.2 PHPCS escaping pass wrapped the logo output in
wp_kses_post(), which strips non-standard HTML elements including<amp-img>and<amp-anim>. …
