In this tutorial I’ll explain how to create a “Mailchimp subscribe form widget” using WordPress, the Mailchimp API, and AJAX. It will be aimed at competent beginner developers, and requires some understanding of PHP and WordPress development. There’s a lot to do, so let’s get stuck in!
A Quick Note on APIs
“The web has gotten really “API-ish”. By that I mean, almost every major site is pushing and pulling content to and from other sites.” – Scott Fennell
I’ve covered APIs a couple of times in recent tutorials, so for a quick definition and to help you get up and running with REST APIs I recommend you take a look at these two resources:
-
REST API
How to Use the WordPress REST API: A Practical Tutorial
Karen Pogosyan
-
WordPress Plugins
How to Incorporate External APIs in Your WordPress Theme or Plugin
Karen Pogosyan
As far as Mailchimp’s API and REST goes, from the documentation:
“Most APIs are not fully RESTful, including the Mailchimp API. But Mailchimp follows most of the practices and common definitions of the style. For example, the Mailchimp API has what we call “resources,” which are typically nouns like “subscribers” or “campaigns.” You take action on resources using the standard HTTP methods: POST, GET, PATCH, and DELETE.”
Beginning the Mailchimp Subscribe Form Plugin
We
will add our widget as a simple plugin called mailchimp-widget. I won’t
describe in detail how to create a plugin, but I’ll provide some resources below to help you get started if you need them. Begin by creating a
folder called mailchimp-widget and inside that folder create a mailchimp-widget.php
file. Add the following File Header code snippet to the top of the file:
/* Plugin Name: Mailchimp widget Plugin URI: http://www.enovathemes.com Description: Mailchimp subscribe widget Author: Enovathemes Version: 1.0 Author URI: http://enovathemes.com */ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly }
A REST API, as with any API, works with an API key. So at this point you’ll need a
Mailchimp account, you’ll need to create a mailing list (nowadays referred to as an “audience”) and the API key.
Mailchimp provides full and detailed information
about how to generate your own API key.
And as with any API, Mailchimp have also published very thorough documentation that we can refer to.
So here is our plan:
- First we will fetch your Mailchimp lists, so you
can choose from the widget settings the list to which your users will be subscribed. - Next we will build the widget itself.
- Then we will create the action that takes your
user data from the Mailchimp subscribe form and sends it to Mailchimp using AJAX and the REST API.
-
News
A Crash-Course in WordPress Plugin Development
Jeffrey Way
-
WordPress
Introduction to WordPress Plugin Development
Rachel McCollin
Fetch the Mailchimp Lists
Let’s create a simple function that connects to the Mailchimp API using cURL, then caches the
results in a WordPress “Transient” (a way to cache information).
At the top of your plugin add the Mailchimp REST API key as a constant:
define('MAILCHIMP_API', 'caa6f34b24e11856eedec90bc997a068-us12-my-fake-api');
Next,
let’s create the Mailchimp connect function. The naming is optional, but should
be descriptive.
mailchimp_connect( $url, $api_key, $data = array() ) {}
This function requires several parameters:
- $url – the Mailchimp REST API endpoint
- $api_key – our API key
- $data – the data we have to transfer to Mailchimp.
Inside
that function add the following code:
$url .= '?' . http_build_query($data);
As this function will be used to get results from Mailchimp,
we need to build a query from our data and make it part of the url.
With that done we are ready to initialize a cURL connection. Add
the following code after it:
$mailchimp = curl_init();
And now using the curl_setopt()
function
we can pass the parameters to the curl_init
we created earlier.
$headers = array( 'Content-Type: application/json', 'Authorization: Basic '.base64_encode( 'user:'. $api_key ) ); curl_setopt($mailchimp, CURLOPT_HTTPHEADER, $headers); curl_setopt($mailchimp, CURLOPT_URL, $url ); curl_setopt($mailchimp, CURLOPT_RETURNTRANSFER, true); curl_setopt($mailchimp, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($mailchimp, CURLOPT_TIMEOUT, 10); curl_setopt($mailchimp, CURLOPT_USERAGENT, 'PHP-MCAPI/2.0'); curl_setopt($mailchimp, CURLOPT_SSL_VERIFYPEER, false);
Whoa, Slow Down There Cowboy
Fair enough, let
me explain what is going on here!
- As with any REST API connection we will need
to specify the content type we expect to get and make a simple authorization
using our API key. That is all dealt with in the$headers
variable. - Next we set the URL option.
-
CURLOPT_RETURNTRANSFER
is where we tell the curl not to echo the result, but instead write it into variable. - After
that we set the request type; the Mailchimp API supports
POST
,GET
,PATCH
,PUT
, andDELETE
. - Next we specify the timeout in seconds.
Then the final two options you can ignore; we use them to specify the user agent and set false
to certificate verification for TLS/SSL connection.
Now we can execute the
curl and return the results. The full function looks like this:
function mailchimp_connect( $url, $api_key, $data = array() ) { $url .= '?' . http_build_query($data); $mailchimp = curl_init(); $headers = array( 'Content-Type: application/json', 'Authorization: Basic '.base64_encode( 'user:'. $api_key ) ); curl_setopt($mailchimp, CURLOPT_HTTPHEADER, $headers); curl_setopt($mailchimp, CURLOPT_URL, $url ); curl_setopt($mailchimp, CURLOPT_RETURNTRANSFER, true); curl_setopt($mailchimp, CURLOPT_CUSTOMREQUEST, 'GET'); curl_setopt($mailchimp, CURLOPT_TIMEOUT, 10); curl_setopt($mailchimp, CURLOPT_USERAGENT, 'PHP-MCAPI/2.0'); curl_setopt($mailchimp, CURLOPT_SSL_VERIFYPEER, false); return curl_exec($mailchimp); }
If no API key is provided you will see an error
message where you would otherwise expect to see the subscribe list select.
Construct the Endpoint URL
Next up, let’s create the Mailchimp url
with the api endpoint:
$url = 'https://' . substr($api_key,strpos($api_key,'-')+1) . '.api.mailchimp.com/3.0/lists/';
Information on API endpoints can be found in the official Mailchimp documentation.
We now need to check if a transient already exists;
if not we should make a new Mailchimp list fetch, otherwise we will return the
results from transient. This way we can save resources with caching. If
there are no results we will show an error message. If any API key is provided
I always use it as a part of the transient name, as a way of ensuring unique naming.
if ( false === ( $mailchimp_list = get_transient( 'mailchimp-' . $api_key ) ) ) { // List fetch here } if ( ! empty( $mailchimp_list ) ) { return unserialize( base64_decode( $mailchimp_list ) ); } else { return new WP_Error( 'no_list', esc_html__( 'Mailchimp did not return any list.', 'mailchimp' ) ); }
Fetch Mailchimp Lists
So
now let’s fetch the Mailchimp list. Add the following code:
$data = array('fields' => 'lists','count' => 'all',); $result = json_decode( mailchimp_connect( $url, 'GET', $api_key, $data) );
Here we send a request to Mailchimp using our previously created
function mailchimp_connect()
to get all the lists available. And as the result is
required in JSON format we’ll need to decode it.
First let’s make sure that we have the results,
otherwise we’ll display the error message:
if (! $result ) { return new WP_Error( 'bad_json', esc_html__( 'Mailchimp has returned invalid data.', 'mailchimp' ) ); } if ( !empty( $result->lists ) ) { foreach( $result->lists as $list ){ $mailchimp_list[] = array('id' => $list->id, 'name' => $list->name,); } } else { return new WP_Error( 'no_list', esc_html__( Mailchimp did not return any list.', 'mailchimp' ) ); }
After the error check, if we do have valid
data and we have at least one Mailchimp list we add the Mailchimp list id and
name to our mailchimp_list
array. Lastly, if there is valid data, but no list, we
display the second error message.
Now
we need to encode, serialize and set the transient:
if ( ! empty( $mailchimp_list ) ) {$mailchimp_list = base64_encode( serialize( $mailchimp_list ) ); set_transient( 'mailchimp-' . $api_key, $mailchimp_list, apply_filters( 'null_mailchimp_cache_time', WEEK_IN_SECONDS * 2 ) );}
So the full function looks like this:
function mailchimp_list() { $api_key = MAILCHIMP_API; if (empty($api_key)) { return new WP_Error( 'no_api_key', esc_html__( 'No Mailchimp API key is found.', 'mailchimp' ) ); } if ( false === ( $mailchimp_list = get_transient( 'mailchimp-' . $api_key ) ) ) { $data = array( 'fields' => 'lists', 'count' => 'all', ); $result = json_decode( mailchimp_connect( $url, 'GET', $api_key, $data) ); if (! $result ) { return new WP_Error( 'bad_json', esc_html__( 'Mailchimp has returned invalid data.', 'mailchimp' ) ); } if ( !empty( $result->lists ) ) { foreach( $result->lists as $list ){ $mailchimp_list[] = array( 'id' => $list->id, 'name' => $list->name, ); } } else { return new WP_Error( 'no_list', esc_html__( 'Mailchimp did not return any list.', 'mailchimp' ) ); } // do not set an empty transient - should help catch private or empty accounts. if ( ! empty( $mailchimp_list ) ) { $mailchimp_list = base64_encode( serialize( $mailchimp_list ) ); set_transient( 'mailchimp-' . $api_key, $mailchimp_list, apply_filters( 'null_mailchimp_cache_time', WEEK_IN_SECONDS * 2 ) ); } } if ( ! empty( $mailchimp_list ) ) { return unserialize( base64_decode( $mailchimp_list ) ); } else { return new WP_Error( 'no_list', esc_html__( 'Mailchimp did not return any list.', 'mailchimp' ) ); } }
Build the WordPress Widget
As with plugin development basics, I won’t cover basic widget creation here, but ours will be a relatively simple widget and I will highlight the most important parts of it.
add_action('widgets_init', 'register_mailchimp_widget'); function register_mailchimp_widget(){ register_widget( 'WP_Widget_Mailchimp' ); } class WP_Widget_Mailchimp extends WP_Widget { public function __construct() { parent::__construct( 'mailchimp', esc_html__('* Mailchimp', 'mailchimp'), array( 'description' => esc_html__('Mailchimp subscribe widget', 'mailchimp')) ); } public function widget( $args, $instance ) { wp_enqueue_style('widget-mailchimp'); wp_enqueue_script('widget-mailchimp'); extract($args); $title = apply_filters( 'widget_title', $instance['title'] ); $list = $instance['list'] ? esc_attr($instance['list']) : ''; $output = ""; $output .= $before_widget; $output .='<div class="mailchimp-form">'; if ( ! empty( $title ) ){$output .= $before_title . $title . $after_title;} $output .='<form class="et-mailchimp-form" name="et-mailchimp-form" action="'.esc_url( admin_url('admin-post.php') ).'" method="POST">'; $output .='<input type="text" value="" name="fname" placeholder="'.esc_html__("First name", 'mailchimp').'">'; $output .='<input type="text" value="" name="lname" placeholder="'.esc_html__("Last name", 'mailchimp').'">'; $output .='<div>'; $output .='<input type="text" value="" name="email" placeholder="'.esc_html__("Email", 'mailchimp').'">'; $output .='<span class="alert warning">'.esc_html__('Invalid or empty email', 'mailchimp').'</span>'; $output .= '</div>'; $output .='<div class="send-div">'; $output .='<input type="submit" class="button" value="'.esc_html__('Subscribe', 'mailchimp').'" name="subscribe">'; $output .='<div class="sending"></div>'; $output .='</div>'; $output .='<div class="et-mailchimp-success alert final success">'.esc_html__('You have successfully subscribed to the newsletter.', 'mailchimp').'</div>'; $output .='<div class="et-mailchimp-error alert final error">'.esc_html__('Something went wrong. Your subscription failed.', 'mailchimp').'</div>'; $output .='<input type="hidden" value="'.$list.'" name="list">'; $output .='<input type="hidden" name="action" value="et_mailchimp" />'; $output .= wp_nonce_field( "et_mailchimp_action", "et_mailchimp_nonce", false, false ); $output .='</form>'; $output .='</div>'; $output .= $after_widget; echo $output; } public function form( $instance ) { $defaults = array( 'title' => esc_html__('Subscribe', 'mailchimp'), 'list' => '', ); $instance = wp_parse_args((array) $instance, $defaults); $list_array = mailchimp_list(); ?> <p> <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php echo esc_html__( 'Title:', 'mailchimp' ); ?></label> <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr($instance['title']); ?>" /> </p> <?php if ( is_wp_error( $list_array ) ): ?> <p><?php echo wp_kses_post( $list_array->get_error_message() ); ?></p> <?php else: ?> <p> <label for="<?php echo $this->get_field_id( 'list' ); ?>"><?php echo esc_html__( 'List:', 'mailchimp' ); ?></label> <select class="widefat" id="<?php echo $this->get_field_id( 'list' ); ?>" name="<?php echo $this->get_field_name( 'list' ); ?>" > <?php foreach ( $list_array as $list ) { ?> <option value="<?php echo $list['id']; ?>" <?php selected( $instance['list'], $list['id'] ); ?>><?php echo $list['name']; ?></option> <?php } ?> </select> </p> <?php endif; ?> <?php } public function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = strip_tags( $new_instance['title'] ); $instance['list'] = strip_tags( $new_instance['list']); return $instance; } }
Here
is the full widget code, paste it inside your plugin’s main file at the end. The most important parts are the public function widget, and public
function form.
Public Function Form
Here, using our previously created function mailchimp_list()
, we
fetch the subscription lists from your account and create a select with the data,
so when adding the widget you can choose the list to which you want your users to
subscribe.
Public
Function Widget
wp_enqueue_style('widget-mailchimp'); wp_enqueue_script('widget-mailchimp');
Before anything else we enqueue the script and style files for the widget,
so go to the top of your plugin and add the code, right after the Mailchimp API constant.
function register_mailchimp_script(){ wp_register_script( 'widget-mailchimp', plugins_url('/js/widget-mailchimp.js', __FILE__ ), array('jquery'), '', true); wp_register_style('widget-mailchimp', plugins_url('/css/widget-mailchimp.css', __FILE__ )); }
Be sure to create the js and css folders referenced here, and create the corresponding widget-mailchimp.js/css files inside those folders. The JS file handles
the AJAX request and the CSS file adds basic styling.
After that we create the Mailchimp subscribe form structure itself. Here we have
three fields visible to the user:
- the fname: First name
- lname: Last name
- and email.
Hidden Fields
Pay close attention to this code:
$output .='<input type="hidden" value="'.$list.'" name="list">'; $output .='<input type="hidden" name="action" value="et_mailchimp" />'; $output .= wp_nonce_field( "et_mailchimp_action", "et_mailchimp_nonce", false, false );
This is a very important part of the form; three crucial fields hidden from
the user.
- The first one is the list that the user
will be subscribed to. - The second one is the action that is required to handle
the Mailchimp subscription functionality from the AJAX request. You can give any
value to it, but remember it as we will need it in future. - And the last one is
the nonce field that is used for validation. In other words this field value
should be validated to make sure the request comes from our site.
And also take a closer look at the action and method
attributes of the form:
action="'.esc_url( admin_url('admin-post.php') ).'" method="POST"
Now if you go to Admin > Appearance > Widgets you will see
our newly added widget. Add it to the widget area and you will see the subscribe form appear on the front-end!
Looking good! If you try to subscribe now nothing will happen as we haven’t yet created the AJAX and action handler yet. Let’s do that next.
Create AJAX POST and Action Handler
Open the JavaScript file we created earlier and paste the following (large amount of) code into it:
(function($){ "use strict"; var valid = "invalid"; function validateValue($value, $target, $placeholder,$email){ if ($email == true) { var n = $value.indexOf("@"); var r = $value.lastIndexOf("."); if (n < 1 || r < n + 2 || r + 2 >= $value.length) { valid = "invalid"; } else { valid = "valid"; } if ($value == null || $value == "" || valid == "invalid") { $target.addClass('visible'); } else { $target.removeClass('visible'); } } else { if ($value == null || $value == "" || $value == $placeholder) { $target.addClass('visible'); } else { $target.removeClass('visible'); } } }; $('.et-mailchimp-form').each(function(){ var $this = $(this); $this.submit(function(event) { // 1. Prevent form submit default event.preventDefault(); // 2. serialize form data var formData = $this.serialize(); var email = $this.find("input[name='email']"), fname = $this.find("input[name='fname']"), lname = $this.find("input[name='lname']"), list = $this.find("input[name='list']"); // 3. Before submit validate email validateValue(email.val(), email.next(".alert"), email.attr('data-placeholder'), true); if (email.val() != email.attr('data-placeholder') && valid == "valid"){ $this.find(".sending").addClass('visible'); // 4. POST AJAX $.ajax({ type: 'POST', url: $this.attr('action'), data: formData }) .done(function(response) { // 5. If success show the success message to user $this.find(".sending").removeClass('visible'); $this.find(".et-mailchimp-success").addClass('visible'); setTimeout(function(){ $this.find(".et-mailchimp-success").removeClass('visible'); },2000); }) .fail(function(data) { // 6. If fail show the error message to user $this.find(".sending").removeClass('visible'); $this.find(".et-mailchimp-error").addClass('visible'); setTimeout(function(){ $this.find(".et-mailchimp-error").removeClass('visible'); },2000); }) .always(function(){ // 7. Clear the form fields for next subscibe request setTimeout(function(){ $this.find("input[name='email']").val(email.attr('data-placeholder')); $this.find("input[name='fname']").val(fname.attr('data-placeholder')); $this.find("input[name='lname']").val(lname.attr('data-placeholder')); },2000); }); } }); }); })(jQuery);
This is an AJAX POST that sends our form data to the Mailchimp subscription action handler that we will write in a moment. I broke the
process into seven steps which you’ll see annotated in the snippet.
At the top of the file, up to line 29, you’ll find an email
validation function. Let me explain each of the other steps here:
- First we will need to prevent default submit behavior of the form so that it remains on the page and handles the request with AJAX.
- Next we serialize the input data both from the user and from our
hidden fields. - Next, before the submission, we need to make sure the email
is valid. - After that we make a simple AJAX POST. Here we need three parameters, the request type: POST, the url – the form action attribute, and the data that is our serialized data.
- And if the request is successful we send the data to our action
handler and show a success message to the user. - If there was an error, we should inform the user.
- And always after a success or fail we clean the form
fields for future entry.
Now if you go to the front-end and submit the form you
will get the success message, but the subscription won’t actually take place as the
subscribe action is not yet created.
Create the Subscribe Action
And so, for the last part. Go to the plugin’s main file and at the very bottom add the code:
function mailchimp_action(){ if ( ! isset( $_POST['et_mailchimp_nonce'] ) || !wp_verify_nonce( $_POST['et_mailchimp_nonce'], 'et_mailchimp_action' )) { exit; } else { $email = filter_var(trim($_POST["email"]), FILTER_SANITIZE_EMAIL); $fname = strip_tags(trim($_POST["fname"])); $lname = strip_tags(trim($_POST["lname"])); $list = strip_tags(trim($_POST["list"])); $api_key = MAILCHIMP_API; mailchimp_post($email, 'subscribed', $list, $api_key, array('FNAME' => $fname,'LNAME' => $lname) ); die; } } add_action('admin_post_nopriv_et_mailchimp', 'mailchimp_action'); add_action('admin_post_et_mailchimp', 'mailchimp_action');
Important!
Notice the last two lines, which are as follows:
add_action('admin_post_nopriv_et_mailchimp', 'mailchimp_action'); add_action('admin_post_et_mailchimp', 'mailchimp_action');
The last part of the first parameters of our two actions is
et_mailchimp
–this is the hidden field value that we have in our form. By using these, WordPress understands that it needs to handle the specific form request. So
if you used different naming for your hidden action field, make sure your actions are added correctly, like this:
add_action('admin_post_nopriv_{your_action_name}', 'your_action_function_name'); add_action('admin_post_{your_action_name}', ' your_action_function_name ');
Nonce
You’ll notice the first thing we did was to make sure the
nonce field is valid and verified; only after that we will subscribe the
user to the list. This is for security reasons., and you can read more about
nonce in the WordPress codex.
After the nonce verification we can subscribe the user
with a new function that we will build now: mailchimp_post()
. It requires several
parameters:
- User email
- Subscriber status
- Targeted list
- API key
- And user data, i.e. first name and last name.
Just before the mailchimp_action()
function add the following
code:
function mailchimp_post( $email, $status, $list_id, $api_key, $merge_fields = array('FNAME' => '','LNAME' => '') ){ $data = array( 'apikey' => $api_key, 'email_address' => $email, 'status' => $status, 'merge_fields' => $merge_fields ); $url = 'https://' . substr($api_key,strpos($api_key,'-')+1) . '.api.mailchimp.com/3.0/lists/' . $list_id . '/members/' . md5(strtolower($data['email_address'])); $headers = array( 'Content-Type: application/json', 'Authorization: Basic '.base64_encode( 'user:'.$api_key ) ); $$mailchimp = curl_init(); curl_setopt($$mailchimp, CURLOPT_URL, $url); curl_setopt($$mailchimp, CURLOPT_HTTPHEADER, $headers); curl_setopt($$mailchimp, CURLOPT_RETURNTRANSFER, true); curl_setopt($$mailchimp, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($$mailchimp, CURLOPT_TIMEOUT, 10); curl_setopt($$mailchimp, CURLOPT_POST, true); curl_setopt($$mailchimp, CURLOPT_POSTFIELDS, json_encode($data) ); curl_setopt($$mailchimp, CURLOPT_USERAGENT, 'PHP-MCAPI/2.0'); curl_setopt($$mailchimp, CURLOPT_SSL_VERIFYPEER, false); return curl_exec($$mailchimp); }
It’s very similar to the mailchimp_connect()
function we wrote earlier, except it has a different request type, different url.
This function takes our user data and using the Mailchimp REST
API it adds the user to the targeted list using the curl function.
Add Some Simple Styles
Now our widget is complete! We just need to add some subtle style changes. So
open the style file we created earlier and paste the following style rules:
.widget_mailchimp form { margin:0; position: relative; } .widget_mailchimp form input { margin-top: 12px; } .widget_mailchimp .alert:not(.visible) {display: none;} .widget_mailchimp .send-div { position: relative; } .widget_mailchimp .sending { position: absolute; bottom: 8px; right: 0; display: none; width: 32px; height: 32px; } .widget_mailchimp .sending.visible {display: block;} .widget_mailchimp .sending:after { content: " "; display: block; width: 32px; height: 32px; margin: 1px; border-radius: 50%; border: 2px solid #c0c0c0; border-color: #c0c0c0 transparent #c0c0c0 transparent; animation: lds-dual-ring 0.5s linear infinite; } @keyframes lds-dual-ring { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
Now, clear the browser cache and visit your site’s front-end to check everything is working perfectly. The subscription
should work on live sties and locally running sites!
Conclusion
I hope you enjoyed this tutorial on creating a Mailchimp subscribe form widget for WordPress, using the Mailchimp API. It was quite a substantial one! Here is the link to the GitHub repo;
you can download the plugin, modify it, and use it in any project you like!