Build a date picker for a specific shipping rate
Collecting extra information in relation to specific shipping rates, like a preferred delivery date, is a common situation. In this tutorial, you'll use checkout UI extensions to collect a delivery date for a specific shipping rate.
Checkout UI extensions are available only to stores on a Shopify Plus plan.

Anchor to What you'll learnWhat you'll learn
In this tutorial, you'll learn how to do the following tasks:
- Generate a checkout UI extension, using Shopify CLI.
- Configure your checkout UI extension in the extension TOML file.
- Query the Storefront API from the extension code to get shipping rate data.
- Use the checkout UI component library to add new user interface to the checkout.
- Use the checkout UI extension API to read and write order information.
- Save the date to a metafield and display the value in the Shopify admin.
Anchor to RequirementsRequirements
-
You've created a Partner account.
-
You've created a new development store with the following:
- Generated test data
- Checkout and Customer Accounts Extensibility developer preview enabled
- You've reviewed the UX guidelines for customizing delivery methods.
Anchor to Sample codeSample code
If you want to quickly get started, then you can copy and paste the following sample code into your src/Checkout.jsx
and shopify.extension.toml
files. This tutorial describes the sample code step by step.
Sample code
import { useState, useCallback, useMemo } from "react";
import {
Heading,
DatePicker,
useApplyMetafieldsChange,
useDeliveryGroupListTarget,
useApi,
reactExtension,
} from "@shopify/ui-extensions-react/checkout";
reactExtension("purchase.checkout.shipping-option-list.render-after", () => (
<Extension />
));
export default function Extension() {
const [selectedDate, setSelectedDate] = useState("");
const [yesterday, setYesterday] = useState("");
const { extension } = useApi();
const { target } = extension;
// Set a function to handle updating a metafield
const applyMetafieldsChange = useApplyMetafieldsChange();
// Get the delivery group list
const deliveryGroupList = useDeliveryGroupListTarget();
// Define the metafield namespace and key
const metafieldNamespace = "yourAppNamespace";
const metafieldKey = "deliverySchedule";
// Sets the selected date to today, unless today is Sunday, then it sets it to tomorrow
useMemo(() => {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const deliveryDate = today.getDay() === 0 ? tomorrow : today;
setSelectedDate(formatDate(deliveryDate));
setYesterday(formatDate(yesterday));
}, []);
// Set a function to handle the Date Picker component's onChange event
const handleChangeDate = useCallback((selectedDate) => {
setSelectedDate(selectedDate);
// Apply the change to the metafield
applyMetafieldsChange({
type: "updateMetafield",
namespace: metafieldNamespace,
key: metafieldKey,
valueType: "string",
value: selectedDate,
});
}, []);
// Guard against duplicate rendering of `shipping-option-list.render-after` target for one-time purchase and subscription sections. Calling `applyMetafieldsChange()` on the same namespace-key pair from duplicated extensions would otherwise cause an overwrite of the metafield value.
// Instead of guarding, another approach would be to prefix the metafield key when calling `applyMetafieldsChange()`. The `deliveryGroupList`'s `groupType` could be used to such effect.
if (!deliveryGroupList || deliveryGroupList.groupType !== 'oneTimePurchase') {
return null;
}
const { deliveryGroups } = deliveryGroupList;
// Function to compute if Express is selected in any of the delivery groups
const isExpressSelected = () => {
const expressHandles = new Set(
deliveryGroups
.map(
({deliveryOptions}) =>
deliveryOptions.find(({ title }) => title === "Express")?.handle,
)
.filter(Boolean),
);
return deliveryGroups.some(({ selectedDeliveryOption }) =>
expressHandles.has(selectedDeliveryOption?.handle),
);
};
// Render the extension components if Express is selected
return isExpressSelected() ? (
<>
<Heading>Select a date for delivery</Heading>
<DatePicker
selected={selectedDate}
onChange={handleChangeDate}
disabled={["Sunday", { end: yesterday }]}
/>
</>
) : null;
}
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
api_version = "2024-07"
[capabilities]
api_access = true
network_access = true
block_progress = true
[[extensions]]
type = "ui_extension"
name = "checkout-ui"
handle = "checkout-ui"
# [START order-status.config-extension]
[[extensions.targeting]]
target = "purchase.checkout.shipping-option-list.render-after"
module = "./src/Checkout.jsx"
export = "purchaseCheckoutShippingOptionListRenderAfter"
# [END order-status.config-extension]
# [[extensions.metafields]]
# namespace = "my-namespace"
# key = "my-key"
# Read more on extension settings at https://shopify.dev/api/checkout-extensions/checkout/configuration
# [settings]
# [[settings.fields]]
# key = "banner_title"
# type = "single_line_text_field"
# name = "Banner title"
# description = "Enter a title for the banner"
Anchor to Step 1: Create a checkout UI extensionStep 1: Create a checkout UI extension
To create a checkout UI extension, you'll use Shopify CLI, which generates starter code for building your extension.
-
Navigate to your app directory.
-
Run the following command to create a new extension:
Terminal
shopify app generate extension -
Select checkout UI as the extension type, and then provide a name for your extension.
-
Select a language for your extension. The example code in this tutorial uses JavaScript React.
You now have a new directory for your app under <app-name>
> extensions
. The extension directory includes the extension script at src
> Checkout.jsx
.
Anchor to Step 2: Import componentsStep 2: Import components
In src/Checkout.jsx
, import the components from the checkout UI extensions API that you need to build the extension:
src/Checkout.jsx
Anchor to Step 3: Set up the extension targetStep 3: Set up the extension target
Targets control where your extension renders in the checkout flow. The purchase.checkout.shipping-option-list.render-after
extension target renders the extension after the shipping methods section on the shipping step of the checkout.
When you generate an app extension, a TOML configuration file named shopify.extension.toml
is automatically generated in your app's extension directory. The TOML file is located in the extensions/
directory of your app project. It contains basic information and settings.
For information about the properties that you can configure in the TOML file, refer to Configuring extensions.
You can define more than one static extension target so that merchants can add the extension to multiple locations in the checkout. You can do this by using multiple reactExtension()
functions with different static extension targets.
Anchor to Use the extension target in your index fileUse the extension target in your index file
In src/Checkout.jsx
, add a reactExtension()
function to render your extension using the purchase.checkout.shipping-option-list.render-after
extension target.
src/Checkout.jsx
Anchor to Reference the target in your configuration fileReference the target in your configuration file
In your checkout UI extension configuration file, create an [[extensions.targeting]]
section with the following information:
- target - An identifier that specifies where you're injecting code into Shopify. This needs to match the target that you exported from your
Checkout.jsx
file. - module - The path to the file that contains the extension code.
- export - The name of the exported function that contains the extension code.
Whenever you edit your extension configuration file, you need to restart your server for the changes to take effect.
shopify.extension.toml
Anchor to Step 4: Set up a metafieldStep 4: Set up a metafield
Now that you've set up the extension target, you'll save custom field values in a metafield. Regular metafields are available to any app or extension.
Anchor to Define the metafield namespace and keyDefine the metafield namespace and key
In this step, you'll set a namespace and key for the metafield where you want to store the custom field value. Later, you'll expose values stored in this metafield to merchants in the Shopify admin.
A metafield is a custom field that you can use to store additional information about a Shopify resource. You can use metafields to store information specific to your app without setting up your own storage.
src/Checkout.jsx
Anchor to Store the user input in the metafieldStore the user input in the metafield
You can use the useApplyMetafieldsChange
hook to store the value that the customer enters in the metafields
property of the checkout. This metafield value is later associated with the order.
useApplyMetafieldsChange
is a React hook that lets you write metafield values. To learn more about the hooks available for checkout UI extensions, refer to the checkout UI extension reference.
src/Checkout.jsx
Anchor to Step 5: Render the date picker and set the constraintsStep 5: Render the date picker and set the constraints
In this step, you'll render a calendar where customers can select a delivery date, which is then saved to a metafield
. The DatePicker component will display the available dates.
src/Checkout.jsx
Anchor to Set date constraints for the DatePicker componentSet date constraints for the Date Picker component
If there are only certain dates that delivery can happen, then you can customize how the DatePicker component can be used by buyers by passing through disabled dates in the disabled
property.
src/Checkout.jsx
Anchor to Step 6: Show the selected date in the Shopify adminStep 6: Show the selected date in the Shopify admin
After you've saved the date picker metafield to the order, display it on the order details page in the Shopify admin so that merchants can view it.
In the Shopify admin, add a metafield definition for orders. Use the same namespace
and key
as defined in your /src/Checkout.jsx
file.
If you already placed an order with delivery instructions, then you might need to select the metafield from the Metafields without a definition list.
Anchor to Step 7: Preview the extensionStep 7: Preview the extension
Preview your extension to make sure that it works as expected.
-
In a terminal, navigate to your app directory.
-
Either start or restart your server to build and preview your app:
Terminal
shopify app dev
- Press
p
to open the developer console. - In the developer console page, click on the preview link for the custom field extension.
The date picker should render in the Shipping step of the checkout when Express Shipping is selected.
Anchor to Step 8: Test the extensionStep 8: Test the extension
Test your extension to make sure that it works as expected by placing an order with delivery instructions in checkout.
-
With your server running, open the storefront of your development store.
-
Add a product to the cart and then check out.
-
Fill out the contact and shipping address information, and then move to the Shipping step of the checkout.
-
Select Express Shipping. A date picker appears.
-
Select a date and then complete the checkout.
-
In the Shopify admin for the development store, open the order details page for the order that you just placed.
The delivery date that you entered is displayed in the Metafields section, with the delivery date field that you created.
Anchor to Step 9: Deploy the UI extensionStep 9: Deploy the UI extension
To release your changes to users, create and release an app version. An app version is a snapshot of your app configuration and all extensions. You can have up to 50 checkout UI extensions in an app version.
-
Navigate to your app directory.
-
Run the following command. Optionally, you can provide a name or message for the version using the
--version
and--message
flags.
Terminal
Shopify CLI builds your app, bundles your app components, and deploys to Shopify. To learn more about the processes that are executed when you run deploy, refer to the Shopify CLI command reference.
If you want to create a version, but want to avoid releasing it to users, then run the deploy command with a --no-release
flag. You can release the unreleased app version using Shopify CLI's release command, or through the Partner Dashboard.
An app version created using Shopify CLI contains the following:
- The app configuration from the local configuration file. If the
include_config_on_deploy
flag is omitted orfalse
, the configuration from the active app version will be used instead. - The local version of the app's CLI-managed extensions. If you have an extension in your deployed app, but the extension code doesn't exist locally, then the extension isn't included in your app version.
- The latest drafts of dashboard-managed extensions.
Releasing an app version replaces the current active version that's served to stores with your app installed. It might take several minutes for app users to be upgraded to the new version.
Anchor to Next stepsNext steps
- Learn about the components that are available in checkout UI extensions.
- Consult the API reference for checkout UI targets and their respective types.