skills/platform-shopify/shopify-checkout-extensions/SKILL.md
Customize Shopify's checkout with UI extensions for upsells and custom fields, plus Shopify Functions for serverless discount and shipping logic
npx skillsauth add finsilabs/awesome-ecommerce-skills shopify-checkout-extensionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Shopify Checkout Extensions allow apps to render custom UI blocks inside Shopify's checkout without forking the checkout template. Shopify Functions let you replace backend logic — discounts, shipping, payment methods, and order validation — with custom WebAssembly modules that run inside Shopify's infrastructure. Together they replace the deprecated checkout.liquid customization approach and work with both Shopify Plus and non-Plus stores (UI extensions) or Plus-only (some Functions targets).
checkout.liquid customizations for Shopify Plus storesScaffold an extension with Shopify CLI
# Inside an existing Shopify app directory
shopify app generate extension
# Choose: Checkout UI extension OR Shopify Function
# Name: my-checkout-extension
This creates an extensions/my-checkout-extension/ directory with src/index.tsx (UI) or src/index.ts (Function).
Build a Checkout UI extension
Checkout UI extensions use a React-like component API from @shopify/ui-extensions-react/checkout:
// extensions/order-upsell/src/index.tsx
import {
reactExtension,
useCartLines,
useApplyCartLinesChange,
useSettings,
BlockStack,
Button,
Image,
Text,
InlineStack,
Divider,
} from "@shopify/ui-extensions-react/checkout";
const CheckoutBlock = reactExtension(
"purchase.checkout.block.render",
() => <OrderUpsell />
);
export { CheckoutBlock };
function OrderUpsell() {
const cartLines = useCartLines();
const applyCartLinesChange = useApplyCartLinesChange();
const { upsell_variant_id: upsellVariantId } = useSettings();
// Only show upsell if a variant is configured and cart doesn't already contain it
if (!upsellVariantId) return null;
const alreadyInCart = cartLines.some(
(line) => line.merchandise.id === upsellVariantId
);
if (alreadyInCart) return null;
const handleAddUpsell = async () => {
await applyCartLinesChange({
type: "addCartLine",
merchandiseId: upsellVariantId,
quantity: 1,
});
};
return (
<BlockStack spacing="base">
<Divider />
<InlineStack blockAlignment="center" spacing="base">
<Image source="https://cdn.shopify.com/s/files/..." aspectRatio={1} />
<BlockStack>
<Text emphasis="bold">Add a gift bag for $3.99</Text>
<Text appearance="subdued">Beautiful packaging for your order</Text>
</BlockStack>
<Button onPress={handleAddUpsell}>Add</Button>
</InlineStack>
</BlockStack>
);
}
Configure the extension in shopify.extension.toml
api_version = "2025-01"
[[extensions]]
type = "ui_extension"
name = "Order Upsell"
handle = "order-upsell"
[[extensions.targeting]]
module = "./src/index.tsx"
target = "purchase.checkout.block.render"
[extensions.settings]
[[extensions.settings.fields]]
key = "upsell_variant_id"
type = "variant_reference"
name = "Upsell Product Variant"
Build a Shopify Function for custom discounts
Shopify Functions compile to WebAssembly. Use Rust or JavaScript:
// extensions/volume-discount/src/index.ts
import type {
RunInput,
FunctionRunResult,
CartLineInput,
} from "../generated/api";
const NO_CHANGES: FunctionRunResult = { discounts: [], discountApplicationStrategy: "FIRST" };
export function run(input: RunInput): FunctionRunResult {
const { cart } = input;
// Calculate total quantity across all lines
const totalQuantity = cart.lines.reduce(
(sum, line) => sum + line.quantity,
0
);
// Tiered volume discount
let discountPercent = 0;
if (totalQuantity >= 20) discountPercent = 20;
else if (totalQuantity >= 10) discountPercent = 10;
else if (totalQuantity >= 5) discountPercent = 5;
if (discountPercent === 0) return NO_CHANGES;
return {
discounts: [
{
value: {
percentage: { value: discountPercent.toString() },
},
targets: [{ orderSubtotal: { excludedVariantIds: [] } }],
message: `${discountPercent}% volume discount (${totalQuantity} items)`,
},
],
discountApplicationStrategy: "FIRST",
};
}
Function shopify.extension.toml:
api_version = "2025-01"
[[extensions]]
type = "function"
name = "Volume Discount"
handle = "volume-discount"
runtime = "javascript"
[[extensions.input.variables]]
name = "cart"
type = "Cart"
[extensions.build]
command = "npm run build"
path = "dist/index.wasm"
Test and deploy extensions
# Run local dev preview (UI extension hot-reloads in checkout)
shopify app dev
# Open the checkout preview URL shown in terminal
# Deploy all extensions to Shopify
shopify app deploy
After deploying, go to Admin → Checkout → Customize to add the UI extension block to a checkout template. Functions are activated by creating a discount with the function from Admin → Discounts.
import {
reactExtension,
TextField,
useApplyMetafieldsChange,
useMetafield,
BlockStack,
Text,
} from "@shopify/ui-extensions-react/checkout";
export default reactExtension(
"purchase.checkout.shipping-option-list.render-after",
() => <GiftMessage />
);
function GiftMessage() {
const giftMessage = useMetafield({ namespace: "custom", key: "gift_message" });
const applyMetafieldsChange = useApplyMetafieldsChange();
return (
<BlockStack spacing="tight">
<Text emphasis="bold">Gift message (optional)</Text>
<TextField
label="Message"
value={giftMessage?.value ?? ""}
multiline={3}
onChange={(value) =>
applyMetafieldsChange({
type: "updateMetafield",
namespace: "custom",
key: "gift_message",
valueType: "string",
value,
})
}
/>
</BlockStack>
);
}
// extensions/payment-customization/src/index.ts
import type { RunInput, FunctionRunResult } from "../generated/api";
export function run(input: RunInput): FunctionRunResult {
const country = input.cart.buyerIdentity?.countryCode;
// Hide "Cash on Delivery" for non-domestic orders
const hideOperations = input.paymentMethods
.filter((pm) => pm.name.toLowerCase().includes("cash on delivery") && country !== "US")
.map((pm) => ({
hide: { paymentMethodId: pm.id },
}));
return { operations: hideOperations };
}
purchase.checkout.block.render target for maximum placement flexibility — merchants can drag the block anywhere in the checkout editoruseSettings() hook to read merchant-configured values from the extension settings schema — avoids hardcoded IDs in extension code@shopify/ui-extensions-react/checkout components only — native HTML and other UI libraries are not available in the extension sandboxapi_version in shopify.extension.toml to access new APIs while keeping backward compatibilityuseApplyCartLinesChange is async; show a spinner while the mutation is in flight| Problem | Solution |
|---------|----------|
| Extension not appearing in checkout editor | Ensure the extension is deployed (shopify app deploy) and the correct checkout template is selected in Admin → Checkout |
| Function returns FUNCTION_EXECUTION_TIMEOUT | Move configuration out of runtime logic into Function input metafields; avoid complex loops on large catalogs |
| useCartLines returns stale data after cart update | Use the returned promise from applyCartLinesChange to wait for checkout to re-evaluate before reading cart lines again |
| Extension crashes with "Cannot use browser APIs" | Remove document, window, localStorage references — the extension runs in a Worker sandbox |
| Discount Function not applying | Verify the Function-based discount is active in Admin → Discounts and the customer qualifies per any eligibility rules |
| Checkout UI extension settings not saving | Settings fields require handle values that match the keys referenced by useSettings() in the extension code |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion