Craft CMS E-Commerce: Why and How I Built a Snipcart Plugin

In a rush? Skip to how I built the plugin

Let’s be honest. If you’re in the market for a CMS for e-commerce, you’ve got plenty of options.

Back in the days when iPads were still iPods, you’d be choosing between Drupal or WordPress—and even those were nothing compared to what they are now.

Personally, I've tried lots of options but came (like a lot of people) from the ExpressionEngine community. It was there that Brandon Kelly (of Pixel and Tonic) became known for making solid and extremely useful add-ons. He transitioned from his day job into doing that full time, eventually starting a CMS called Blocks which is now Craft.

Recently, I developed a Snipcart plugin for Craft and realized it would be useful to other developers as well. After a lot of work and refinement, I finally created a product I’m happy to see in the Craft plugin store.

In this article, I’ll talk about:

Let’s dive in!

A bit about Craft CMS

craft-cms-logo

Craft CMS was created by Pixel and Tonic, a small company that built content management tools for web professionals. In 2011, they came up with the idea for and began working on Craft. They released Craft 1.0 two years later. Since then, they’ve released updated versions and have carved a name for themselves in the CMS world by winning awards for “Best CMS for Developers” and “Best WordPress Alternative.”

Craft’s focus is content management and their team knows it. They’re constantly evolving their tools and techniques to stay competitive. Particularly with Craft 3, it’s become a content management platform and embraces headlessness. Its JSON Element API has been around since 2015 and Mark Huot’s CraftQL plugin is making everything accessible in a GraphQL API.

As a developer with a design background, I personally became interested in headless CMSs because I build sites for non-developers. That means I need to have more flexibility in safeguarding an excellent content management experience while still providing the mechanics of presenting the actual content. I can use a project like GatsbyJS to pull in data from seemingly anything and generate a crazy-performant, best-practice-adhering modern frontend. I wasn’t born working in React, so the fact that these things are accessible to me is fairly exciting.

Why I prefer Craft over other CMSs

What makes Craft CMS stand out to me is balance. It's great to work with as a frontend developer, a PHP developer, a designer, and a content author. A lot of platforms cater to one of those groups, but Craft manages to offer a thoughtful, rich experience for all of them. So most of my clients seeing the Craft control panel for the first time get excited at how it could be tailored for them.

Plus, it's a commercial software—not a hobbyist platform—and has a welcoming professional community around it doing great work and helping each other out. That community goes a long way on those days where things just don’t seem to be working right which, as we all know, happens from time to time.

Fitting Snipcart into Craft CMS e-commerce

craft-snipcart-plugin

Now if you’re familiar with Craft, then you’re probably aware that there are already two e-commerce solutions for sites using Craft CMS: Commerce Pro and Commerce Lite, both of which fall under the “Craft Commerce” umbrella.

With two great options currently on the market, you may be wondering why I bothered to make another e-commerce plugin in the first place and, also, why I chose to use Snipcart? Both are fair questions. To understand the answers, let’s take a look at Commerce Pro and Commerce Lite to get a better view of the gap I was trying to bridge.

Craft Commerce

craftcommerce

Commerce Pro and Commerce Lite are both first-party options built by Pixel and Tonic. The Commerce Pro plugin is mature and always evolving. It’s really great for building out a more comprehensive store. In other words, it’s complex which can be both good and bad.

Like Craft CMS, Commerce Pro is minimally opinionated and lets you build whatever you want. This is essential if you’re building a larger, customized store. The downside is that the developmental burden can hinder some developers from using Commerce Pro for smaller e-commerce projects. In the wrong context, its strength (more freedom via complexity) quickly becomes its weakness.

On the other hand, Commerce Lite is a more recent, feature-limited variant that’s less expensive. As you may expect, Commerce Lite has the exact opposite problem as Commerce Pro. It addresses the needs of smaller projects but does away with many features that can still be important for developers.

Like everything in life, you simply get what you pay for.

So I recognized a need for something else in the world of Craft Commerce. There were already two great options available but both had fallen into the classic “Goldilocks” dilemma: one bowl was too hot, the other was too cold.

I wanted a tool that was just right. That’s where Snipcart entered the picture.

Building Snipcart as a Craft plugin

building-snipcart-plugin

I began creating my Snipcart Craft plugin in 2014 but, at the time, I hadn’t made it public. It wasn’t until last year when I was an adding a ShipStation integration for the Craft 3 version when I realized that I was building something that would probably be useful to other Craft CMS developers.

When I started, Snipcart was already an existing service that fell somewhere in between Commerce Pro and Commerce Lite—though perhaps a bit closer to the former. This is what attracted me to it in the first place—not to mention it’s quick to implement but still full-featured and flexible. Plus, Craft and Snipcart had already developed a great relationship as the creator of Snipcart, Charles Ouellette, was active in our community and made a Google Hangouts recording on making a Craft CMS E-Commerce integration with Snipcart.

And the relationship worked both ways.

I was actually the first full-featured testimonial on Snipcart’s site! But really, at the end of the day it came down to improving UX for Craft users. Turning Snipcart into a plugin helps its integration into Craft CMS without taking any speed or flexibility away from the Craft site. In other words, it meshes really well in the Craft world.

Advantages of Snipcart as a Craft plugin

Like I said, Snipcart is already fast to start working with, but the plugin also offers some Craft-native pieces like its Field Type and Twig tags. Because of these “Craft-friendly” features, Snipcart is quicker to get up and running than other e-commerce solutions—even for someone who’s never used Snipcart.

But the majority of Snipcart’s benefits are centered around webhooks. They support custom order email notifications, automatic quantity adjustment, and the ability to fetch live shipping rates or send orders to ShipStation. On top of that, every one of Snipcart’s webhooks can easily be hooked into as Craft-friendly Events.

This is also particularly exciting because with Pixel & Tonic’s first-party Webhooks plugin you can turn around and fire your own webhooks to other services to build almost anything without writing any code. The Craft site becomes the glue for whatever e-commerce scheme you can think of.

Plus, Snipcart isn’t all that opinionated, so regardless of your configurations, you’ll be able to browse store stats, orders, etc. right from the Craft control panel.

Let’s take a quick look at how easy it is to install the plugin and configure something simple like “Orders.”

Setting up and Configuring your Snipcart plugin

The first thing you’ll want to do is create a Snipcart account (I know it seems obvious, but you’d be surprised…). That will get you the Snipcart Public API Key and Snipcart Secret API Key which you'll need later.

Then you install the plugin which is straightforward.

From the plugin store, find Snipcart and choose Install. Done.

Or you can add with composer from your local project :

composer require workingconcept/craft-snipcart  

You can then install from the Craft CMS control panel: Settings → Plugins, choose the gear dropdown to the right of Snipcart, and select Install.

Or install from the command line:

./craft install/plugin snipcart

Once you’ve got your account, you’ll need to add the Snipcart Public API Key and Snipcart Secret API Key.

setting-sup-snipcart-account

From here, you’re pretty much done!

Now let’s say you want to configure a specific aspect of your plugin like “Orders.” Several features can be managed from plugin settings in Craft's control panel. If you want to use the included Field Type and Twig tags, you can configure various aspects of your orders without writing any extra code. You can also use your own markup, field layouts, and integrations if you'd like!

configuring-order-snipcart-plugin

For configuring the rest of the plugin's settings, see the detailed documentation. This covers everything from when/why/how to use Snipcart’s webhooks with your Craft site to troubleshoot any problems you may run into along the way.

Hopefully you’re starting to see why I chose Snipcart to build the plugin. It’s the perfect solution for Craft designers/developers who want to build a full-featured store with minimal development time.

Now, since we’ve covered the “why,” let’s turn our attention to the “how.”

Building the actual plugin

craft-cms-snipcart

The Snipcart plugin is my first commercial (paid-license) plugin on Craft’s plugin store. Initially, it started as a much smaller and simpler free plugin for Craft’s previous version but I’ve since made some major enhancements. Overall, I wanted this plugin to:

  • Ease store setup

  • Improve developer-friendliness under the hood

  • Take advantage of Snipcart’s webhooks and REST API

As all developers know, there are always improvements to be made with each iteration but I’m definitely proud of the product as it stands. Especially considering how far it’s come.

Let’s take a look!

How I built Snipcart’s plugin for Craft CMS e-commerce

I initially built a Snipcart plugin for Craft 2—the previous version—that was a thin control panel view for Snipcart's REST API. After rounding out some things that were missing, I had two main goals:

  1. Combine some of Craft and Snipcart's strengths to optimize the path to running store.

  2. Surround REST API interaction with native Craft/Yii models, services, and events that make it faster to build custom integrations.

Solving the first problem involved offering a custom Field Type to store product information, and some features that can piggyback off that with templating conveniences, custom order notifications, and ShipStation integration—all without writing a line of PHP.

I'm fond of this Twig template that's used to render a buy button, for example.

{# https://docs.snipcart.com/configuration/product-definition #}
{% set classes = templateParams.classes ?? [] %}
<a href="{{ templateParams.href }}"
    class="snipcart-add-item{% if classes | length %} {{ classes | join(' ') }}{% endif %}"
    {% if templateParams.target %}target="{{ templateParams.target }}"{% endif %}
    {% if templateParams.title %}title="{{ templateParams.title|t }}"{% endif %}
    {% if templateParams.rel %}rel="{{ templateParams.rel }}"{% endif %}
    data-item-id="{{ fieldData.sku }}"
    data-item-name="{{ fieldData.element.title }}"
    data-item-price="{{ fieldData.price }}"
    data-item-url="{{ fieldData.element.url }}"
    data-item-quantity="{{ templateParams.quantity }}"
    data-item-taxable="{{ fieldData.taxable ? 'true' : 'false' }}"
    data-item-shippable="{{ fieldData.shippable ? 'true' : 'false' }}"
    {%- if fieldData.shippable %}
    data-item-width="{{ fieldData.getDimensionInCentimeters('width') | round(0, 'ceil') }}"
    data-item-length="{{ fieldData.getDimensionInCentimeters('length') | round(0, 'ceil') }}"
    data-item-height="{{ fieldData.getDimensionInCentimeters('height') | round(0, 'ceil') }}"
    data-item-weight="{{ fieldData.getWeightInGrams() }}"
    {% endif -%}
    {%- if templateParams.additionalProps is defined and templateParams.additionalProps | length %}
    {% for property, value in templateParams.additionalProps %}
    {{ property }}="{{ value }}"
    {%- endfor %}
    {%- endif %}
    {%- for customOption in templateParams.customOptions -%}
    data-item-custom{{ loop.index }}-name="{{ customOption.name }}"
    data-item-custom{{ loop.index }}-required="{{ customOption.required ? 'true' : 'false' }}"
    data-item-custom{{ loop.index }}-options="{% for option in customOption.options -%}
        {{ option.name }}[+{{ option.price }}]
        {%- if loop.last != true %}|{% endif %}{% endfor %}"
    {%- endfor -%}
    >
        {{- templateParams.text|t -}}
</a>

The combination of that Field Type and its template interface will speed up the already-fast process of creating buy buttons, with added perks like automatically converting units for your superior un-American metric system.

Writing documentation was a necessity. Without a guide, it might still be easy to miss what should be a very quick setup.

Pixel & Tonic was kind enough to publish its customized VuePress theme, which I happily used for the documentation. If not for that I'd probably still tinkering with my own setup without having written any real content. It's fast, searchable, and hosted for free on Netlify.

Being completely new to Netlify and VuePress, it took me probably less than five minutes to point Netlify to my GitHub repo, tell it which branch to build, and have it run npm run docs: build to deploy. I don't know why everyone's letting me use these things for free, but I'm grateful they are!

On to goal number two.

If you do want to write your own PHP, the plugin exposes Events you can hook into, detailed models of Snipcart and ShipStation objects, and what I hope are sensible services for wiring up whatever you'd like. The events and services are all documented, but the models are so hilariously long that I need to find a good way to generate their documentation so I can finish this year.

Modeling Snipcart and ShipStation's API objects took awhile and involved much refinement and testing for my webhook handler and Snipcart→ShipStation connection. I ended up using Codeception to run a battery of tests against the webhook handler. My goal is to keep growing the API tests to make sure that everything's as stable as possible.

A series of interesting failures helped me further scrutinize incoming webhook data, for example:

private function _hasInvalidRequestData($payload)
{
    /**
     * Reject requests that can't be validated.
     */
    if (self::$validateWebhook && ! $this->_requestIsValid())
    {
        return 'Could not validate webhook request. Are you Snipcart?';
    }
    
    /**
     * Every Snipcart post should have an eventName, so we've got
     * missing data or a bad format.
     */
    if ($payload === null || !isset($payload->eventName))
    {
        return 'NULL request body or missing eventName.';
    }
    
    /**
     * Every Snipcart post should have a content property.
     */
    if (!isset($payload->content))
    {
        return 'Request missing content.';
    }
    
    /**
     * Every Snipcart post should either be in live or test mode.
     */
    if (!isset($payload->mode))
    {
        return 'Request missing mode.';
    }
    
    /**
     * Every Snipcart post should clarify whether it's in live or test mode.
     */
    if (! in_array(
        $payload->mode,
        [Webhooks::WEBHOOK_MODE_LIVE, Webhooks::WEBHOOK_MODE_TEST],
        false
    ))
    {
        return 'Invalid mode.';
    }
    
    /**
     * We've received an invalid `eventName`.
     */
    if (! array_key_exists(
        $payload->eventName,
        self::WEBHOOK_EVENT_MAP
    ))
    {
        return 'Invalid event.';
    }
    
    // We *don't* have invalid request data!
    return false;
}

And speaking of scrutinizing, I started using Scrutinizer on this project, and combining that feedback with PhpStorm greatly improved my pace and code quality. Following Scrutinizer's advice often led me to redesign aspects of the code that got much better as a result.

Some of my learning came from practical experience, too. There's a console tool you can call with ./craft snipcart/verify/check-orders that will make sure your most recent orders made it to ShipStation if you're set up to use it:

-------------------------------------
Checking last 3 orders...
-------------------------------------
Snipcart SNIP-5556 … ShipStation [✓]
Snipcart SNIP-5557 … ShipStation [✓]
Snipcart SNIP-5558 … ShipStation [✓]
-------------------------------------
Finished in 3.0893619060516 seconds.

Just to be clear, I definitely didn’t create this because everything went perfectly the first time around (does it ever?). But I'm confident that my code will, at some point, save somebody time ;)

The documentation and tiny universe surrounding the plugin took a good amount of time as well. We re-launched our website to make room for plugins, established a support system, and hopefully released a well-rounded offering that will give people a great way to build stores with Craft. We wanted to be sure that our documentation would adequately support clients when things aren't quite making sense or aren't working as they should.

We have more features and documentation to add, but another thing I've learned from the Craft community, and Pixel & Tonic specifically, is that it's important to listen carefully and keep supporting your customers. So I plan on doing that, too.

Conclusion

Creating Snipcart’s Craft CMS plugin was definitely time consuming, but also a whole lot of fun. Because Snipcart lies somewhere between the fully featured Commerce Pro and the inexpensive Commerce Lite, I find that it’s a really useful tool for anyone looking to create a Craft CMS e-commerce site. At any rate, I’m definitely pleased with the result of my first commercial product on the market—and I certainly hope you are as well!

For those interested in building their site with Craft, I highly recommend starting here. And once you’re ready to turn your site into an e-commerce store, the Snipcart plugin makes the process quick, easy, and incredibly user friendly!


If you've enjoyed this post, please take a second to share it on Twitter. Got comments, questions? Hit the section below!

Suggested posts: