Skip to main content

Introduction

What is Cart.js?

Cart.js is a very small open source Javascript library that makes the addition of powerful Ajax cart functionality to your Shopify theme a breeze.

It's designed to be simple to use, while providing some really powerful and nifty features, like:

  • Simple, consistent API for cart manipulation;
  • Data API for markup-only use without needing to write a line of Javascript;
  • DOM Binding to dynamically render HTML templates as your cart changes.

You don't need to worry about ensuring your Ajax requests are synchronous, binding event listeners, or updating the DOM.

Getting Started

Getting started with Cart.js is designed to be pretty easy.

You just need to fetch a copy of the library, include it in your theme, and call a single initialisation method. Then you can make calls to Cart.js from within your theme code.

Installation

You can install Cart.js via npm, bower or simply by downloading the latest copy of the library.

NPM

npm install shopify-cartjs

Bower

bower install shopify-cartjs

Download

Download the latest stable release of Cart.js from here.

Setup

The Cart.js distribution comes packaged with fours versions of the library:

cart.js is the unminified source code, containing the Core and Data APIs.

cart.min.js is a minified version of the library, and also contains the Core and Data APIs.

rivets-cart.min.js is a minified version of Cart.js that also bundles the Rivets.js library. Together, they provide support for the DOM Binding functionality.


Once you've selected the version you'd like to use, add the relevant file to your theme's /assets directory.

You then just need to include the script on your page and call CartJS.init(). The best place to do this is at the bottom of your theme's theme.liquid file, so that Cart.js functionality is available across your whole site.

Because Cart.js depends on jQuery, you should load it after you've included the jQuery library.

        ... contents of your theme.liquid ...

        <!-- Include jQuery from Google's CDN. -->
        <!-- Your theme may already include jQuery - if so, you can skip this line. -->
        {{ '//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js' | script_tag }}

        <!-- Include Cart.js -->
        {{ 'cart.min.js' | asset_url | script_tag }}

        <!-- Initialise Cart.js once the page has loaded -->
        <script type="text/javascript">
            jQuery(function() {
                CartJS.init({{ cart | json }});
            });
        </script>

    </body>
</html>

Note that the call to CartJS.init() requires that {{ cart | json }} is passed as an argument. This tells Liquid to render the initial cart state as a JSON object and pass it to Cart.js.

Dependency when formatting monetary values

If you're using any of the money-formatting features of Cart.js (such as the | money filter in the DOM Binding module or the data-cart-render attributes of the Data API), you'll need to make sure that Shopify's currency Javascript library is loaded.

Do this simply by ensuring that the option_selection.js library is loaded on all of your theme's pages, using a line something like {{ 'option_selection.js' | shopify_asset_url | script_tag }} in your theme.liquid.

Next Steps

Now that you gotten Cart.js set up with your theme, you're ready to start using the library.

The recommended (and easiest) way to interact with Cart.js is via the Data API. All this requires is adding some additional markup to your HTML and Cart.js will take care of the rest - you don't need to write any additional Javascript.

If you want to do something that's not supported by the Data API, or use Cart.js from within your own custom Javascript, then you can use the Core API to call methods on the CartJS object directly. Of course, these two approaches aren't mutually exclusive - you can always use the Data API for the majority of your cart functionality, drop down to the Core API only when needed.

The Core API is covered first in the documentation below, as it provides the foundation for the Data API.

However, if you just want to get stuck in to using Cart.js, feel free to go straight to the coverage of the Data API.

Once you've gotten the hang of the Core and Data APIs, you might be interested in using Cart.js in conjunction with Rivets.js to create HTML templates that are automatically updated along with your cart. That's covered in the DOM Binding section later on.

Browser Support

If you're using only the Core and Data functionality of Cart.js (that is, you're using the cart.js or cart.min.js library in your theme), then the range of browsers your theme will support is limited purely by the version of jQuery you're using. That means that using a 1.x version of jQuery will allow you to support IE6+, Chrome, Firefox, Safari 5.1+ and up.

If you're using the DOM Binding functionality (that is, you're using the rivets-cart.min.js library in your theme), then you may see problems on browsers that don't support the ES5 Javascript standard (namely, Internet Explorer 8 and below).

As of November 2015, Shopify no longer requires themes submitted to the theme store to support Internet Explorer 8, so as long as you don't have a special use case that requires supporting these older browsers, you can happily use the DOM Binding library in your themes.

What's the issue with older browsers?

Non-ES5 browsers (namely, IE8) don't support EcmaScript 5's Object.defineProperty() method, which Rivets.js uses to observe changes on data models and trigger DOM updates.

Earlier versions of Cart.js provided a workaround for this by bundling a number of ES5 shims and polyfills into the library, as well as forcibly binding and re-binding Rivets views whenever a charge to the cart occurred. This "compatibility mode" was dropped once Shopify changed their theme guidelines to only require support for Internet Explorer 9+.


Core API

The Core API consists of methods called on the global CartJS object. You can use the Core API to do pretty much everything you'd expect of a cart manipulation library - add items, update quantities and custom properties, and so on.

A full list of methods can be found in the API Reference.

Configuration

In the "Setup" section above, we saw that we need to call CartJS.init() before use, like this:

<script type="text/javascript">
    jQuery(function() {
        CartJS.init({{ cart | json }}, {
            "dataAPI": false,
            "requestBodyClass": "loading"
        });
    });
</script>

As you can see, the init() method takes two arguments. The first argument is required, and is provided by rendering the current Shopify cart as a JSON object through the {{ cart | json }} Liquid tag.

The second argument is an optional hash of configuration options. A full list of these options is available in the Option Reference.

Cart State

If we want to inspect the state of our cart at any time, we can access the CartJS.cart object. You can read any of the standard cart properties (such as item_count or requires_shipping, for example), as well as a list of the current items in the cart.

While developing with Cart.js, it's often useful to open your browser's Javascript console and inspect the cart state, or test out Cart.js methods. Here's an example from the developer console in Chrome:

Screenshot of the Chrome developer console.

Don't write values via the cart object

You should only ever read values from CartJS.cart, and avoid altering the object directly.

Assigning a value with code like CartJS.cart.items[0].quantity = 5; will make the change locally in the browser, but won't save it to the server. This means that the changes will be lost when the customer refreshes or navigates to a new page, or when Cart.js fetches an updated version of the cart from Shopify.

Adding Items

Adding items to your cart is as simple as calling CartJS.addItem(), and passing the ID of the variant you'd like to add as the first argument.

Assume we have a Shopify store that's selling widgets, and that one of those widgets has a variant with an ID of 12345678 that costs $9.99. Let's create a button customers can click to add a widget to their cart, and then hook it up to some Javascript code via jQuery:

<button id="button">Add a Widget</button>

<script type="text/javascript">
    $('#button').click(function() {
        CartJS.addItem(12345678);
    });
</script>

Now when a customer clicks our button, Cart.js will make an Ajax request and add a single Widget to the customer's cart.

When using the addItem() method, you can optionally specify the quantity to add and a hash of custom line item properties. Let's update our code to add five widgets when we click the button, and to set a custom "added_by" property on the resulting line item:

<button id="button">Add Five Widgets</button>

<script type="text/javascript">
    $('#button').click(function() {
        CartJS.addItem(12345678, 5, {
            "added_by": "Cart.js"
        });
    });
</script>

If we loaded this example, clicked "Add Five Widgets", then typed CartJS.cart.items in to our browser's Javascript console, we'd see something like this (simplified for this example):

[
    {
        "handle": "widget-1",
        "id" 12345678,
        "price": 999,
        "line_price": 4995,
        "properties": {
            "added_by": "Cart.js"
        },
        "quantity": 5,
        "title": "Widget 1",
        "variant_id": 12345678
    }
]

That's it! You can call addItems() as many times as you like in the same function, and Cart.js will queue up Ajax requests as needed.

Note on multiple line items with the same variant ID

Shopify will collate multiple line items for the same variant into one — for example, if we clicked "Add Five Widgets" in the example above again, we'd end up with one line item with "quantity": 10 instead of two line item with "quantity: 5".

However, this doesn't apply when you add the same variant with custom line item properties that differ — if we changed the value of the added_by property and clicked the button, we'd end up with separate line items.

Updating Items

Updating the quantities or properties of line items is just as simple as adding them -- we just make a call to the updateItem() method.

Let's continue from our example above, and say we want to have a button that doubles the number of widgets in our order.

<button id="button-double">Double my Order!</button>

<script type="text/javascript">
    $('#button-double').click(function() {
        var newQuantity = CartJS.cart.items[0].quantity * 2;
        CartJS.updateItem(1, newQuantity);
    });
</script>

Existing items referenced by index, not variant ID

One important thing to note is that the updateItem() method takes the line number (the "index") of the item in the cart you'd like to update, not the variant ID. This is because it's possible (and quite common) to have multiple items in the cart with the same variant ID but with different properties.

Shopify uses a 1-based index for line items, so the index of the first line item in a cart is 1, not 0 as is common in many programming languages.

If you'd like to update an item using just the variant ID, you can use updateItemById(), which operates the same way as updateItem() but takes the variant ID as the first parameter.

Removing Items

Removing items works in a similar way to updating items -- just call the removeItem() method, passing the line number of the line item you'd like to remove. As with the update method, if you'd like to remove all line items with a particular variant ID, you can use removeItemById() instead.

If you'd like to empty the cart completely, just call the clear() method:

<button id="button-empty">Empty Cart</button>

<script type="text/javascript">
    $('#button-empty').click(function() {
        CartJS.clear();
    });
</script>

Cart Attributes

In addition to cart items, Shopify also provides support for cart attributes, which are used to store custom information about an order. For example, a common use case for attributes is to store a flag indicating whether the customer would like their order gift wrapped.

Cart.js provides some conveniences around manipulating these attributes. Let's take the gift wrap example and see how we could implement it with a simple checkbox:

<label>
    <input id="gift-wrap-checkbox" type="checkbox" />
    Please gift wrap my order
</label>

<script type="text/javascript">
    // Update the 'Gift Wrap' cart attribute based on the state of the checkbox whenever it changes.
    $('#gift-wrap-checkbox').change(function() {
        CartJS.setAttribute('Gift Wrap', this.checked ? 'Yes': 'No' );
    });
</script>

Now, any time a customer checks or un-checks the options, CartJS will make an update request to the Shopify server.

Cart.js provides convenience methods for setting multiple attributes at once, clearing attributes, and setting the special-case note attribute. For a full list of supported methods, see the API Reference.

Callbacks

All Core API methods that result in an Ajax request being made to the server allow you to specify one or more callback functions, just like a regular jQuery $.ajax() request. Callbacks are specified through an options hash, passed as the final optional argument to the core methods.

To take a common example, we'll often want to provide callback methods to handle possible results when we try to add an item to our cart. We want to inform the user of the success or failure of their action, and potentially take some other action.

<button id="button">Add to Cart</button>

<div id="message"></div>

<script type="text/javascript">
    $('#button').click(function() {

        // Call the addItem() method.
        // Note the empty object as the third argument, representing no line item properties.  
        CartJS.addItem(12345678, 1, {}, {

            // Define a success callback to display a success message.
            "success": function(data, textStatus, jqXHR) {
                $('#message').addClass('message-success');
                $('#message').html('Successfully added to cart.');
            },

            // Define an error callback to display an error message.
            "error": function(jqXHR, textStatus, errorThrown) {
                $('#message').addClass('message-error');
                $('#message').html('There was a problem adding to the cart!');
            }

        });
    });
</script>

Data API

The Data API is the easiest way to start using Cart.js, and for a lot of use cases it'll be all you need.

All you need to do is add the appropriate data- attributes to your code, and the event listeners automatically set up by Cart.js will do the rest.

This guide provides a quick overview - for a full list of available markup attributes, check out the Data API Reference.

Adding Items

To create an element that adds an item to the cart on a click event, just mark up an element with a data-cart-add attribute, and set the value of the attribute to the ID of the variant you'd like to add:

<button data-cart-add="12345678">Add a Widget</button>

Of course, you can always use Liquid to render these elements dynamically in your templates:

{% for variant in product.variants %}
    <button data-cart-add="{{ variant.id }}">Add a {{ variant.title }}</button>
{% endfor %}

Removing Items

To create an element that removes a line item from the cart on a click event, use data-cart-remove or data-cart-remove-id.

The difference between these two is that data-cart-remove expects the index of the line item you'd like to remove from the cart, while data-cart-remove-id expects a variant ID.

See Updating Items in the Core API section for an explanation of why this distinction is required.

<button data-cart-remove-variant="12345678">Remove Widgets from Cart</button>

Toggling Items

If you want to give your customers the ability to "toggle" items in or out of their cart, you can use a data-cart-toggle attribute on an element that fires a change event (like a checkbox or radio input).

A common use case for this is having an "added extra" on the cart page before the customer checks out - for example, a gift card.

Let's say we have a selection of gift cards a customer can choose from, and that we've created a collection containing them:

{% for gift_card_product in collections.gift-cards.products %}
<label>
    <input type="checkbox" data-cart-toggle="{{ gift_card_product.variants.first.id }}" />
    Add {{ gift_card_product.title | escape }} to my order (+{{ gift_card_product.variants.first.price | money_with_currency }})
</label>
{% endfor %}

This code will render a checkbox that a customer can turn on or off to add or remove the desired gift card from their card.

Submitting Forms

If you've already got a working Shopify theme, you'll already have a couple of <form> elements around the place that you use to add items to the cart - for example, in your product.liquid template.

You can convert these existing forms to use Cart.js simply by adding a data-cart-submit attribute to the <form> element:

<form action="/cart/add" method="post" data-cart-submit>
    <select name="id">
        {% for variant in product.variants %}
          <option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
        {% endfor %}
    </select>

    <button type="submit">Buy Now</button>
</form>

When the customer clicks "Buy Now", Cart.js will intercept the form submission and convert it to an Ajax request. All of the usual inputs - id to specify the variant ID, quantity to specify the quantity, and property[] inputs - are supported.

Form submission doesn't work with files

Because Ajax requests don't support POST requests with enctype="multipart/form-data", Cart.js won't be able to submit your form if it contains <input type="file"> elements.

This is something that we hope to auto-detect in future, but for now you should avoid using data-cart-submit on forms that need to upload files.

Events

To make it easy to respond to changes in the customer's cart, Cart.js fires a number of custom jQuery events that you can bind listeners to.

All events are triggered on the document, are prefixed with a cart namespace, and pass the current cart object as the first custom event argument.

For example, if we wanted to update a <span> element with the number of items in the cart after a request, we could listen for the cart.requestComplete event like so:

You have <span id="counter">{{ cart.item_count }}</span> items in your cart.

<script type="text/javscript">
    $(document).on('cart.requestComplete', function(event, cart) {
        $('#counter').html(cart.item_count);
    });
</script>

A complete list of the events triggered by Cart.js is available in the Event Reference.

DOM Binding

Cart.js ships with support for Rivets.js, which allows you to describe in your markup how your data should be rendered, and then automatically handles updating the DOM when that model data changes. This allows us to describe a template for our cart that will be automatically updated whenever the cart changes.

For example, the following simplified template will be populated and updated by Cart.js on page load, and whenever the cart changes:

<section class="cart" data-cart-view="data-cart-view">
  <div rv-each-item="cart.items">
    <div rv-text="item.title">
    <div rv-text="item.quantity"></div>
    <div rv-html="item.price | money Currency.currentCurrency"></div>
    <a href="#" rv-data-cart-remove="index | plus 1">&times;</a>
  </div>

  <div rv-show="cart.item_count | eq 0">
    You don't have any items in your cart.
  </div>

  <div rv-html="cart.total_price | money Currency.currentCurrency"></div>
</section>

You can see a fully featured interactive example of this in action at the end of this section.

Getting Started

To get started with the DOM Binding module, first add a data-cart-view attribute to a parent element of any content you'd like to be automatically updated when the cart changes. You can then use one a number of rv-* attributes to describe how HTML should be rendered from your cart data.

The core syntax for templates involves providing a reference to a model that has been bound to Rivet.js - Cart.js binds the cart data model by default, however you can pass in other data models as the rivetsModels Cart.js initialisation option.

Rivets Binders

Binders are the core of Rivets' power, and provide for both rendering content into the template, and applying conditional logic to the content.

For example, if we wanted to keep a <p> element updated with the number of items in the cart, we could add the rv-text="cart.item_count" attribute like so:

<div data-cart-view="data-cart-view">  
  <p rv-text="cart.item_count"></p>
</div>

This would set the contents of the <p> element to the current value of cart.item_count, and update this content whenever cart changes. If you need to render html instead of plaintext, you can use rv-html instead of rv-text.

Rivets also provides an alternative to rv-text that enables interpolating content into an element, using a syntax of single curly-braces (not to be confused with Liquid's {{) - the previous example could also be stated as:

<div data-cart-view="data-cart-view">  
 <p>{ cart.item_count }</p>
</div>

Along with using Rivets to render content, there are also control flow and loop iterator rv-* tags available.

For example, the following will:

  • iterate over each item in the cart and render a <div>
  • each line item <div> will have its class set to the product_type
  • each line item will contain an element <a href="#" data-cart-remove="index">&times;</a>, where index is the iterator loop-index plus 1
  • conditionally show a message at the end if the cart total is greater than 50
<div data-cart-view="data-cart-view"> 
  <div rv-each-item="cart.items" rv-class="item.product_type">
    <div rv-text="item.title">
    <div rv-text="item.quantity"></div>
    <div rv-html="item.price | money Currency.currentCurrency"></div>
    <a href="#" rv-data-cart-remove="index | plus 1">&times;</a>
  </div>

  <div rf-if="cart.total_price | gt 50">Your cart qualifies for free shipping!</div>
</div>

A full breakdown of Rivets' available binders can be found under the Rivets docs Binder Reference.

Formatters

Rivets provides a range of formatters, that are analogous to filters in Liquid (though the syntax is slightly different). These are extensible, and so there are some Shopify-specific formatters included with Cart.js.

Formatters look similar to Liquid filters, but have two key differences.

  • Where in Liquid a filter should be appended with a colon before passing arguments (e.g. {{ 'Title' | prepend: 'A ' }}), in Rivets you should not include the colon (e.g. { 'Title' | prepend 'A ' })
  • Formatters can also be used for evaluation and return a boolean result - e.g. { cart.item_count | eq 0 } will return true if the cart is empty.

A full list of included formatters can be found in the DOM Binding Reference.


You may also need to create additional formatters to meet the requirements of a specific project. With Rivets, this is very straightforward - a global rivets object is available once Cart.js has finished loading, and additional formatters can be extended onto the rivets object with the following syntax (where formatter_name is the newly defined formatter's name):

rivets.formatters.formatter_name = function(value) {
  // Do something to value
  return value;
};

To learn more about creating additional formatters, check out the Rivets Guide on Formatters.


DOM Binding Live Example

ItemPriceQtyLine Price
  • {property.name}: {property.value}
 -  + ×
You don't have any items in your cart.

<!-- Add to cart form, using Data API -->
<form data-cart-submit="data-cart-submit">
    <label>Select a Product</label>
    <select name="id">
        <option value="716986707">Coat</option>
        <option value="716934999">Hat</option>
    </select>

    <label>Choose a Quantity</label>
    <select name="quantity">
        <option>1</option>
        <option>2</option>
        <option>3</option>
        <option>4</option>
        <option>5</option>
    </select>

    <label>Add a Custom Label <small>Optional</small></label>
    <input type="text" name="properties[Custom Label]" />

    <button type="submit">Add to Cart</button>
</form>

<!-- Cart table, rendered using DOM Binding  -->
<table data-cart-view="data-cart-view">
    <thead>
        <tr>
            <th>Item</th>
            <th>Price</th>
            <th colspan="2">Qty</th>
            <th>Line Price</th>
        </tr>
    </thead>
    <tbody>
        <tr rv-each-item="cart.items">
            <td>
                <strong rv-text="item.title"></strong>
                <ul rv-hide="item.propertyArray | empty">
                    <li rv-each-property="item.propertyArray < properties">
                        <small class="text-muted">{property.name}: {property.value}</small>
                    </li>
                </ul>
            </td>
            <td rv-html="item.price | money Currency.currentCurrency"></td>
            <td>
                <a href="#" rv-data-cart-update="index | plus 1" rv-data-cart-quantity="item.quantity | minus 1">-</a>
                <span rv-text="item.quantity"></span>
                <a href="#" rv-data-cart-update="index | plus 1" rv-data-cart-quantity="item.quantity | plus 1">+</a>
            </td>
            <td>
                <a href="#" rv-data-cart-remove="index | plus 1">&times;</a>
            </td>
            <td rv-html="item.line_price | money Currency.currentCurrency"></td>
        </tr>
        <tr rv-show="cart.item_count | lt 1">
            <td colspan="5">You don't have any items in your cart.</td>
        </tr>
    </tbody>
    <tfoot rv-show="cart.item_count | gt 0">
        <tr>
            <td colspan="4" rv-html="cart.total_weight | weight_with_unit"></td>
            <td rv-html="cart.total_price | money Currency.currentCurrency"></td>
        </tr>
    </tfoot>
</table>

<img src="loader.gif" width="16" height="11" class="cart-visible-loading"/>