Building Snipcart E-commerce Wordpress Plugin - Part 1

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

Snipcart vows to simplify e-commerce for everyone. We get a lot of interest from the hardcore geek community, but we also receive a lot a email from people not comfortabe editing html files, asking for help. With that in mind, we decided to make a Wordpress plugin that would allow to use Snipcart without ever opening an html file in a text editor.

The goal of the plugin was to add a Product type and an Add to Cart button, without the user have to edit a file.

This is a serie of post about the making of this plugin.

Before begining this plugin, it has been about 3 years since I wrote any PHP. I still was familiar with the language, but I had to search a lot find how things are named in Wordpress. This serie of post is intended to share what I've learned about authoring a Wordpress plugin.

You can see the latest source of the plugin at https://github.com/snipcart/snipcart-wordpress-plugin.

Basic file structure

Since I knew I was to have more than one file, I created a directory in wp-content/plugins that I named snipcart. Into it, I added a file named snipcart.php.

In my git repository, I also wanted to track files that are not directly part of the plugin itself, so I moved the snipcart folder into my git repository and created a symlink to it in my wp-content/plugins directory. Now, I have my git repository at ~/src/snipcart-wordpress-plugin, and my plugin is located at ~/src/snipcart-wordpress-plugin/snipcart. For those not familiar with simlink, this is how I created it (while I was in the plugin directory):

ln -s ~/src/snipcart-wordpress-plugin/snipcart

That way, I can have a normal repository structure, with the README.md file outside the source, and the possibility to add various util files that I don't want in the plugin itself.

If I am not clear enough, have a look at the repository.

Wordpress plugin header

In your main Wordpress plugin file, you have to add the header for metadata and the license. Ours looks something like this:

/*
Plugin Name: Snipcart
Plugin URI: https://snipcart.com
Description: Easily sell with Snipcart. Adds "Product" post type and settings.
Author: Snipcart Team
Version: 0.1
Author URI: https://snipcart.com
*/
/*
The MIT License

Copyright (c) 2013 Snipcart, Inc. (https://snipcart.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

The metadata are what the user sees in the page to activate your plugin.

Also, be careful when you choose a license, it must be compatible with the license of Wordpress itself: GPL v2 or later. I decided to use MIT as I do not think that having a more restrictive license goes in the interest of Snipcart and it's users.

Add Custom Type

First thing I wanted to do is I wanted to add a new post type. In Wordpress, there are posts and pages. I also wanted products.

In Wordpress, this is called a Custom Type.

The way to do this, is that you can use a hook to call one of your functions at a precise moment in Wordpress workflow. In snipcart.php, the main file in the plugin, I added:

add_action('init', 'snipcart_register_product_type');

This means that on init, Wordpress will call a function named snipcart_register_product_type. So you have to declare it.

function snipcart_register_product_type() {
    // do stuff
}

If you put this function in snipcart.php, it will be called at every page load, at init. But I did not want to have single file of 3000 lines of code. So I added a file named register_type.php. In snipcart.php, I added the following line:

include('register_type.php');

Now everything I put in register_type.php will be included in snipcart.php. Here's what register_type.php looks like:

function snipcart_register_product_type() {
    $labels = array(
        'name' => __('Products', 'snipcart-plugin'),
        'singular_name' => __('Product', 'snipcart-plugin'),
        'add_new' => __('Add New', 'snipcart-plugin'),
        'add_new_item' => __('Add New Product', 'snipcart-plugin'),
        'edit_item' => __('Edit Product', 'snipcart-plugin'),
        'new_item' => __('New Product', 'snipcart-plugin'),
        'all_items' => __('All Products', 'snipcart-plugin'),
        'view_item' => __('View Product', 'snipcart-plugin'),
        'search_items' => __('Search Products', 'snipcart-plugin'),
        'not_found' =>  __('No products found', 'snipcart-plugin'),
        'not_found_in_trash' => __('No products found in Trash', 'snipcart-plugin'),
        'parent_item_colon' => '',
        'menu_name' => __('Products', 'snipcart-plugin')
    );
    $args = array(
        'labels' => $labels,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'query_var' => true,
        'rewrite' => array('slug' => 'products'),
        'capability_type' => 'post',
        'has_archive' => true,
        'hierarchical' => false,
        'menu_position' => 20,
        'supports' => array(
            'title',
            'editor',
            'author',
            'thumbnail',
            'excerpt',
            'comments'
        )
    );
    register_post_type('snipcart_product', $args);
}

You see the call to register_post_type at the end. It's that function that adds the new type. When naming things in a plugin, you should always prefix everythig to avoid name collision. Kind of namespacing for the poor. In my case, I named the new type snipcart_product.

Most of the parameters you see are for the strings to be used when talking about this new type. You see a lot of call to __(). This is used for internationalization (i18n). I'll probably write a post about that later. For the moment, just keep in mind that those are strings that will be used by Wordpress when showing our new type.

At that point, if you go in the admin and activate the plugin, you should see in the left column, near Posts and Pages, a new link named Products. You can now add and edit products. First step completed. Almost. When you click Publish, Wordpress shows a message that looks like Post saved. We provided every label necessary, how come Wordpress is not using them?

Turns out Wordpress also has a $messages array that contains other messages, that are displayed on form submit. I don't think it was directly in the docs for the custom types. I searched a bit and here's what I added to register_type.php:

function snipcart_post_updated_messages($messages) {
    global $post, $post_ID;
    $messages['snipcart_product'] = array(
        0 => '',
        1 => sprintf(__('Product updated. <a href="%s">View product</a>',
            'snipcart-plugin'),
            esc_url(get_permalink($post_ID))),
        2 => __('Custom field updated.', 'snipcart-plugin'),
        3 => __('Custom field deleted.', 'snipcart-plugin'),
        4 => __('Product updated.', 'snipcart-plugin'),
        5 => isset($_GET['revision'])
            ? sprintf(__('Product restored to revision from %s',
                'snipcart-plugin'),
            wp_post_revision_title((int) $_GET['revision'], false )) : false,
        6 => sprintf(__('Product published. <a href="%s">View product</a>',
                'snipcart-plugin'),
            esc_url(get_permalink($post_ID))),
        7 => __('Product saved.', 'snipcart-plugin'),
        8 => sprintf(__('Product submitted. <a target="_blank" href="%s">Preview product</a>',
                'snipcart-plugin'),
            esc_url(add_query_arg('preview', 'true',
                get_permalink($post_ID)))),
        9 => sprintf(__('Product scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview product</a>',
                'snipcart-plugin'),
            date_i18n(__('M j, Y @ G:i'), strtotime($post->post_date)),
            esc_url(get_permalink($post_ID))),
        10 => sprintf(
            __('Product draft updated. <a target="_blank" href="%s">Preview product</a>', 'snipcart-plugin'),
            esc_url(add_query_arg('preview', 'true',
            get_permalink($post_ID)))),
    );
    return $messages;
}

Also, it had to hook it into Wordpress by adding to snipcart.php the following line:

add_filter('post_updated_messages', 'snipcart_post_updated_messages');

Now if you save a product, you should see product instead of post.

The other parts

That's all for this post. In later posts, I'll talk about meta boxes and how to validate them, how I affected a product (post) markup to add the Add to Cart button and the Snipcart <script> tag, the setting page, internationalization and maybe other stuff.

In the mean time, if you didn't have a look yet at Snipcart, we encourage you to try it!

Suggested posts: