Gatsby Theme Shopify Manager

The easiest way to build a Shopify store on Gatsby.
A cheery-looking hipster holding a laptop wearing a yellow shirt, a black hat, and glasses

Gatsby Theme Shopify Manager is a Gatsby theme that manages the data connections between Shopify and your Gatsby storefront. All you have to do is:

  1. Install the theme
  2. Provide API credentials
  3. Import hooks

And then start coding. 🚀

This is a data theme, not a UI theme. It makes setting up a Shopify cart and buyer flow simple (including managing state), so you can focus on making it look great.

Want to see this in action? This page is a working example, as well as documentation. Read on to see it!

Getting Started

To start using the theme, install it with your package manager of choice:

yarn add gatsby-theme-shopify-manager gatsby-source-shopify

To start using it, open your gatsby-config file and include your Shop name and access token from the Storefront API.

{
  resolve: `gatsby-theme-shopify-manager`,
  options: {
    shopName: 'your shop name',
    accessToken: 'your storefront API access token',
  },
},

The options you pass to this theme are used to configure both gatsby-source-shopify and the shopify-buy client.

Configuration options

There are four options to configure this theme, with only the first two being required.

  1. shopName
  2. accessToken
  3. shouldConfigureSourcePlugin
  4. shouldWrapRootElementWithProvider

In case you're looking for a quick copy and paste 👇:

{
  resolve: `gatsby-theme-shopify-manager`,
  options: {
    shopName: 'your-shop-name', // or custom domain
    accessToken: 'your-api-access-token',
    shouldConfigureSourcePlugin: true, // default
    shouldWrapRootElementWithProvider: true, // default
  },
},

shopName

This is the first part of the default Shopify domain. If your domain is my-store.myshopify.com, the shopName would be my-store. This value is required unless you pass false to both shouldConfigureSourcePlugin and shouldWrapRootElementWithProvider.

If you're using a custom domain with Shopify, you should enter your custom domain instead (e.g. mystore.com). Make sure to only include the name and domain, and omit the protocol (http) and any trailing slashes.

{
  resolve: `gatsby-theme-shopify-manager`,
  options: {
    shopName: 'my-store', // or mystore.com
  },
},

accessToken

This is the Storefront API token that you get when you make a new Shopify app. This value is required unless you pass false to both shouldConfigureSourcePlugin and shouldWrapRootElementWithProvider.

{
  resolve: `gatsby-theme-shopify-manager`,
  options: {
    accessToken: '12lg(@!l129gj12p['
  },
},

shouldConfigureSourcePlugin

By default, gatsby-theme-shopify-manager passes a configuration object to the gatsby-source-shopify plugin in gatsby-config. If you need to do advanced configuration of that plugin, pass false to this option. From there, you can set up and configure your source plugin as you please.

{
  resolve: `gatsby-theme-shopify-manager`,
  options: {
    // default value is true
    shouldConfigureSourcePlugin: false
  },
},

shouldWrapRootElementWithProvider

By default, gatsby-theme-shopify-manager wraps the application in a <ContextProvider>, and passes the shopName and accessToken provided to the theme options through to the provider. Pass false to this option to prevent this behavior.

{
  resolve: `gatsby-theme-shopify-manager`,
  options: {
    // default value is true
    shouldWrapRootElementWithProvider: false
  },
},

Context Provider

The Shopify buy client and current cart state are managed using React context. By default, the application is wrapped by the Provider and the shopName and accessToken are pulled from the config options and passed to it. However, in some cases, it might be preferable to manage the provider.

By default, gatsby-theme-shopify-manager wraps the application in a provider. If you want to manage this yourself, pass shouldWrapRootElementWithProvider: false to the theme options. If you don't, you'll have multiple providers that may result in unintended side-effects.

To use the provider, import it and pass shopName and accessToken to it as props.

import React from 'react';
import {ContextProvider} from 'gatsby-theme-shopify-manager';
export const App = ({children}) => {
  const shopName = 'some-shop-name';
  const accessToken = 'some-access-token';

  return (
    <ContextProvider shopName={shopName} accessToken={accessToken}>
      {children}
    </ContextProvider>
  );
};

Hooks

The main export of this package are the hooks that you can use. Here are the hooks you can use:

useCart()

The most basic hook is the useCart() hook. This hook gives you access to the current cart state (or null, if there is no cart state). From this object you can get access to the line items, the total amounts, and additional checkout-related information.

The cart object is currently null.

import React from 'react';
import {useCart} from 'gatsby-theme-shopify-manager';

export function ExampleUseCart() {
  const cart = useCart();

  if (cart == null) {
    return <p>The cart object is currently null.</p>;
  }

  const cartDate = new Date(cart.createdAt).toLocaleDateString();

  return (
    <p>
      Your cart was created on {cartDate}.
      <br />
      You have ${cart.totalPrice} worth of products in your cart.
    </p>
  );
}

useCartItems()

The useCartItems() hook provides access to the items currently in the cart. This hook always returns an array.

Your cart is empty.

import React from 'react';
import {useCartItems} from 'gatsby-theme-shopify-manager';

export function ExampleUseCartItems() {
  const cartItems = useCartItems();

  if (cartItems.length < 1) {
    return <p>Your cart is empty.</p>;
  }

  return (
    <>
      <p>Your cart has the following items:</p>
      <ul>
        {cartItems.map((lineItem) => (
          <li key={lineItem.title}>
            {lineItem.title} - {lineItem.variant.title}
          </li>
        ))}
      </ul>
    </>
  );
}

useCartCount()

The useCartCount() hook provides the number of items currently in the cart. This hook returns 0 if the cart is null (and will always return a number). This method does not return just cartItems.length, but sums the quantity of each variant in the cart.

Your cart has 0 items.

import React from 'react';
import {useCartCount} from 'gatsby-theme-shopify-manager';

export function ExampleUseCartCount() {
  const cartCount = useCartCount();

  return <p>Your cart has {cartCount} items.</p>;
}

useCheckoutUrl()

The useCheckoutUrl() hook provides the checkout url that is associated with the current cart. It returns null when the cart is null, and otherwise returns a string.

There is no active checkout.

import React from 'react';
import {useCheckoutUrl} from 'gatsby-theme-shopify-manager';

export function ExampleUseCheckoutUrl() {
  const checkoutUrl = useCheckoutUrl();

  return checkoutUrl == null ? (
    <p>There is no active checkout.</p>
  ) : (
    <p>
      <a href={checkoutUrl} target="_blank" rel="noopener noreferrer">
        Complete Your Order →
      </a>
    </p>
  );
}

useAddItemsToCart()

The useAddItemsToCart hook allows you to add multiple items to the cart at a single time. The hook returns a function that accepts an array of objects with keys variantId and quantity. It returns a void promise that will throw if it encounters an error. You can optionally include an array of customAttributes with each item.

There are currently 0 items in your cart.

import React from 'react';
import {Button} from 'theme-ui';
import {useAddItemsToCart, useCartCount} from 'gatsby-theme-shopify-manager';

export function ExampleUseAddItemsToCart() {
  const cartCount = useCartCount();
  const addItemsToCart = useAddItemsToCart();

  async function addToCart() {
    const items = [
      {
        variantId: 'some_variant_id',
        quantity: 1,
      },
    ];

    try {
      await addItemsToCart(items);
      alert('Successfully added that item to your cart!');
    } catch {
      alert('There was a problem adding that item to your cart.');
    }
  }

  return (
    <>
      <p>There are currently {cartCount} items in your cart.</p>
      <Button onClick={addToCart}>
        Add items to your cart
      </Button>
    </>
  );
}

useAddItemToCart()

The useAddItemToCart is similar to the useAddItemsToCart, but is only for a single item at a time. The hook returns a function that accepts three arguments: variantId, quantity, and (optionally) an array of customAttributes.

There are currently 0 items in your cart.

import React from 'react';
import {Button} from 'theme-ui';
import {useAddItemToCart, useCartCount} from 'gatsby-theme-shopify-manager';

export function ExampleUseAddItemToCart() {
  const cartCount = useCartCount();
  const addItemToCart = useAddItemToCart();

  async function addToCart() {
    const variantId = 'some_variant_id';
    const quantity = 1;

    try {
      await addItemToCart(variantId, quantity);
      alert('Successfully added that item to your cart!');
    } catch {
      alert('There was a problem adding that item to your cart.');
    }
  }

  return (
    <>
      <p>There are currently {cartCount} items in your cart.</p>
      <Button onClick={addToCart}>
        Add an item to your cart
      </Button>
    </>
  );
}

useRemoveItemsFromCart()

The useRemoveItemFromCart hook allows you to remove multiple items from the cart at a single time. The hook returns a function that accepts an array of variantId strings. It returns a void promise that will throw if it encounters an error.

Your cart is empty.

import React from 'react';
import {Button} from 'theme-ui';
import {
  useRemoveItemsFromCart,
  useCartItems,
} from 'gatsby-theme-shopify-manager';

export function ExampleUseRemoveItemsFromCart() {
  const cartItems = useCartItems();
  const removeItemsFromCart = useRemoveItemsFromCart();

  async function removeFromCart() {
    if (cartItems.length < 1) {
      return;
    }
    const variantId = cartItems[0].variant.id;

    try {
      await removeItemsFromCart([variantId]);
      alert('Successfully removed an item from your cart!');
    } catch {
      alert('There was a problem removing that item from your cart.');
    }
  }

  const cartMarkup =
    cartItems.length > 0 ? (
      <>
        <p>Your cart has the following items:</p>
        <ul>
          {cartItems.map((lineItem) => (
            <li key={lineItem.title}>
              {lineItem.title} - {lineItem.variant.title}
            </li>
          ))}
        </ul>
      </>
    ) : (
      <p>Your cart is empty.</p>
    );

  return (
    <>
      {cartMarkup}
      <Button onClick={removeFromCart} sx={{mb: 3}}>
        Remove items from your cart
      </Button>
    </>
  );
}

useRemoveItemFromCart()

The useRemoveItemFromCart is similar to the useRemoveItemsFromCart hook, but is only for a single item at a time. The hook returns a function that accepts a single argument: variantId.

Your cart is empty.

import React from 'react';
import {Button} from 'theme-ui';
import {
  useRemoveItemFromCart,
  useCartItems,
} from 'gatsby-theme-shopify-manager';

export function ExampleUseRemoveItemFromCart() {
  const cartItems = useCartItems();
  const removeItemFromCart = useRemoveItemFromCart();

  async function removeFromCart() {
    if (cartItems.length < 1) {
      return;
    }
    const variantId = cartItems[0].variant.id;

    try {
      await removeItemFromCart(variantId);
      alert('Successfully removed an item from your cart!');
    } catch {
      alert('There was a problem removing that item from your cart.');
    }
  }

  const cartMarkup =
    cartItems.length > 0 ? (
      <>
        <p>Your cart has the following items:</p>
        <ul>
          {cartItems.map((lineItem) => (
            <li key={lineItem.title}>
              {lineItem.title} - {lineItem.variant.title}
            </li>
          ))}
        </ul>
      </>
    ) : (
      <p>Your cart is empty.</p>
    );

  return (
    <>
      {cartMarkup}
      <Button onClick={removeFromCart} sx={{mb: 3}}>
        Remove item from your cart
      </Button>
    </>
  );
}

useUpdateItemQuantity()

The useUpdateItemQuantity() hook returns a function that updates the quantity of a lineitem currently in the cart. The returned function accepts two arguments: variantId and quantity. It returns a void Promise that throws if it encounters an error. If 0 is passed in as the quantity, it removes the item from the cart.

Your cart is empty.

import React, {useState} from 'react';
import {Flex, Button, Input} from 'theme-ui';
import {
  useUpdateItemQuantity,
  useCartItems,
} from 'gatsby-theme-shopify-manager';

export function ExampleUseUpdateItemQuantity() {
  const [quantity, setQuantity] = useState(1);
  const [item] = useCartItems();
  const updateItemQuantity = useUpdateItemQuantity();

  async function updateQuantity() {
    if (item == null) {
      return;
    }

    const variantId = item.variant.id;

    try {
      await updateItemQuantity(variantId, quantity);
      alert('Successfully updated the item quantity!');
    } catch {
      alert("There was a problem updating that item's quantity.");
    }
  }

  const itemMarkup =
    item == null ? (
      <p>Your cart is empty.</p>
    ) : (
      <p>
        {item.title} - {item.variant.title} ({item.quantity})
      </p>
    );

  const formMarkup = (
    <Flex sx={{alignItems: 'flex-start'}} as="form" onSubmit={updateQuantity}>
      <Input
        sx={{width: '50px', mr: 3}}
        type="number"
        defaultValue={quantity}
        onChange={(event) => setQuantity(Number(event.target.value))}
      />
      <Button sx={{mb: 3}}>Update quantity</Button>
    </Flex>
  );

  return (
    <>
      {itemMarkup}
      {formMarkup}
    </>
  );
}

Escape Hooks

In addition to the normal hooks, there are two 'escape' hooks. These hooks allow access to setting the cart state and the client object that is used to interact with Shopify. It's important to note that these are considered experiemental–using these hooks may result in unintended side-effects.

useClientUnsafe

The useClientUnsafe hook returns the client object currently held in the context. From there you can call methods on it to enable more functionality. Shopify has all the documentation for what you can do with the client object. Example usage:

import React from 'react';
import {useClientUnsafe} from 'gatsby-theme-shopify-manager';

export function ExampleUseClientUnsafe() {
  const client = useClientUnsafe();
  // do work with the client here
}

useSetCartUnsafe

The useSetCartUnsafe returns a function that allows the user to set the current cart state. You can use it similar to the function returned from a useState destructure. This is useful for interactions with the client object that return an updated cart object. Example usage:

import React from 'react';
import {useClientUnsafe, useSetCartUnsafe} from 'gatsby-theme-shopify-manager';

export function ExampleUseSetCartUnsafe() {
  const client = useClientUnsafe();
  const setCart = useSetCartUnsafe();

  async function changeCart() {
    const newCart = await client.doSomeMethodThatReturnsACartObject();
    setCart(newCart);
  }

  changeCart();
}

Examples

Only Down

Only Down is an example site that shows how to use the gatsby-theme-shopify-manager plugin.

Contributing & Issues

Want to add a feature, or report a bug? Head over to the GitHub repo to jump in!