in AMP, Search Engine Optimization

Implementing AMP Forms from Start to Finish with amp-form [Examples]

The introduction of the amp-form component to AMP suspended the need to use hacks and expanded its functionality allowing for a much more flexible experience. Lead capture, commenting, search capability, and other site features common to most web pages suddenly became much more achievable with AMP HTML.

As with the rest of the AMP project, the amp-form documentation seems pretty straightforward, as it’s pretty close to standard HTML. The AMP team has even built out AMP By Example, a site with different example code snippets and demos.

However, upon speaking to numerous people while speaking at SMX West on the subject, it became clear to me that the examples weren’t detailed enough and the average joe who is less familiar with web development was still struggling to implement forms on their amp pages. Actually, it seems that many aspects of AMP suffer from this, but to a greater extent AMP forms. The former lends itself to a discussion for another day and another post, but I digress. So, why is the amp-form component a struggle for people to implement?

When you get into the actual implementation for forms in AMP, there are some nuances that impact implementation. There are plenty of useful examples on the amp-form AMP by Example page, but they aren’t really end-to-end examples with the necessary server-side portions.

Although this won’t be a problem for someone with a decent grasp of web development, it often isn’t the web developers making the changes or the one advocating for AMP implementation, it’s the marketers, the SEOs of the world. So, let’s explore a couple of simple examples that will hopefully make amp-form seem a little easier.

amp-form GET Method Example

AMP forms using the GET method are the easier of the two, and likely not a problem for most people when using the amp-form component. I think it’s the POST method that has nuances that cause the most issues, but let’s explore a GET method with a tangible example anyway.

Since WordPress powers more than 25% of all websites, and has an easy deploy AMP plugin, it serves as a good example for implementation of the GET method.

Step #1: Add the amp-form script to your <head>.

amp-form is an extended AMP component, so you must include an additional script within the <head> your AMP HTML document in order to use forms.

<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>

Step #2: Construct the GET form

The following form returns results from WordPress’s internal search functionality. As with regular HTML forms, we must specify the method within an attribute in the <form> tag.

Since we are using the GET method, we can specify the action attribute, the value of which must be an https URL. If we were using the POST method, we couldn’t use the action attribute and would have to use the action-xhr attribute (see below).

We also set the target attribute to either _blank, the response is displayed in a new window or tab, or _top, the response is displayed in the full body of the window.

<form action="https://searchwilderness.com/" method="get" target="_top">
    <input name="s" placeholder="Search The Blog" type="text" required>
    <input type="submit" value="Search">
</form>

We pass the name s, which include the value of our search, performing our search upon form submission. Easy peasy.

amp-form POST Method Example

The post method is a little bit more complicated in AMP, due to two factors:

  1. Your request must be an XMLHttpRequest (XHR) request.
  2. Your endpoint has to follow the AMP CORS spec.

Let’s explore a common example, an email capture form that integrates with MailChimp. Mail…Kimp? Shimp?

Step #1: Add the extended component scripts to your <head>.

Like our GET method example, we need to make sure that the amp-form script is included in the <head> of our document. This example is also utilizing the optional amp-mustache component, so that script must also be included in the <head> of our AMP HTML document.

<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.1.js"></script>

Now, a requirement of using the POST method is that instead of using the action attribute, we must use the action-xhr attribute. As the attribute implies, our request must be an XMLHttpRequest (XHR), essentially AJAX. So, using MailChimp’s specified endpoint for my newsletter, https://searchwilderness.us6.list-manage.com/subscribe/post it simply doesn’t work.

<form name="submit" method="post" action-xhr="https://searchwilderness.us6.list-manage.com/subscribe/post" target="_top">
    <input type="email" name="MERGE0" placeholder="[email protected]" required>
    <input type="hidden" name="u" value="uvalue">
    <input type="hidden" name="id" value="idvalue">
    <input type="submit" value="Subscribe!">
</form>

The above code renders an error in Google Chrome Developer Tools:

direct posting amp form to mailchim chrome developer tools fail

So, how do build my email list on my AMP pages if this won’t work?

We need to create our own endpoint that meets the XHR/CORS requirement and use it as a middleman.

Step #2: Create an Endpoint (PHP + MailChimp Example)

To accommodate WordPress as an example, I wrote my endpoint in PHP, but you could something similar in any programming language. Feel free to follow the notes in the code’s comment to modify to your needs.

In addition to simply POSTing our data to MailChimp’s endpoint, the important bits to note are:

  • Our endpoint returns a JSON result to accommodate the XHR and amp-mustache requirement
  • Our endpoint specifies headers to satisfy the requirements of CORS
<?php
if (!empty($_POST)) {
    header("access-control-allow-credentials:true");
    header("access-control-allow-headers:Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token");
    header("access-control-allow-methods:POST, GET, OPTIONS");
    header("access-control-allow-origin:".$_SERVER['HTTP_ORIGIN']);
    header("access-control-expose-headers:AMP-Access-Control-Allow-Source-Origin");
    // change to represent your site's protocol, either http or https
    header("amp-access-control-allow-source-origin:https://".$_SERVER['HTTP_HOST']);
    header("Content-Type: application/json");
    $email = isset($_POST['email']) ? $_POST['email'] : '';
    $output = ['email' => $email];
    header("Content-Type: application/json");
    echo json_encode($output);
    // $post_data['u'] and $post_data['id'] are required hidden field per:
    // http://kb.mailchimp.com/lists/signup-forms/host-your-own-signup-forms
    $post_data['u'] = 'uvalue';
    $post_data['id'] = 'idvalue';
    // $post_data['MERGE0'] represents the value of my email submission input tag's name attribute.
    // In my case the attribut of name="MERGE0", so $post_data['MERGE0'] is used as a variable.
    $post_data['MERGE0'] = urlencode($_POST['email']);
    foreach($post_data as $key => $value) {
        $post_items[] = $key.
        '='.$value;
    }
    $post_string = implode('&', $post_items);
    // Replace URL with your own. In the case of MailChimp, see:
    // http://kb.mailchimp.com/lists/signup-forms/host-your-own-signup-forms
    $curl_connection = curl_init('https://searchwilderness.us6.list-manage.com/subscribe/post');
    curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
    curl_exec($curl_connection);
    curl_close($curl_connection);
}
?>

Save the script and upload it to your server somewhere, so it can be used as your form’s endpoint. I called mine mailchimp-post.php

Step #3: Construct the POST form

As I already mentioned, because we are using the POST method, we can’t use the action attribute. Instead we use the action-xhr attribute and the value is set to our example endpoint from above, mailchimp-post.php. I have set the target attribute to _top, but it is ignored for this type of form.

We have two input tags, one with the name and type attributes set to email and one with the type set to submit. These are passed to our endpoint script.

<form name="submit" method="post" action-xhr="mailchimp-post.php" target="_top">
    <input type="email" name="email" placeholder="[email protected]" required>
    <input type="submit" value="Subscribe!">
    <div submit-success>
        <template type="amp-mustache">
        Thanks! Check {{email}} to confirm your subscription to the newsletter.
        </template>
    </div>
</form>

We also specified a div tag with submit-success as an attribute. This is an event that is triggered if the form is submitted successfully. There are a couple of others we can also specify if we want to, submit and submit-error.

Within that div tag, we use the optional amp-mustache component, within the tag <template type="amp-mustache">, is the mustache tag {{email}} which pulls from the JSON value returned by our endpoint.

Upon successful submission of our AMP form:

  1. The email address is submitted to my newsletter through my custom endpoint which posts to MailChimp’s endpoint
  2. The value of the email JSON object is printed to our page where we specified the mustache tag of {{email}}, but only upon successful submission of our form.

The result looks like this:

successful submission of a form in amphtml for mailchimp

Conclusion

These are just simple examples and are hardly all encompassing. There’s still a whole bunch other things you can do and nuances to AMP forms, but I hope this helps some people who were struggling with them get started with implementing amp-form on their own website.

For additional examples, check out the forms.amp.html example on GitHub and of course the AMP by Example amp-form page.

Write a Comment

Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

25 Comments

  1. Hello, Paul. I really loved your post. It is the closest I’ve ever come to getting a proper guide to implementing an XHR-type submission on AMP.

    One more thing, though. Is it possible to use Formspree’s system in a similar fashion? I’ve spent a few hours trying to get it to work but facing some challenges. Not really well-vexed in PHP, still learning the basics and I really need this to finish my project.

    Any help will be greatly appreciated.

    • Hey Steve, I appreciate that. I believe it would be a very similar approach. Having never used Formspree and tried this code, you’d probably do something like this:\

      <?php
      if (!empty($_POST)) {
          header("access-control-allow-credentials:true");
          header("access-control-allow-headers:Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token");
          header("access-control-allow-methods:POST, GET, OPTIONS");
          header("access-control-allow-origin:".$_SERVER['HTTP_ORIGIN']);
          header("access-control-expose-headers:AMP-Access-Control-Allow-Source-Origin");
          // change to represent your site's protocol, either http or https
          header("amp-access-control-allow-source-origin:https://".$_SERVER['HTTP_HOST']);
          header("Content-Type: application/json");
          $email = isset($_POST['email']) ? $_POST['email'] : '';
          $output = ['email' => $email];
          header("Content-Type: application/json");
          echo json_encode($output);
          $post_data['email'] = urlencode($_POST['email']);
          foreach($post_data as $key => $value) {
              $post_items[] = $key.
              '='.$value;
          }
          $post_string = implode('&', $post_items);
          $curl_connection = curl_init('http://formspree.io/YOUREMAILHERE');
          curl_setopt($curl_connection, CURLOPT_CONNECTTIMEOUT, 30);
          curl_setopt($curl_connection, CURLOPT_RETURNTRANSFER, true);
          curl_setopt($curl_connection, CURLOPT_SSL_VERIFYPEER, false);
          curl_setopt($curl_connection, CURLOPT_FOLLOWLOCATION, 1);
          curl_setopt($curl_connection, CURLOPT_POSTFIELDS, $post_string);
          curl_exec($curl_connection);
          curl_close($curl_connection);
      }
      ?>
  2. Hi,

    nice one, where do you check that a subscription was successful?
    Is there any response possible? If yes, you can use better the Mailchimp API.

    Beside this it would be interesting how to stop spam bots 🙂

  3. Hi Paul,

    Great post! really useful.

    One thing I discovered after implementing this is that if you have a field of the form group[1234][23] and you want to send it to MailChimp you need to URLENCODE the info ie

    $post_data[urlencode(‘group[1234][23]’)] = urlencode($_POST[‘group_1’]);

    It is something which was not obvious (to me) in the MailChimp docs.

  4. hi
    amp form post classic asp
    header(“access-control-allow-credentials:true”);
    header(“access-control-allow-headers:Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token”);
    header(“access-control-allow-methods:POST, GET, OPTIONS”);
    header(“access-control-allow-origin:”.$_SERVER[‘HTTP_ORIGIN’]);
    header(“access-control-expose-headers:AMP-Access-Control-Allow-Source-Origin”);

  5. Hi Paul,

    Many thanks for writing this – it’s the clearest explanation I’ve found, and I’ve been reading up on this for a few days now and getting nowhere!

    I am converting my existing HTML pages to AMP and I have a simple search form that POSTs values to a search results page (php). The search results page (which I’m deciding to leave as non-AMP because of various custom JS functions that I can’t replace at the moment) then uses those values to perform a search.

    I’m still struggling to understand how I can POST values from an AMP form to another page.

    It looks like this can be achieved fairly easily with a GET form, as you describe above, but I can’t seem to find an example of actually navigating to another page and passing through those variables using POST.

    Any thoughts much appreciated.

    Thanks

  6. Hi Paul –

    Thanks for your post. This is a very helpful post.

    Unfortunately, I can’t get this to work. This is impossibly hard in AMP, and the AMPbyExample pages are of no help.

    Thanks again for trying to make this simple for non-expert developers like me.

  7. I’m very grateful to finally find an article from someone who speaks plain language on this issue. I write Perl CGI scripts for my website. I am not a networking expert and was finding this CORS issue really frustrating after having spent so much time working out my AMP web page formatting and then being faced with a conundrum way over my head. After bouncing back and forth between overly technical specs on generating headers, which always leave out something critical (sometimes I think intentionally), there you go and plainly state that forms can be implemented in AMP without the need to use CORS by simply using the GET method instead of POST, and that was all I needed. Thanks much.

  8. Thank you for creating this tutorial Paul! It is exactly what I need to do. I can echo my $output in json format, but it isn’t adding to my MailChimp list. Anyway you could take a quick peek and see if I am missing something? Fresh eyes on this would really help!

  9. I got it working! What a wonderful tutorial, Paul. Thank you for taking the time to write this for us.

  10. I tried implementing the example but I’m getting error “action-xhr” “must start with “https://” or “//” or be relative and served from either https or from localhost. Invalid value:” “processsubmit.php”

    ….

    I followed your example though. Any ideas?

  11. Thanks Paul, this put me on the right track.

    Question – Where do I find the URL endpoint for other email services? I’m using Constant Contact.