Shopify Checkout Extension Upsell App

By Nicholas Drishinski | Published

Giving customers the best checkout experience is paramount. One effective way to achieve this is by implementing checkout extensions that offer personalized product suggestions. In this blog post, we will explore the process of creating a checkout extension using Shopify's UI Extensions framework. Specifically, we will focus on a feature that suggests a "surprise" or "mystery" product to customers during the checkout process, thereby increasing the likelihood of additional purchases. By leveraging the power of React and Shopify's APIs, we will guide you through the steps to build a seamless and engaging upsell experience.

Code: https://github.com/ndrishinski/checkout-ex-upsell/blob/main/extensions/checkout-upsell/src/Checkout.jsx

Prefer video?

Enjoy, otherwise read on!

Creating our App

First, ensure you have the Shopify CLI installed and run `shopify app init`. From the options in the terminal I am going to select:

  • `Build a remix app`
  • `JavaScript`
  • `Yes, create a new app`
  • name it 'mystery-upsell'

Now that our app is created, we can `cd mystery-upsell` and run `npm i`.

Then we can generate the checkout extension by running `shopify app generate extension` and then select `checkout UI` in the list of options.I will also name it `mystery-upsell`, and select `JavaScript React ` as the option.

Everything should be in place now so we can run `shopify app dev` and select the store we want to run this app on!

Building the extension UI

Now that everything is setup, we can navigate in our codebase to the Checkout.jsx file which is where we will be working.

You can paste the following code in place:

 


import {
  reactExtension,
  Text,
  Button,
  useApi,
  BlockLayout,
  useApplyCartLinesChange
} from "@shopify/ui-extensions-react/checkout";
import React, {useState, useEffect} from "react";

export default reactExtension("purchase.checkout.block.render", () => (
  <UpsellFreeShipping />
));

function UpsellFreeShipping() {
  const [suggestedProduct, setSuggestedProduct] = useState(null)
  const [showWidget, setShowWidget] = useState(true)
  const {lines, query} = useApi();
  const applyCartLinesChange = useApplyCartLinesChange();

  useEffect(() => {
    query(
      `query {
        product(id: "gid://shopify/Product/<insert-product-id>") { // insert your product ID here
          id
          title
          variants(first: 1) {
            edges {
              node {
                id
                title
                price {
                  amount
                  currencyCode
                }
              }
            }
          }
        }
      }`,
    )
      .then(({data, errors}) => {
        if (data && data.product) {
          setSuggestedProduct({ id: data.product.id, title: data.product.title, price: data.product.variants.edges[0].node.price.amount, variantId: data.product.variants.edges[0].node.id });
        } else if (errors) {
          console.log('Error querying product: ', errors)
        }
      })
      .catch(err => {
        console.error('Error: ', err)
      });
  }, [query]);



  const productAlreadyInCart = lines.current.some(line => line.merchandise.id === suggestedProduct?.variantId);
  async function handleAddProduct(variantId) {
    const result = await applyCartLinesChange({
      type: 'addCartLine',
      merchandiseId: variantId,
      quantity: 1,
    });

    if (result.type === 'error') {
      setShowWidget(true)
    } else {
      setShowWidget(false)
    }
  }
  
  if (!productAlreadyInCart && suggestedProduct != null && showWidget) {
    return (
      <BlockLayout border="base" blockAlignment="baseline" inlineAlignment="center" cornerRadius="base" padding="base">
        <Text size="medium" fontWeight="bold" color="primary">
          Add {suggestedProduct?.title} for just ${suggestedProduct?.price}!
        </Text>
        <Button onPress={() => handleAddProduct(suggestedProduct.variantId)} secondary>
          Add to Checkout
        </Button>
      </BlockLayout>
    );
  } else {
    return (
      <></>
    )
  }
}

That is a lot of code, so I'll only talk about the important pieces. Some important hooks that we are using are the `useApi` and `useApplyCartLinesChange` hooks. These give us the ability to query the GraphQL storefront API easily as well as update the cart with ease! 

We are also utilizing several UI components from the checkout documentation as well. It's always worth noting that Shopify is very protective of the checkout page so there is more limitations there. These UI components have props that allow you to apply additional styling.

If you are interested in learning more about Checkout Extensions then it is definitely recommended to read the documentation here: https://shopify.dev/docs/api/checkout-ui-extensions/2023-07

Important

In the useEffect hook above we are querying for our mystery or surprise product. Make sure you insert your product ID in this line:

`product(id: "gid://shopify/Product/<insert-product-id>`

You can find your product ID by navigating in your store to the product page and typing .json at the end of the url. This will present a JSON structure of your product where you can copy the ID

Conclusion

Creating a checkout extension not only enhances the shopping experience for customers but also provides merchants with an opportunity to boost their sales. By implementing features like product suggestions, businesses can effectively engage customers and encourage them to add more items to their cart. In this blog, we have walked through the development of a simple yet powerful upsell feature using Shopify's UI Extensions. As you continue to explore the capabilities of Shopify and React, consider how you can further customize and optimize your checkout process to meet the unique needs of your customers. Happy coding!

Nick Drishinski

Nick Drishinski is a Senior Software Engineer with over 5 years of experience specializing in Shopify development. When not programming Nick is often creating development tutorials, blogs and courses or training for triathlons.