
Shopify Checkout Extensions
- 60 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Customize Shopify checkout with UI extensions for upsells and custom fields, plus Shopify Functions for serverless discount and shipping logic.
About
Extends Shopify checkout using UI extensions for upsells and custom fields and Shopify Functions for serverless discount and shipping logic. A developer uses it to modify checkout behavior on Shopify Plus.
- Checkout UI extensions for upsells and custom fields
- Shopify Functions for serverless discount and shipping logic
Shopify Checkout Extensions by the numbers
- 60 all-time installs (skills.sh)
- Ranked #1,212 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shopify-checkout-extensionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 60 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Customize Shopify checkout with UI extensions for upsells and custom fields, plus Shopify Functions for serverless discount and shipping logic.
Files
Shopify Checkout Extensions
Overview
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).
When to Use This Skill
- When adding custom UI blocks to checkout (upsells, trust badges, gift message fields, warranty options)
- When implementing custom discount logic beyond native Shopify discount rules (e.g., tiered discounts, B2B pricing)
- When creating custom shipping method filtering or renaming based on cart contents
- When building payment customization to hide/rename payment methods for specific customers
- When validating order contents before checkout completes (e.g., quantity limits, region restrictions)
- When replacing deprecated
checkout.liquidcustomizations for Shopify Plus stores
Core Instructions
1. Scaffold 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-extensionThis creates an extensions/my-checkout-extension/ directory with src/index.tsx (UI) or src/index.ts (Function).
2. 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>
);
}3. 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"4. 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"5. 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 deployAfter 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.
Examples
Gift message field using useApplyMetafieldsChange
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>
);
}Payment customization Function (hide cash on delivery for international orders)
// 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 };
}Best Practices
- Use the `purchase.checkout.block.render` target for maximum placement flexibility — merchants can drag the block anywhere in the checkout editor
- Keep Function execution under 5ms — Shopify enforces a strict execution time limit; avoid network calls inside Functions (use metafields or Function input variables for configuration)
- Use `useSettings()` hook to read merchant-configured values from the extension settings schema — avoids hardcoded IDs in extension code
- Never read DOM or use browser APIs in UI extensions — they run in a sandboxed Worker environment without DOM access
- Use `@shopify/ui-extensions-react/checkout` components only — native HTML and other UI libraries are not available in the extension sandbox
- Test payment and shipping Functions with real checkout sessions — the local dev preview only works for UI extensions; Functions need to be deployed to test
- Version-pin your extension API version — increment the
api_versioninshopify.extension.tomlto access new APIs while keeping backward compatibility - Handle async operations with loading states —
useApplyCartLinesChangeis async; show a spinner while the mutation is in flight
Common Pitfalls
| 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 |
Related Skills
- @shopify-app-development
- @shopify-storefront-api
- @shopify-metafields
- @checkout-flow-optimization
- @shopify-admin-api
{
"context": "Tests whether the agent correctly uses Shopify's metafield hooks for reading and writing checkout data, imports from the correct Shopify extension library, registers the extension against a valid checkout target, and produces a properly configured TOML file.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Correct import source",
"max_score": 10,
"description": "All hooks, components, and `reactExtension` are imported exclusively from `@shopify/ui-extensions-react/checkout` — no imports from `react-dom`, native HTML form elements, or other UI libraries"
},
{
"name": "useApplyMetafieldsChange for saving",
"max_score": 15,
"description": "Uses the `useApplyMetafieldsChange` hook to persist the note text — does NOT use `useApplyCartLinesChange`, `fetch`, or `localStorage` for this purpose"
},
{
"name": "useMetafield for reading",
"max_score": 15,
"description": "Uses the `useMetafield` hook (with a namespace and key) to read the existing note value for pre-population — does NOT derive the value from local state alone"
},
{
"name": "updateMetafield operation type",
"max_score": 10,
"description": "Calls `applyMetafieldsChange` with `type: \"updateMetafield\"` and includes `namespace`, `key`, `valueType`, and `value` fields"
},
{
"name": "No DOM/browser APIs",
"max_score": 8,
"description": "Does NOT reference `document`, `window`, `localStorage`, `sessionStorage`, or other browser/DOM globals in the extension code"
},
{
"name": "reactExtension entry point",
"max_score": 8,
"description": "Uses `reactExtension()` as the exported entry point wrapping the component"
},
{
"name": "Valid checkout target in source",
"max_score": 8,
"description": "Registers against a valid checkout extension target string (e.g. `purchase.checkout.shipping-option-list.render-after`, `purchase.checkout.block.render`, or another documented target) — not an invented string"
},
{
"name": "TOML api_version present",
"max_score": 8,
"description": "The `shopify.extension.toml` includes an `api_version` field with a version string"
},
{
"name": "TOML type ui_extension",
"max_score": 8,
"description": "The `shopify.extension.toml` sets `type = \"ui_extension\"` (not `function` or other)"
},
{
"name": "TOML target matches source",
"max_score": 10,
"description": "The `target` value in `[[extensions.targeting]]` in the TOML matches the target string passed to `reactExtension()` in the source file"
}
]
}
Order Notes Checkout Extension
Problem/Feature Description
A gifting marketplace wants to let customers add a personal message to their order during checkout. The message should be saved alongside the order so that the fulfillment team can print it on the packing slip. Customers who return to the checkout after navigating away should see their previously entered message still populated in the field.
The marketplace uses Shopify and needs a Checkout UI extension that renders a text input in the checkout flow, after the shipping options section. Whatever the customer types must be persisted to the order using Shopify's standard persistence mechanism for extensions, and it must be pre-populated on re-render if data was already entered.
The team wants this built as a standalone extension with its configuration file, ready to be added to an existing Shopify app.
Output Specification
Produce the following files:
extensions/order-notes/src/index.tsx— the extension source codeextensions/order-notes/shopify.extension.toml— the extension configuration
Do not create a full Shopify app scaffold — just these two files as if they were part of an existing app.
{
"context": "Tests whether the agent builds a Checkout UI extension correctly using the Shopify-specific component library, registers it with the right target and entry point pattern, exposes merchant configuration via settings (not hardcoded), handles the async cart mutation with a loading state, and configures the TOML with version-pinned API and matching settings keys.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Correct import source",
"max_score": 12,
"description": "Imports components and hooks exclusively from `@shopify/ui-extensions-react/checkout`, with no imports from `react-dom`, native HTML elements used directly, or other UI libraries"
},
{
"name": "reactExtension entry point",
"max_score": 8,
"description": "Uses `reactExtension()` as the exported entry point (not a plain React component export or other pattern)"
},
{
"name": "purchase.checkout.block.render target",
"max_score": 10,
"description": "Registers the extension against the `purchase.checkout.block.render` target (not a fixed-position target like `purchase.checkout.contact.render`)"
},
{
"name": "useSettings for variant ID",
"max_score": 12,
"description": "Reads the warranty product variant ID via `useSettings()` rather than hardcoding it as a literal string in the source code"
},
{
"name": "No DOM/browser APIs",
"max_score": 8,
"description": "Does NOT reference `document`, `window`, `localStorage`, or any other browser/DOM globals anywhere in the extension code"
},
{
"name": "Async loading state",
"max_score": 12,
"description": "Tracks a loading/pending state while `applyCartLinesChange` (or equivalent) is in flight, and uses it to disable the button or show a spinner during the operation"
},
{
"name": "Await cart mutation",
"max_score": 8,
"description": "Awaits the promise returned by `applyCartLinesChange` before treating the operation as complete (does not fire-and-forget)"
},
{
"name": "TOML api_version set",
"max_score": 8,
"description": "The `shopify.extension.toml` includes an `api_version` field (e.g. `api_version = \"2025-01\"`) rather than omitting it"
},
{
"name": "TOML settings field with handle",
"max_score": 12,
"description": "The `shopify.extension.toml` defines a settings field with a `key` (handle) that matches the key accessed via `useSettings()` in the TypeScript code"
},
{
"name": "Already-in-cart guard",
"max_score": 10,
"description": "Uses `useCartLines()` to check whether the warranty variant is already present in the cart and conditionally hides the upsell block"
}
]
}
Warranty Upsell Block for Checkout
Problem/Feature Description
A Shopify app agency is building a warranty upsell app for e-commerce merchants. The app needs to display a warranty add-on offer inside the Shopify checkout — merchants want to offer customers an optional warranty product alongside their main purchase. Critically, different merchants sell different warranty products, so the specific warranty product variant must be configurable per merchant through the Shopify Admin rather than being baked into the app's code.
The team needs a Checkout UI extension that renders the warranty upsell offer. When the customer clicks to add the warranty, the extension should add it to the cart. The UI must not flicker or allow double-clicks during the add operation. The extension should only display the offer when the warranty item is not already in the cart.
Output Specification
Produce the following files representing the extension source code and configuration:
extensions/warranty-upsell/src/index.tsx— the React extension source codeextensions/warranty-upsell/shopify.extension.toml— the extension configuration file
Do not create a full Shopify app scaffold — just write these two files as if they were part of an existing app.
{
"context": "Tests whether the agent correctly builds a Shopify Function for discounts, respects the WASM compilation constraints (no network calls, fast execution), configures the TOML accurately with the function type, runtime, and build output path, and documents the correct activation path via Admin Discounts.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Function TOML type",
"max_score": 8,
"description": "The `shopify.extension.toml` sets `type = \"function\"` (not `type = \"ui_extension\"` or missing)"
},
{
"name": "Runtime declared",
"max_score": 8,
"description": "The `shopify.extension.toml` includes a `runtime` field set to `\"javascript\"` or `\"rust\"`"
},
{
"name": "WASM output path",
"max_score": 10,
"description": "The `[extensions.build]` section in TOML includes `path = \"dist/index.wasm\"` as the build output"
},
{
"name": "No network calls in Function",
"max_score": 12,
"description": "The Function source code (`index.ts`) does NOT contain any `fetch()`, HTTP client calls, or other network I/O inside the `run()` function"
},
{
"name": "Configuration via input variables",
"max_score": 10,
"description": "Discount thresholds or configuration values are either hardcoded constants or derived from Function input (e.g. `input.cart` metafields / input variables) — NOT fetched externally at runtime"
},
{
"name": "FunctionRunResult discounts field",
"max_score": 8,
"description": "The `run()` function returns an object with a `discounts` array containing at least one discount entry with a `value.percentage` or `value.fixedAmount` field"
},
{
"name": "discountApplicationStrategy present",
"max_score": 8,
"description": "The returned `FunctionRunResult` includes the `discountApplicationStrategy` field (e.g. `\"FIRST\"` or `\"MAXIMUM\"`)"
},
{
"name": "No-op return for zero discount",
"max_score": 8,
"description": "When no discount tier is reached, the Function returns early with an empty discounts array (avoids unnecessary processing)"
},
{
"name": "Activation via Admin Discounts",
"max_score": 14,
"description": "DEPLOYMENT.md states that after deployment, the Function is activated from Admin → Discounts (NOT from Admin → Checkout or Checkout editor)"
},
{
"name": "Deploy command documented",
"max_score": 8,
"description": "DEPLOYMENT.md includes `shopify app deploy` as the deployment command"
},
{
"name": "Testing note for Functions",
"max_score": 6,
"description": "DEPLOYMENT.md notes that Functions cannot be fully tested with local dev preview and require deployment to a real store for end-to-end testing"
}
]
}
Tiered B2B Discount Function
Problem/Feature Description
A B2B wholesale supplier has been using Shopify Plus and wants to implement automatic tiered pricing for bulk orders. Their requirement is simple: buyers who add 5 or more units to their cart get a 5% discount, 10+ units get 10%, and 20+ units get 15%. Native Shopify discount rules can't handle quantity-based tiers automatically — each order needs to be evaluated at checkout.
The engineering team has decided to implement this as a Shopify Function so discounts apply instantly during checkout without any manual coupon codes. The Function must be production-ready: it needs to perform well within Shopify's runtime constraints and be configured correctly so it can be deployed and activated. The team is unfamiliar with the Shopify Functions runtime and wants clear documentation of the deployment and activation steps alongside the code.
Output Specification
Produce the following files:
extensions/b2b-tiered-discount/src/index.ts— the Function source code in TypeScript/JavaScriptextensions/b2b-tiered-discount/shopify.extension.toml— the Function configurationDEPLOYMENT.md— a brief document describing the steps required to deploy this Function and make it active on a store, including what admin section to use and what action to take after deployment
Do not create a full Shopify app scaffold — just these three files as if they were part of an existing app.
{
"name": "finsi/shopify-checkout-extensions",
"version": "0.1.0",
"summary": "Checkout UI extensions and Shopify Functions for custom logic",
"skills": {
"shopify-checkout-extensions": {
"path": "SKILL.md"
}
}
}