How to Develop with the AMP Plugin

In this section, you will learn how to develop plugins, themes, and sites using the AMP plugin. You’ll walk through the different parts of the plugin as you learn about the development workflow and process.

AMP Settings Page

Let’s start with the AMP Settings page. This page provides you with different options for setting up AMP on the site.

There are 3 Template Modes: Native, Paired, and Classic. To learn more about these modes, visit the this document.

Developing while in Native or Paired mode will be the most useful. Both of these modes use the active theme and provide a more rich AMP experience.

Developing in Classic mode helps with backwards compatibility with previous version of this plugin, but it does not use the main theme templates, does not support widgets, and it only supports posts, pages, and media.

If you’re developing a theme, you can also add theme support programmatically.

AMP Plugin Compatibility Tool

The AMP plugin validates the web page’s markup. When possible, it modifies the document and creates a fully valid AMP document. Some elements are converted into AMP components, e.g. <img> to <amp-img>. Invalid nodes, such as <script>, are stripped out.

The challenge is retaining the full functionality after the plugin strips invalid nodes. This can be more difficult when interactivity and functionality are delivered via JavaScript.

AMP provides a large library of components, but there are also some tags and attributes that it doesn’t allow. As a developer you need to identify the invalid nodes that are stripped out and then find the right components or functionality to replace each of them with.

How do you know what is invalid? The Compatibility Tool provides you with the information you need to know about what the problem is and where it exists. Using this tool, you will be able to work through each of the problem areas. If a node needs to be replaced, you can then leverage the AMP Project’s library.

Let’s review the two main admin pages in the Compatibility Tool and how you can use them.

AMP Validated URLs Page

As you’re developing, you need to know what URLs are invalid and where you need to focus your attention. Start with the AMP Validated URLs page, as it shows you the AMP validation errors and the sources of those errors:

Notice that the theme is generating an invalid <object>:

From here, you can “Recheck” the URL or dive deeper into the “Details” to better understand the problems:

AMP Validation Error Index

As you’re developing, you’ll want to know the types of validation errors. The AMP Validation Error Index page provides this information for you. You can view a list of errors. Then for each error, this index provides the error, status, type of error, when it was last seen, and the number of URLs where this error was found.

This index page is highly useful for finding certain types of errors.

For example, let’s say you’re working to reduce the size of the stylesheet. Using this tool, you can validate CSS Errors to know if its over the 50 KB limit, even with the AMP plugin’s tree-shaking. Using the filter by error type, you could filter by “CSS Errors” as shown in this example:

AMP Components

The AMP plugin will automatically convert certain elements to AMP components. This conversion does not cause a validation error.

Here are all of the the HTML elements that the plugin will convert to their AMP equivalents:

Non-AMPAMP
<img><amp-img>
<iframe><amp-iframe>
<form><amp-form>
<audio><amp-audio>
<video><amp-video>

In most cases, there’s nothing you need to do, as the plugin automatically handles the conversion for you. One exception is creating forms that submit a POST request (see details below).

The AMP plugin uses sanitizers to convert the elements to AMP. These sanitizers are found in includes/sanitizers/. They include allowed tags, audio, comments, embed, form, gallery, and more. Refer to the repository for a complete list of sanitizers.

There are also sanitizers that convert almost all native WordPress embeds and shortcodes to AMP. Here’s the current support. In most cases, you won’t need to do anything to make these AMP-compatible.

How about native WordPress functions?  Best practice is to continue using these native functions. For example, you can use wp_get_attachment_image() and then let the plugin convert the <img> to an <amp-img>.

Most native WordPress embeds are also automatically converted to AMP components. Including Facebook, Instagram, YouTube, and Vimeo.

Creating Forms that Submit POST Requests

When there’s a form that submits via POST, i.e. <form method="post">, there are additional steps you’ll need to take.  

See the AMP documentation for more details.

Plugin Development

If your plugin doesn’t affect the front-end of the site, then there’s no concern for AMP compatibility. A good example is when your plugin is adding a custom taxonomy metabox to the editor.

However, when your plugin or theme does affect the front-end of the site, then you may need to dive deeper to resolve AMP validation errors. JavaScript is probably the most likely cause of AMP incompatibility, as scripts are not allowed and will be stripped out.

Plugin does automaticallyYou might need to do
Renders a fully valid AMP document, if AMP is enabled for the URLPrevent outputting invalid elements, so that validation errors don’t disable AMP
Removes invalid elements, as long as AMP isn’t disabled by validation errorsReimplement the removed elements, like the example below of AMP bind replacing JavaScript
Outputs extended component scripts, like
<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
nothing

Example: Plugin Adds a Widget

Let’s walk through the AMP validation process using an example plugin called City Info. You’ll learn how to make this plugin AMP-compatible.

This plugin’s widget uses JavaScript to display an image for a selected city:

1. In /wp-admin, go to “AMP Settings” > “Template Mode,” and select “Paired”:

2. Go to a URL that has the widget. Then click “View AMP” > “Re-validate” in the admin bar:

3. This will take you to the validator UI:

4. Click in the “Error” column, and you’ll see the invalid node highlighted in yellow. This is a <script>. On other plugins you might see errors with other elements or attributes, like onmouseover.

5. In the “Sources” column, look for the name of the plugin. In this example, the City Info plugin appears, so there’s an error to address.

Addressing The Error

The best way to handle this is to prevent the script from enqueuing on AMP URLs, and reimplement the JavaScript with amp-bind.

For example, here is the original snippet that enqueues the script:

if ( is_active_widget( ‘city_info’ ) ) {
        enqueue_script( ‘city-info-selection’ );
}

First, create a function to check whether it’s on an AMP URL:        

/**
 * Whether this is an AMP endpoint.
 *
 * @see https://github.com/Automattic/amp-wp/blob/e4472bfa5c304b6c1b968e533819e3fa96579ad4/includes/amp-helper-functions.php#L248
 * @return bool
 */
function city_info_is_amp() {
        return function_exists( ‘is_amp_endpoint’ ) && is_amp_endpoint();
}

Then, use that function to only enqueue the script for a non-AMP URL:

if ( is_active_widget( ‘city_info’ ) && ! city_info_is_amp() ) {
        enqueue_script( ‘city-info-selection’ );
}

Next, we’ll reimplement the JavaScript with amp-bind.

To review how this widget works, changing the value of the <select> causes the image’s src to change:

The original non-AMP markup is:

<select id="destination-city" title="<?php esc_html_e( 'Destination City', 'city-info' ); ?>">
          <option value="guadalajara"><?php esc_html_e( 'Guadalajara', 'city-info' ); ?></option>
          <option value="paris"><?php esc_html_e( 'Paris', 'city-info' ); ?></option>
          <option value="tokyo"><?php esc_html_e( 'Tokyo', 'city-info' ); ?></option>
</select>
<img id="selected-city" src="https://upload.wikimedia.org/wikipedia/commons/2/29/Plaza_de_Armas%2C_Guadalajara%2C_Jalisco%2C_M%C3%A9xico.jpg">

This uses a JavaScript file with an event handler to listen for the <select> value changing:

document.addEventListener( 'DOMContentLoaded', function() {
          const cities = {
                  guadalajara: 'https://upload.wikimedia.org/wikipedia/commons/2/29/Plaza_de_Armas%2C_Guadalajara%2C_Jalisco%2C_M%C3%A9xico.jpg',
                paris: 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/2007_Paris_Seine.jpg/1200px-2007_Paris_Seine.jpg',
               tokyo: 'https://upload.wikimedia.org/wikipedia/commons/6/67/Tokyo_night_view_1.jpg'
  };

               document.getElementById( 'destination-city' ).addEventListener( 'change', function( event ) {
                       const cityImage = cities[ event.target.value ];

                       if ( event.target.matches( 'select' ) && cityImage ) {
                               document.getElementById( 'selected-city' ).setAttribute( 'src', cityImage );
                       }
               } );
} );

To implement this with AMP bind, you can start by storing the cities constant from the script above in an <amp-state> element. This is similar to storing a global JavaScript value.

This can be in the markup, right above the <select>. Not in a JavaScript file.

<?php if ( city_info_is_amp() ) : ?>
        <amp-state id="cityInfo">
                <script type="application/json">
                        {
                                "selectedCity": "",
                                "guadalajara": "https://upload.wikimedia.org/wikipedia/commons/2/29/Plaza_de_Armas%2C_Guadalajara%2C_Jalisco%2C_M%C3%A9xico.jpg",
                                “paris": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/2007_Paris_Seine.jpg/1200px-2007_Paris_Seine.jpg",
                               "Tokyo": "https://upload.wikimedia.org/wikipedia/commons/6/67/Tokyo_night_view_1.jpg"
                       }
                </script>
        </amp-state>
<?php endif; ?>

The <script> inside the AMP-state is very similar to the cities constant from the script above. But it uses JSON format, and it has a "selectedCity" value. We’ll look at that later.

Next, you can implement the <select> change handler from the JavaScript file above. This will involve adding attributes to the first and last lines here:

<select id="destination-city" title="<?php esc_html_e( 'Destination City', 'city-info' ); ?>" <?php echo city_info_is_amp() ? 'on="change:AMP.setState({ cityInfo: {selectedCity: event.value}})"' : ''; ?>>
        <option value="guadalajara"><?php esc_html_e( 'Guadalajara', 'city-info' ); ?></option>
        <option value="paris"><?php esc_html_e( 'Paris', 'city-info' ); ?></option>
        <option value="tokyo"><?php esc_html_e( 'Tokyo', 'city-info' ); ?></option>
</select>
<img id="selected-city" src="https://upload.wikimedia.org/wikipedia/commons/2/29/Plaza_de_Armas%2C_Guadalajara%2C_Jalisco%2C_M%C3%A9xico.jpg" <?php echo city_info_is_amp() ? '[src]="cityInfo[cityInfo.selectedCity]"' : ''; ?>>

On the first line above, notice the on attribute:

<?php echo city_info_is_amp() ? 'on="change:AMP.setState({ cityInfo: {selectedCity: event.value}})"' : ''; ?>

This creates a handler that will update the selectedCity value from the <amp-state>. For example, if the user changes the <select> to tokyo, the value of cityInfo.selectedCity will also change to tokyo.

Then, the last line updates the src of the <img> based on that new value:

<?php echo city_info_is_amp() ? '[src]="cityInfo[cityInfo.selectedCity]"' : ''; ?>

AMP attributes that are wrapped in brackets use dynamic values. For example:

<img src=”/foo.png” [src]=”fooState.imageSrc”>

The src is the initial value, and the [src] can change dynamically.

It’s important to set an initial value of the src, in addition to the dynamic [src]. Otherwise, there won’t be a src value on loading the page.

Alternative Option

You can also let the plugin strip the <script> and report the validation error.

The plugin’s users might have to accept the error, so that it doesn’t block AMP.

To avoid blocking AMP, in the validation UI from step 4 above, the users will need to ensure that the “Status” of the error is either “New Accepted” or “Accepted”:

See AMP Validation for more information about these statuses.

If the JavaScript isn’t critical, or if it has a <noscript> fallback, you might decide to not reimplement it with AMP bind.

Also, when activating your plugin, you’ll see a notice if it immediately outputs invalid AMP:

But this only reports some of the possible errors. For this example City Info plugin, it won’t report the error until a widget is added.

Theme Development

The plugin handles many AMP requirements for you, including most of the markup and metadata in the Creating your AMP HTML page.

Plugin automatically outputsYou might need to do
The amp attribute in <html>nothing
<script async src="https://cdn.ampproject.org/v0.js"></script>nothing
Extended component scripts, like <script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
nothing
<link rel="canonical" href="$FOO_URL">nothing
<style amp-custom>Check the validator in /wp-admin to see if there are excessive style rules or other styling errors
An allowed style rule when it finds the disallowed !importantnothing
AMP components when it finds an HTML equivalent, like <amp-img> for <img>Sometimes make changes, like to <form method="post">

Component Scripts

AMP requires scripts for extended components. For example, if there’s an <amp-audio> element, there should also be a <script async custom-element="amp-audio" src="https://cdn.ampproject.org/v0/amp-audio-0.1.js"></script>.

But the plugin will automatically output the component scripts when it finds these elements in the document. You don’t need to do anything.

Styling

The plugin handles much of the work for you for stylesheets and inline styles.

Automatic <style amp-custom>

The AMP plugin lets you enqueue stylesheets using wp_enqueue_style(). It then prints the styles in the <style amp-custom>.  

CSS Tree Shaking

AMP limits custom styles to 50KB. The AMP plugin performs CSS tree shaking for each URL to compare the markup to the needed styles. It removes unneeded styles, making it easier for you to get under that 50KB limit.

Even with tree shaking, it is possible that a web page has more than 50KB of styling. If that happens, you will need to manually evaluate the page and reduce the styling.

You can see if there’s too much styling enqueued by going to the AMP Validation Error Index, filtering for CSS Errors, and looking for excessive_css errors:

You might also disable the admin bar, as this takes around a third (17KB at the time of writing) of the allowed 50KB. Simply go to AMP Settings and check “Disable admin bar on AMP pages”:

Inline Styles

When there’s an inline style attribute, the plugin will create a class with the style rule and add it to the element. So you still won’t need to change anything, and there won’t be a validation error.

For example, this element:

<span style="margin-right: 5px">Lorem ipsum</span>

…will have the style attribute stripped, and replaced with a class that has the style rule:

!important Qualifier

The !important qualifier isn’t allowed in AMP, so the plugin will convert it to an allowed style rule, and output it in the <style amp-custom>. You won’t have to do anything, and there won’t be a validation error.

For example, this style rule:

.blocks-gallery-item figcaption {
    font-size: 1.2rem !important;
}

…will appear as an allowed style rule, with enough specificity to override other styles:

Example Theme

You can use WP Rig to develop your theme, as it is AMP-ready.

Otherwise, you can take the following steps, which are similar to those in WP Rig.

Step 1

Define a function similar to wprig_is_amp():

function prefix_is_amp() {
        return function_exists( ‘is_amp_endpoint’ ) && is_amp_endpoint();
}

Step 2

Use this function to prevent enqueuing or printing scripts on non-AMP URLs:

add_action( ‘wp_enqueue_scripts’, function() {
        if ( ! prefix_is_amp() ) {
                return;
        }
        wp_enqueue_script( ‘example-script’ );
        // Enqueue more scripts.
} );

Step 3

The comments.php template on non-AMP pages usually allows displaying new comments without a page refresh. But this is with JavaScript, which isn’t allowed in AMP.

So you can optionally implement this in AMP with amp-live-list. WP Rig has an example of this in its comments.php:

<?php if ( wprig_using_amp_live_list_comments() ) : ?>
        <amp-live-list
        <!-- continue here -->
<?php endif; ?>

This also requires taking steps 4 and 5 below.

Step 4

You can optionally register theme support for AMP with add_theme_support( ‘amp’ ), with optional arguments. But this is required for amp-live-list support, along with the argument mentioned in Step 5.

-list support, along with the argument mentioned in Step 5.

Registering theme support programmatically is most useful if a theme is only intended for AMP. For example, if it uses AMP components like amp-carousel, you may want to force Native mode.

This will ensure that there aren’t non-AMP URLs, unless users opt-out of AMP on certain templates with /wp-admin > “AMP Settings” > “Supported Templates.”

Adding theme support will force Native or Paired mode, depending on the arguments array(), or lack thereof.

Please see AMP Plugin Serving Strategies for more details on adding theme support, including the arguments array().

The alternative to adding theme support programmatically is letting the user set the template mode via /wp-admin > “AMP Settings” > “Template Mode.”

Step 5

If you add theme support for ‘amp’, pass ’comments_live_list’ => true to the arguments array():

add_theme_support( 'amp', array(
        'comments_live_list' => true
) );     

This enables the live-refreshing of comments from step #3 above.

Step 6

Reimplement JavaScript, possibly using amp-bind. Here’s an example of an expandable menu that is implemented in JavaScript for non-AMP and amp-bind for AMP.

As that example shows, you can use something like prefix_is_amp() to only output the AMP markup when needed:

if ( prefix_is_amp() ) :
        ?>
        <amp-state …>
        <!-- continue here -->
        <?php
endif;

Step 7

Check for validation errors from the theme by going to “AMP Settings” > “AMP Validated URLs”:

Check for validation errors

If you see the theme name in the “Sources” column, click the URL to the left, like /2018/ in the screenshot. You’ll then see:

Click the first item in the “Error” column (circled in the screenshot above). You’ll see information about the invalid markup. In this case, it’s from the theme outputting a disallowed <object>.

Custom Blocks

When developing custom blocks, you’ll see any validation errors above the block:

As usual, the most common source of errors will probably be JavaScript.

You also may want to develop blocks for AMP components. For example, this plugin has blocks for several media components, including amp-jwplayer and amp-ooyala-player.

These will only work with AMP, so it’s best to only add them when in Native mode.

You might want to enqueue the block registration script like how this plugin does:

add_action( 'enqueue_block_editor_assets', function() {
        if ( function_exists( ‘amp_is_canonical’ ) && amp_is_canonical() ) {
                wp_enqueue_script( ‘my-block-script’ );
        }
} );     

So the block will only appear when using Native AMP: