Overview
Some Shopify themes and accelerated checkout methods can allow customers to proceed to checkout before selecting a required delivery date or delivery time. This can create fulfillment issues, failed deliveries, scheduling conflicts, and manual customer support work.
This ProPickup validation script prevents checkout until the customer selects a valid delivery date when using Local Delivery.
Why Force a Delivery Date?
Enforcing a delivery date helps merchants:
Prevent incomplete delivery orders
Reduce failed deliveries
Eliminate manual follow-up emails/calls
Improve fulfillment accuracy
Ensure route planning is possible
Maintain same-day or scheduled delivery rules
Prevent customers from bypassing delivery widgets
Ensure Shopify checkout contains required delivery metadata
Without validation, customers may:
Click checkout before the widget fully loads
Skip date selection accidentally
Use Shop Pay / accelerated checkout too quickly
Submit orders with missing delivery scheduling information
Common Use Cases
This validation is especially useful for:
Business Type | Why It Matters |
Florists | Delivery timing is critical |
Grocery stores | Fresh inventory scheduling |
Bakeries | Production planning |
Meal prep services | Route batching |
Cannabis delivery | Compliance scheduling |
Furniture delivery | Delivery windows required |
Gift shops | Holiday/date-sensitive orders |
What This Script Does
The script:
Detects when the customer selected Delivery
Checks whether the hidden ProPickup
Delivery-Dateattribute contains a valueBlocks checkout if no date exists
Displays an error message
Automatically scrolls the customer back to the ProPickup widget
Validation Script
<script>
/**
* ---------------------------------------------------------
* ProPickup — Force Delivery Date + Postal/ZIP Validation
* ---------------------------------------------------------
*
* Prevents checkout unless Local Delivery customers enter:
* 1. Postal/ZIP code
* 2. Delivery date
*
* Required Elements:
* - attributes[Checkout-Method]
* - attributes[Delivery-Date]
* - input[name="sca-filter-location-by-postal-code"]
*
* ---------------------------------------------------------
*/
(function () {
const ERROR_ID = 'propickup-delivery-validation-error';
function getCartForm() {
return document.querySelector('form[action*="/cart"]');
}
function getAttribute(name) {
return document.querySelector(`input[name="attributes[${name}]"]`);
}
function getPostalCodeInput() {
return document.querySelector(
'input[name="sca-filter-location-by-postal-code"]'
);
}
function getErrorMount() {
return (
document.querySelector('.sca-filter-location-by-postal-code-container') ||
getCartForm() ||
document.body
);
}
function showMainError(message) {
let error = document.getElementById(ERROR_ID);
const mount = getErrorMount();
if (!error) {
error = document.createElement('div');
error.id = ERROR_ID;
error.setAttribute('role', 'alert');
error.style.color = '#b00020';
error.style.background = '#fff3f5';
error.style.border = '1px solid #b00020';
error.style.borderRadius = '6px';
error.style.padding = '10px 12px';
error.style.margin = '12px 0';
error.style.fontSize = '14px';
error.style.fontWeight = '600';
mount.prepend(error);
}
error.textContent = message;
}
function clearMainError() {
const error = document.getElementById(ERROR_ID);
if (error) error.remove();
}
function showZipError(input) {
const helper = document.querySelector('.scasp-zipcode-required');
if (helper) {
helper.style.display = 'block';
helper.textContent = 'Please enter your postal/ZIP code before checkout.';
}
if (input) {
input.style.border = '1px solid #b00020';
if (input.scrollIntoView) {
input.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
setTimeout(function () {
input.focus();
}, 250);
}
}
function clearZipError(input) {
const helper = document.querySelector('.scasp-zipcode-required');
if (helper) {
helper.style.display = 'none';
}
if (input) {
input.style.border = '';
}
}
function isCheckoutAction(target) {
return !!target.closest(
'button[name="checkout"], input[name="checkout"], [type="submit"][name="checkout"], a[href*="/checkout"], button[onclick*="checkout"], [data-testid*="Checkout"], [class*="checkout"]'
);
}
function blockCheckout(e) {
e.preventDefault();
e.stopPropagation();
if (typeof e.stopImmediatePropagation === 'function') {
e.stopImmediatePropagation();
}
return false;
}
function validate(e) {
const checkoutMethod = getAttribute('Checkout-Method');
const deliveryDate = getAttribute('Delivery-Date');
const postalCodeInput = getPostalCodeInput();
const checkoutMethodValue = checkoutMethod && checkoutMethod.value
? checkoutMethod.value.trim().toLowerCase()
: '';
const isDelivery =
checkoutMethodValue === 'delivery' ||
checkoutMethodValue === 'local delivery' ||
checkoutMethodValue === 'local_delivery';
/**
* If Checkout-Method is missing, we still validate the ZIP field
* because some themes/apps load the attribute after the checkout button.
*/
const shouldValidateDelivery = isDelivery || !checkoutMethodValue;
if (!shouldValidateDelivery) {
clearMainError();
clearZipError(postalCodeInput);
return true;
}
const hasPostalCode =
postalCodeInput &&
postalCodeInput.value &&
postalCodeInput.value.trim() !== '';
const hasDeliveryDate =
deliveryDate &&
deliveryDate.value &&
deliveryDate.value.trim() !== '';
if (!hasPostalCode) {
blockCheckout(e);
showMainError('Please enter your postal/ZIP code before checkout.');
showZipError(postalCodeInput);
return false;
}
clearZipError(postalCodeInput);
if (!hasDeliveryDate) {
blockCheckout(e);
showMainError('Please select a delivery date before checkout.');
const widget = document.querySelector(
'#sca-storepickup-container, .sca-storepickup-container, #date-delivery, [data-propickup-widget]'
);
if (widget && widget.scrollIntoView) {
widget.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
return false;
}
clearMainError();
return true;
}
document.addEventListener(
'click',
function (e) {
if (isCheckoutAction(e.target)) {
return validate(e);
}
},
true
);
document.addEventListener(
'submit',
function (e) {
const form = e.target;
if (
form &&
(
form.matches('form[action*="/cart"]') ||
form.querySelector('input[name="checkout"]') ||
form.querySelector('button[name="checkout"]')
)
) {
return validate(e);
}
},
true
);
})();
</script>
Installation
Option 1 — Shopify Theme (Recommended)
Add the script to:
theme.liquid
Place it:
Near the bottom of the
<body>After the ProPickup widget/app block loads
Option 2 — Cart Template
Add directly inside:
cart.liquidmain-cart-footer.liquidcart drawer templates
How It Works
The script checks these hidden cart attributes generated by ProPickup:
Attribute | Purpose |
| pickup or delivery |
| selected delivery date |
If:
Checkout-Method = delivery
AND
Delivery-Date = empty
Checkout is blocked.
Optional Enhancements
Require Delivery Time Too
You can also validate:
attributes[Delivery-Time]
Example:
const deliveryTime = getAttribute(form, 'Delivery-Time');
const hasDeliveryTime =
deliveryTime &&
deliveryTime.value &&
deliveryTime.value.trim() !== '';
Then require both date AND time before checkout.
Recommended When Using
This validation is strongly recommended when merchants use:
Same-day delivery
Delivery cutoff times
Time slot scheduling
Driver route optimization
Delivery fees by date/time
Perishable products
Holiday scheduling
Multi-location delivery routing
Troubleshooting
Validation Not Triggering
Check:
ProPickup widget loads before script runs
Hidden cart attributes exist
Theme uses standard Shopify cart form
Checkout buttons are inside the cart form
Error Message Appears But Checkout Continues
Some themes use AJAX or dynamic checkout methods.
Try:
Moving script lower in the page
Disabling accelerated checkout buttons
Binding validation to custom checkout buttons
Best Practice Recommendation
For delivery-based stores, requiring:
Delivery date
Delivery time
Delivery location
before checkout dramatically reduces:
support tickets
fulfillment mistakes
failed delivery attempts
refund requests
This creates a cleaner operational workflow and improves customer experience.
