Use Case: A product or service requires some customer-provided instructions for special considerations related to their cart (eg. hard-to-find address, delivery difficulties for heavy items, etc.)
Approach: Shopify's Checkout UI Extensions has pre-built components for text entry within the Checkout sections, which can take customer input as a string.
Problem: Providing extra delivery instructions for every checkout could introduce some customer confusion and negatively impact conversion rates. Only certain conditions of the cart should prompt the customer for more information, and those extra details need to be stored on the resulting order for a 3PL/WMS/OMS to use for fulfilment.
This extension started a personal little addition to a dev site where I'm experimenting with Checkout UI APIs. Initially, I'd just implemented Shopify's standard Delivery Date Picker, but I needed a demo that expanded on the idea of a date picker and allowed for more context to be provided by a customer once a date had been chosen.
Going one step further, I figured I'd make it even more conditional by adding in a few rules:
-
The 'Date Picker' Calendar is only displayed when a product features a specific
show_date_picker
boolean metafield (instead of the .dev example, which is tied to chosen Shipping Rate). -
Extra delivery instructions can only appear when two things are true: an
address2
field is present, and a delivery date has been picked.
One more thing needs to happen to round out the solution; write the delivery instructions input back to a metafield on the Order to actually use the result in a fulfilment workflow. Otherwise, it was a nice afternoon spent in VSCode for absolutely no useful reason (story of my life).
Ultimately, this is our customer view in Checkout, and then the output on the completed order:
Surprisingly, the whole 'Delivery Instructions' module is less than 100 lines of code. It could be simplified even further, because I've made some UX choices that are optional. One could argue you probably don't need the 'checkbox' condition that expands the text field input, but I felt like an auto-expanded instructions textfield was a little presumptuous, as if it was asking to be filled out even if there was no need. Customers don't need to see an empty box that clutters their experience - our priority should be to get them through the Checkout quickly and without annoying them.
I'm also including some quality-of-life improvements here:
-
If 'Delivery Instructions' were provided by a customer, but then they changed their mind and unchecked the box (leaving their instructions), then the extension deletes the metafield that's been set on the Checkout.
-
The text field is multi-line by default, but doesn't have to be - a single line is probably sufficent.
-
Fancy icons and spacing elements from the Shopify Component Library.
Here is my Checkout.jsx
file in it's entirety for this extension:
import React, { useState } from "react";
import {
reactExtension,
TextField,
BlockStack,
useApplyMetafieldsChange,
useMetafield,
Checkbox,
Text,
Banner,
BlockSpacer,
InlineLayout,
Icon,
View,
useShippingAddress,
} from "@shopify/ui-extensions-react/checkout";
// Set the entry point for the extension
export default reactExtension("purchase.checkout.shipping-option-list.render-after", () => {
return <App />;
});
function App() {
// Set up the checkbox state
const [checked, setChecked] = useState(false);
// Define the metafield namespace and key
const metafieldNamespace = "custom";
const metafieldKey = "delivery_instructions";
// Get a reference to the metafield
const deliveryInstructions = useMetafield({
namespace: metafieldNamespace,
key: metafieldKey,
});
// Set a function to handle updating a metafield
const applyMetafieldsChange = useApplyMetafieldsChange();
// Get the shipping address
const shippingAddress = useShippingAddress();
// Check if 'address2' is provided in the Shipping Address form
const hasAddress2 = shippingAddress && shippingAddress.address2;
// Handle when the checkbox is checked.
// If the checkbox is unchecked by the buyer, unset (delete) the metafield
const handleCheckboxChange = (isChecked) => {
setChecked(isChecked);
if (!isChecked) {
updateMetafield({
type: "removeMetafield",
namespace: METAFIELD_NAMESPACE,
key: METAFIELD_KEY,
});
}
};
// Render the extension components
return (
<BlockStack>
{hasAddress2 && (
<View maxInlineSize={700}>
<Banner status="info">
<InlineLayout blockAlignment="center" spacing="small100" columns={['auto', 'fill']}>
<Icon source="note" appearance="monochrome"></Icon>
<Text size="medium" appearance="accent" emphasis="bold">
Add Delivery Instructions?
</Text>
</InlineLayout>
<BlockSpacer spacing="loose" />
<Checkbox checked={checked} onChange={handleCheckboxChange}>
<Text>Yes, please see details:</Text>
</Checkbox>
{checked && (
<>
<BlockSpacer spacing="loose" />
<TextField
label="Add Your Delivery Instructions"
multiline={3}
onChange={(value) => {
// Apply the change to the metafield
applyMetafieldsChange({
type: "updateMetafield",
namespace: metafieldNamespace,
key: metafieldKey,
valueType: "string",
value,
});
}}
value={deliveryInstructions?.value}
/>
</>
)}
</Banner>
</View>
)}
</BlockStack>
);
}
Note: Publishing this via an extension-only app does not auto-create the metafield definition for the shop. You'll need to create the custom.delivery_instructions
(or the namespace and key of your choice) metafield within a store's admin for the extension to apply it's values properly to an order.
Thinking out loud, this has potential changes you could make here to address an additional use cases:
Allergy Substitutions Instructions at Checkout:
-
Allow a customer to provide their allergens in a profile customisation, store those allergens in a Customer Metafield.
-
Store ingredient details on products, also using metafield(s) (could be a list, or a JSON array).
-
Re-Configure the above extension to look for any matching metafields between the logged-in Customer's allergens, and the ingredients list(s) of the products within their cart.
-
Warn them of potential issues, and surface the 'Instructions' text field to allow them to specify any substitutions to make before delivery.
-
[Optional] Block checkout progress via the same extension until they provide substitutions so you don't have a lawsuit on your hands.
I'd love to hear any other use cases you can think of! Contact me either though my website at www.mackiec.ca, or on the Shopify Partners Slack
Summary:
Checkout UI Extensions can be nested behind conditions of the cart (including product or customer metafields) to only be served when it makes the most contextual sense for the business. Prioritise conversion speed and only bring forward UI elements for the customers who absolutely need to see them.
âď¸