AMP WP – Google AMP For WordPress

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

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)

  1. Log in to your WordPress admin panel.
  2. Navigate to Plugins -> Add New.
  3. Search for AMP WP.
  4. Click Install Now, then Activate.
  5. Go to AMP WP -> Customize AMP to configure your settings.

Manual Installation

  1. Download the plugin ZIP from the WordPress plugin repository.
  2. Extract the ZIP and upload the amp-wp folder to /wp-content/plugins/ via FTP or your hosting file manager.
  3. Activate the plugin from Plugins in your WordPress admin panel.
  4. 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:

  1. Prefix format: https://yoursite.com/amp/page-name
  2. Suffix format: https://yoursite.com/page-name/amp

You can choose your preferred format from AMP WP -> Settings -> General.

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 via amp-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 tag

Only 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):

  1. Open Dashboard -> AMP WP -> Settings -> General.
  2. Scroll to the Google Auto Ads section.
  3. 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-ads runtime 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 the amp_wp_adsense_publisher_id filter. Drop this into your theme’s functions.php or 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-pasted amp-auto-ads loader 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.

AdSense Auto Ads for AMP – Google Help

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

July 31, 2022
Этой самый простой и лучший плагин AMP, который я пробовала. Если бы еще добавить поддержку рекламных блоков – цены бы ему не было.

70

March 17, 2022
Our photos
March 10, 2022
Works out of the box. Just enough features for my use case. Two suggestions: 1. Would be nice to have a performance option: to enable server-side cache for amp pages specifically at the plugin level 2. Need an option to add “view desktop version” to the header as well.
February 16, 2022
The extension works better than the other AMP ones and is way more customizable. However, the authors don’t bother answering to questions on the forum. Most of the topics sat unsolved for more than 6 months. Just for that, it deserves a 2 star rating. You can’t just put a plugin out there and be like: “ok do your thing now, not concerned anymore, bye”
Read all 56 reviews

Contributors & Developers

“AMP WP – Google AMP For WordPress” is open source software. The following people have contributed to this plugin.

Contributors

Changelog

1.7.11 – 2026-05-25

  • AMP-Validity: three-layer defense for invalid CSS at-rule stripping, resolving the @view-transition AMP 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 through amp_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 into Amp_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 @media or @supports blocks. 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-transition inside the @media wrapper. The sanitizer now runs a second pass with preg_replace_callback that 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) Split amp_wp_direction() (void/echo) into amp_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 in sidebar.php continues to work unchanged. (2) Replaced the broken Amp_WP_Public::get_instance()->call_components_method() call in amp_wp_do_shortcode()Amp_WP_Public has no get_instance() method — with a direct iteration over the $amp_wp_registered_components global, matching the same logic the instance method uses. (3) Fixed possibly-undefined $area_label and $param in amp_wp_social_share_get_li(): the facebook switch case only set these variables when facebook_app_id was present; when missing, execution fell through to code that used them. Inverted the guard to return early when the app ID is absent. (4) Fixed amp_wp_comment_reply_link() assigning the void return of comment_reply_link(): set $args['echo'] = false so 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 narrow void|string based on the runtime echo key.
  • Refactor: WPCS cleanup of includes/functions/amp-wp-core-functions.php. Fixed all 36 errors and 4 warnings: added missing @param descriptions 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 @param names on amp_wp_add_inline_style and amp_wp_enqueue_inline_style, fixed @return bool on void function, added @throws tag, added translators comment, removed structured-array hash notation that tripped ParamCommentFullStop, 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 @var annotation format, and terminated all param comments with full stops. Removed dead mysql_get_server_info() branch in amp_wp_get_server_database_version() (the mysql_* extension was removed in PHP 7.0; WordPress itself dropped this codepath in 5.x). Fixed @param null -> @param WP_Query|null on is_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 $var to $tooltip in amp_wp_sanitize_tooltip() and to $value in amp_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 $string to $url in amp_wp_remove_query_string() and added missing description, moved Copyright: URL lines to proper @link tags so PHPCS no longer treats them as unterminated param comments, added missing return statement to amp_wp_remove_class_action() (was silently discarding the boolean from amp_wp_remove_class_filter()), switched loose == to strict === in amp_wp_transpile_text_to_pattern(), replaced urlencode() with rawurlencode() in amp_wp_parse_url(). No behaviour change.
  • Refactor: WPCS cleanup of includes/functions/amp-wp-ad-functions.php. Fixed 3 errors: added missing @param descriptions on amp_wp_get_ad_location_data() and amp_wp_show_ad_location(), corrected @return array to @return void on amp_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 standalone amp_wp_create_image() function to includes/functions/amp-wp-core-functions.php to resolve Universal.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 (resolves Squiz.Commenting.FileComment.Missing and Squiz.Commenting.ClassComment.SpacingAfter), fixed PHPDoc tag ordering and added missing param/return descriptions, removed inline @var type hint flagged as commented-out code, added missing summary periods. Standardized file docblock tags to @package/@subpackage/@author/@copyright/@since order.
  • Refactor: PHPCS cleanup of includes/components/class-amp-wp-playbuzz-component.php. Added separate file and class docblocks, fixed assignment-in-condition, removed inline @var type hint, added missing PHPDoc summaries and param/return descriptions, standardized file docblock tag ordering, added @subpackage tag. 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 passes phpcs --standard=WordPress with 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 in amp_wp_locate_template(); backfilled @param descriptions on more than thirty docblocks across the file (every helper from amp_wp_load_template() and amp_wp_hw_attr() through the amp_wp_set_global / amp_wp_get_prop cluster, 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 six phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames on the public signatures for amp_wp_locate_template($require_once), amp_wp_load_template($require_once), amp_wp_body_class($class), and four $default parameters across the global / prop accessors – renaming would have broken sites that call these by name; tagged the historical amp-wp-template-default-theme-mod filter at amp_wp_get_option() with a phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores plus a justification explaining why renaming to underscores would break any site filtering this 1.0.0-era public hook; added a single phpcs:ignore on the wp_reset_query() call inside amp_wp_clear_query() with a paragraph-long justification about why wp_reset_postdata() would not undo a custom amp_wp_set_query() swap; replaced rand() with wp_rand() in amp_wp_element_unique_id(); removed the @ error-silencing on number_format() inside amp_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 title sprintf( __( '%1$s: %2$s' ) ) in amp_wp_get_archive_title_fields() and corrected its textdomain from the placeholder beetter-amp to amp-wp; collapsed twelve inline comments to proper full-stop terminators; corrected the bogus @return int on amp_wp_comment_link() (the function only echoes) to @return void; tightened the $_GET whitelist intersect in amp_wp_get_canonical_url() so the existing nonce-recommended ignore lands on the exact $_GET token rather than the line above; and scoped a phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter on amp_wp_comment_item() whose $comment / $args / $depth parameters are required by the Walker_Comment callback signature and are read inside the included comment-item.php template via scope inheritance. Removed dead code: the long commented-out Google Plus social-share block inside amp_wp_social_share_fetch_count() (the service was retired by Google in 2019; the case 'google_plus': body had been mid-deletion for years, leaving unreachable if ( ! is_wp_error( $remote ) ) debris after the preceding break;), plus the dangling 'google_plus' entry in the amp_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 passes phpcs --standard=WordPress with exit 0. Added the missing file-level docblock; gave transform(), handle_gallery(), handle_slider(), slider_posts(), and gallery_attachments() proper @param descriptions; added short descriptions to the two protected renderers that had @param-only blocks. The slider_posts() docblock now carries a multi-paragraph “Uses query_posts() intentionally because…” explanation mirroring the existing Amp_WP_Public::amp_wp_set_page_query() docblock that documents the equivalent front-page override: the slider template iterates via amp_wp_have_posts() / amp_wp_the_post() (in includes/functions/amp-wp-theme-functions.php) which fall back to the global $wp_query / $wp_the_query when no custom query has been staged via amp_wp_set_query(), so a fresh WP_Query would silently iterate the page’s original main query and a wp_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() and wp_reset_query() are both phpcs:ignored with short -- see method docblock references. Also removed a dead local variable in config() ($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 passes phpcs --standard=WordPress with exit 0. Highlights: added the missing file-level docblock; backfilled missing @param descriptions on 16 methods; renamed the $string parameter on get_iframe_dimension() to $html to 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; switched in_array( $provider, $this->support_sites ) to strict mode; refactored the two assignment-in-condition patterns inside transform() into explicit fetch-then-test ladders; gave get_frame_height() a typed @param string $url; ended the inline // Fix: remove aparat iframe extra tags comment with a full stop; and scoped a single phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase block around the four $element->parentNode / $element->previousSibling DOM-API reads in sanitize().
  • 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 a while loop 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 a DOMDocumentFragment, parses the HTML under suppressed libxml errors (clearing the queue afterwards so noisy embed markup does not pollute downstream handlers), and uses replaceChild( $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 detects amp_is_request() / AMP__VERSION / the amp_post_template_footer hook), 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_id filter for theme/plugin overrides, (2) Site Kit’s stored AdSense module setting googlesitekit_adsense_settings['clientID'] – the same key already inspected by amp_wp_is_site_kit_adsense_active(). Returned value is normalised to ca-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: function amp_wp_get_adsense_publisher_id(), function amp_wp_render_auto_ads_element(), filter amp_wp_adsense_publisher_id. Runtime path: amp_wp_render_auto_ads_element() hooks amp_wp_template_body_start at priority 5 (runs before amp_wp_custom_code_body_start so a manual snippet in the body-start Customizer field continues to layer on top), gates on amp_wp_is_auto_ads_enabled(), and uses printf with esc_attr() on the data-ad-client value. 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 the amp-wp-ads-manager add-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-ads runtime loader (https://cdn.ampproject.org/v0/amp-auto-ads-0.1.js) on every AMP page via the existing amp_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 existing amp_wp_template_enqueue_scripts action and calls amp_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 same amp_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 an amp-auto-ads loader, the enqueue is skipped (duplicate guard via amp_wp_head_field_has_auto_ads_loader()); on save, AMP WP also strips that manually-pasted loader from the head-code field via amp_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() checks is_plugin_active( 'google-site-kit/google-site-kit.php' ) plus the AdSense module’s stored clientID in googlesitekit_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 both update_option_amp_wp_general_settings (subsequent saves) and add_option_amp_wp_general_settings (first-ever save where the option does not yet exist – WordPress fires add_option_<name> instead of update_option_<name> in that branch). New option key amp_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_value for 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.eu array inside the <amp-geo> JSON config was being emitted through esc_js(), which is the wrong tool for JSON content: it HTML-encodes " as &quot;, so amp-geo received a single corrupted string like PK","AT","BE",...,"CH" instead of an array of ISO country codes, and silently rejected the whole config (breaking geo-gated consent behaviour). Replaced the esc_js( implode( '","', ... ) ) pattern at public/partials/tez/components/gdpr/gdpr.php:22 with wp_json_encode( array_values( array_map( 'sanitize_text_field', $gdpr_countries ) ) ) so the country list is emitted as a valid JSON array. The amp_wp_gdpr_country_list filter contract is unchanged.
  • Refactor: WordPress Coding Standards (WPCS) cleanup of the Tez theme directory (public/partials/tez/). All 37 template files now pass phpcs --standard=WordPress with 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_switch are replaced with ! empty( $foo_switch ) (handles both string '1' and integer 1 saved values uniformly without behaviour change); the file-level docblock is present and correctly spaced on every template (added to header.php, footer.php, shortcodes/amp-slider.php, shortcodes/gallery.php, and shortcodes/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 in components/social-list/social-share.php; assignments lifted out of if conditions in attachment.php and social-share.php; dead commented-out Google Plus social-share scaffolding (a service Google retired in 2019) removed from components/social-list/social-links.php; the slider’s meta_query for 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.php and its parent directory from core. The file (a half-completed placement-manager UI sketch from an earlier design – never require-d anywhere, hooks two filters that are never fired, calls two helper functions that are not defined) was relocated verbatim to the amp-wp-ads-manager add-on at includes/placement-manager/ads.php to 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 flagged echo / printf site 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 with isset() and run through esc_url_raw( wp_unslash() ) or sanitize_text_field( wp_unslash() ) as appropriate per use site. Translation strings with intentional HTML (e.g. <strong> for inline emphasis) now route through wp_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: the WordPress.Security.EscapeOutput, WordPress.Security.NonceVerification, and WordPress.Security.ValidatedSanitizedInput sniffs 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 or phpcs:ignore annotations 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 new amp_wp_premium_extensions_section action – callbacks echo a card (heading + form-table row group) inside the tab body, and the existing amp_wp_save_setting_sections action persists each add-on’s option independently. New files: includes/admin/settings/class-amp-wp-premium-extensions.php (registers the tab) and admin/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>>, and amp_wp_has_active_premium_extension(): bool (all in includes/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 filter amp_wp_premium_extensions for 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 ): void and amp_wp_get_registered_template_paths(): array (in includes/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 filter amp_wp_template_paths lets advanced users reorder or suppress paths per-request. Companion change in amp-wp-woocommerce migrates 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_comments fires 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 forking admin/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 existing amp_wp_layout_settings[...] name prefix to persist via the existing save handler at Amp_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 hooks amp_wp_premium_extensions_section directly 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-extensions no 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'] through Amp_WP_Content_Sanitizer::transform_to_amp_url(), which blindly appended /amp to any internal URL it didn’t already recognise as an AMP path or wp-content asset. When a site has no static favicon.ico file at the document root, the browser-issued favicon request fell through to index.php, hit template_redirect on 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 new amp_wp_non_amp_url_extensions filter so unusual extensions can be added per-site. Case-insensitive match. Side benefit: every other call site of transform_to_amp_url() – filters on term_link, author_link, attachment_link, post_type_link, the nav-menu href filter, 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 only wp_is_mobile(), which returns true for Googlebot-Mobile, Bingbot, YandexBot, DuckDuckBot, AppleBot, FacebookExternalHit, Twitterbot, LinkedInBot, Slurp, AhrefsBot, and SemrushBot. Each was being 302-redirected from https://yoursite.com/ to https://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 helper amp_wp_is_search_engine_bot(): bool in includes/functions/amp-wp-core-functions.php checks HTTP_USER_AGENT against 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 via amp_wp_search_engine_bot_user_agents (UA fragment list) and amp_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 of is_amp_wp() (used during pre_get_posts before template_redirect fires, when the amp query 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 returned false. The cascade was severe: any third-party pre_get_posts callback gated on is_amp_wp() (notably the AMP WP – WooCommerce add-on’s priority-0 bridge that re-registers WC_Query::pre_get_posts at 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 type page, ID 588) with paged=2 instead of the products grid. The old second regex also had a separate false-positive bug where amp/* 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 (was post_count=1 paged=2 post_type="" is_archive=0 is_page=1 is_amp_wp=0 before the fix).
  • The “Premium Extensions” tab was rendering an empty <i> tag with no visible icon next to the label. The original amp-wp-admin-icon-star class did not exist in admin/css/amp-wp-admin.css or in the icon font at admin/fonts/amp-wp-admin-icons/. Switched to amp-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 in esc_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 like yoursite.os.tc that 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 Permanently to 302 Found and emit nocache_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 defines DONOTCACHEPAGE and 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-shot Clear-Site-Data: "cache" header (HTTPS-only, gated by an amp_wp_cache_cleared cookie marker so normal HTTP caching is preserved on subsequent redirects), which evicts any stale 301 pinned in the mobile browser before it follows the redirect.
  • Fixed: amp-state elements 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 include amp-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 $depth argument from wp_list_comments() to the comment-item.php template. WordPress 6.8 updated get_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 websiteid and title properties 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.php brought into WordPress Coding Standards compliance. Added a proper file-level docblock (the previous one only documented the class). Sanitized $_SERVER['REQUEST_URI'] via isset() + esc_url_raw( wp_unslash() ) in redirect_to_amp_url(). Renamed the singleton accessor Run() to run() 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 in Amp_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.php brought into WordPress Coding Standards compliance. Sanitized and unslashed all $_SERVER['REQUEST_URI'] and $_SERVER['HTTP_HOST'] reads via esc_url_raw( wp_unslash() ) / sanitize_text_field( wp_unslash() ) with isset() guards in redirect_to_start_point_amp(), redirect_to_end_point_amp(), and amp_wp_get_requested_page_url(). Switched all internal same-origin redirects from wp_redirect() to wp_safe_redirect(). Replaced loose == comparisons with strict === in amp_wp_transform_post_link_to_amp(), amp_wp_init_json_ld(), and is_amp_excluded_by_url(); added true strict flag to all in_array() calls in amp_version_exists(). Refactored the empty-body if/elseif assignment-in-condition chain in amp_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-template prepend_attachment filter removal. Renamed the private helper _amp_wp_can_call_component_method() to drop the leading underscore (visibility is already encoded in the private keyword) and removed its unused &$args reference parameter; the single internal caller was updated to match. Renamed the $default parameter on amp_wp_get_option() to $default_value to avoid the PHP reserved-keyword shadow. Filled in 19 missing or incomplete @param doc-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, hardened Set-Cookie flags (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>. …