Building Snipcart E-commerce WordPress Plugin - Part 2

NB: We recently abandoned support for the WordPress plugin, read this blog post to learn why.

In part one, I explained the basics of authoring a Wordpress plugin and adding a post type. In this this post, I'll explain how we added the extra boxes in the product edition page in the admin. Remember that the plugin is open source on GitHub: Also, if you want to use this e-commerce Wordpress plugin, you can download it here:

Meta Boxes

In Wordpress parlance, a meta box is a new box in the admin where you can add more data to a post. In the case of Snipcart, we wanted to have all the necessary data in new fields that would show up in the admin when editing a product.

Product details Wordpress screenshot

Adding a meta box is not very hard. You add a call to a hook:

<!-- lang: php -->

add_action('add_meta_boxes', 'snipcart_add_product_meta_box');

Then you call add_meta_box (reference):

<!-- lang: php -->

function snipcart_add_product_meta_box() {
        __('Product Details', 'snipcart-plugin'),

In this case, snipcart_display_product_metabox is the function that will be called to display the meta box. This function must output the form. I'll show you an extract to understand how it works, but feel free to have a look at the complete and up-to-date version on GitHub:

<!-- lang: php -->

function snipcart_display_product_metabox($post) {
    $product_id = get_post_meta($post->ID, 'snipcart_product_id', true);
    /* other attributes here */
    <div class="snipcart-field">
            <label for="snipcart-product-id">
                <?php _e('Product ID', 'snipcart-plugin'); ?>
            <span class="snipcart-required">*</span>
            <input type="text"
                value="<?php echo $product_id; ?>"
    <!-- other attributes here -->

At this point, the new meta box is shown when editing a snipcart_product, which is nice. But if you submit it, it does not save the data. To save the data, you have to use another hook:

<!-- lang: php -->

add_action('save_post', 'snipcart_save_product', 10, 2);

With that in place, snipcart_save_product will be called every time a post is saved. Again, an extract to better understand:

<!-- lang: php -->

function snipcart_save_product($product_id, $product) {
    if ($product->post_type != 'snipcart_product') return;
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if ($_SERVER['REQUEST_METHOD'] != 'POST') return;

    update_post_meta($product_id, 'snipcart_product_id',
    /* other attributes here */

We have to check first if the user is saving a snipcart_product because, remember, our function is called every time a post is saved, whatever its type.

Up to that point, that is the normal way of adding a meta box. In most cases, it would end there.

Meta Boxes Validation

This is the part where it gets tricky. Wordpress does not offer anything to validate user input in meta boxes. Most of the examples you can find on the web show you how to do some JavaScript-only validation. In our cases, we had to go further than that, because we had some validation logic that had to be executed on the server.

Wordpress does not allow to validate a meta box when submitting. For example, if you simply choose not to save data that the user sends, your data will be the only data that does not get saved. The rest of the form will save (like the post content). Which can lead to weird results. To get more confusing, most of the examples on the web simply save NULL when the data is invalid. That was inacceptable. The form had to be saved entirely or not at all. And when there was errors, error messages had to be shown to the user, so he knows that he must fix them in order to save.

We had to hack something. This is not as clean as I hoped it could be, but it's currently the cleanest way I could find, and I am pretty sure there is no cleaner way. Also, I did not find a single place on the web where a trick like that was used.

Basically, what we do is send an AJAX request to the server just before submitting the form. The server validates the fields and return the result. If everything is valid, the form can then submit normally. If not valid, error messages are displayed. With this solution, there is the possibility of forging a request that would allow to save invalid data to the database. But in order to save data to the database, you must be logged with a user account that allows to edit content anyway, so I can assume that this is not a major issue since that user could do all other stuff without hacking anything.

<!-- lang: php -->


This hooks two function. One to display the javascript that will be used to validate the meta box. The other is to add a path that is callable by AJAX in the admin. The full version of what follows can be found here:

<!-- lang: php -->

function snipcart_meta_box_validation_script() {
    global $post;
    if (!is_admin()) return;
    if ($post != NULL && $post->post_type != 'snipcart_product') return;
    $nonce = wp_create_nonce('snipcart_validation');
    <script language="javascript" type="text/javascript">
    var snipcartStrings = {};
    snipcartStrings['containsErrors'] =
        '<?php _e('The form contains errors. The product was not updated.'); ?>';
    jQuery(function($) {
        function showErrors(errors) {
            // removed for briefness
        /* other stuff removed for briefness */
        var formIsValid = false;
        $('#post').submit(function(ev) {
            if (!formIsValid) ev.preventDefault();
            var formData = JSON.stringify($('#post').serializeArray());
            var data = {
                action: 'snipcart_meta_box_validation',
                security: '<?php echo $nonce; ?>',
                form: formData
            $.post(ajaxurl, data, function(errors) {
                    .prop('disabled', false)
                if ($.isEmptyObject(errors)) {
                    formIsValid = true;
                } else {


Let's review this a bit. If we are in the admin, editing a snipcart_product, add some JavaScript. When the form is submitted, instead of submitting, do a AJAX query to ajaxurl to make sure the form is valid. If it is valid, submit, or else, show errors. ajaxurl is a variable defined by Wordpress. If you view source of the form to edit a post, you can see somewhere: var ajaxurl = '/wp-admin/admin-ajax.php'. Also, you can see in the data that is submitted, there is the attribute snipcart_meta_box_validation. Remember? That is what we hooked just a few paragraphs before.

<!-- lang: php -->

function snipcart_meta_box_validation() {
    check_ajax_referer('snipcart_validation', 'security');
    $form_json = $_POST['form'];
    $form_json = str_replace('\\"', '"', $form_json);
    $form_json = str_replace("\\'", "'", $form_json);
    $form = json_decode($form_json, true);
    $post_id = snipcart_get_form_value($form, 'post_ID');
    header('Content-Type: application/json');
    $errors = array();

    $product_id = snipcart_get_form_value($form, 'snipcart-product-id');
    if ($product_id == NULL || trim($product_id) == '') {
        snipcart_add_error($errors, 'snipcart-product-id',
            __('This field is required', 'snipcart-plugin'));
    } else {
        $product_id = trim($product_id);
        $other_product_id =
            snipcart_other_product_that_uses_id($post_id, $product_id);
        if ($other_product_id != null) {
            $link = get_edit_post_link($other_product_id);
            snipcart_add_error($errors, 'snipcart-product-id', sprintf(
                __('Must be unique. <a href="%s">This product</a> already uses this ID.',

    /* other validation removed for briefness */

    if (count($errors) == 0) echo '{}';
    else echo json_encode($errors);
    die(); // or else will append '0' to response body

function snipcart_add_error(&$errors, $key, $error) {
    if (!array_key_exists($key, $errors))
        $errors[$key] = array();
    $errors[$key][] = $error;

function snipcart_get_form_value($form, $key) {
    $values = snipcart_get_form_values($form, $key);
    if (count($values) == 0) return NULL;
    return $values[0];

function snipcart_get_form_values($form, $key) {
    $values = array();
    foreach ($form as $elem) {
        if ($elem['name'] == $key) $values[] = $elem['value'];
    return $values;

function snipcart_other_product_that_uses_id($post_id, $product_id) {
    global $wpdb;
    $sql =
        "SELECT post_id FROM {$wpdb->prefix}postmeta
            WHERE post_id != %d
                AND meta_key = 'snipcart_product_id'
                AND meta_value = '%s'";
    $existing_post_id =
        $wpdb->get_var($wpdb->prepare($sql, $post_id, $product_id));
    return $existing_post_id;

This, again, has been stripped to keep only the essential. This function receives the data, validate them and returns errors if any, in JSON. It looks normal, there is only some notable stuff. There is the call to check_ajax_referer, to check the nonce that was created when showing the form. Also, at the end of the function, we had to call die, or Wordpress would output a 0.

And that's about it for this post.

Suggested posts: