checkout-customization
Skill48 starsChanged 9 months agoMITView on GitHub
Customize Shopify checkout with UI extensions and functions. Use this skill for building checkout UI extensions, adding custom fields, implementing payment customizations, creating post-purchase experiences, and extending customer accounts. Covers Checkout UI Extensions API and checkout branding.
In this repository7 other files it gives its agents.
- api-graphqlSkill
- app-developmentSkill
- cli-toolsSkill
- headless-hydrogenSkill
- liquid-templatingSkill
- shopify-functionsSkill
- theme-developmentSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 4 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 16
Use this skill when:
- Distinctive: line 31
Performant: - Run in a sandboxed environment
- Distinctive: line 473
Add your extension block
- Distinctive: line 480
Support localization: - Use translation files
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Checkout Customization
When to use this skill
Use this skill when:
- Building checkout UI extensions
- Adding custom fields to checkout
- Implementing payment customizations
- Creating post-purchase upsells
- Extending customer account pages
- Customizing the checkout experience
- Adding custom validation logic
Overview
Checkout customization uses extensions that are:
- Upgrade-safe - Work with Shop Pay and new features
- Performant - Run in a sandboxed environment
- Secure - Limited access for security
Extension Types
| Type | Description |
| Checkout UI | Add custom UI to checkout |
| Post-purchase | Upsells after order |
| Customer Accounts | Extend account pages |
| Thank You | Customize order confirmation |
| Order Status | Extend order tracking |
Getting Started
1. Create Checkout Extension
# In an existing app
shopify app generate extension
# Select: Checkout UI2. Extension Structure
extensions/
└── checkout-ui/
├── src/
│ └── Checkout.jsx
├── locales/
│ └── en.default.json
└── shopify.extension.toml3. Configuration
# shopify.extension.toml
api_version = "2025-01"
[[extensions]]
type = "ui_extension"
name = "Custom Checkout"
handle = "custom-checkout"
[[extensions.targeting]]
module = "./src/Checkout.jsx"
target = "purchase.checkout.block.render"Checkout UI Extensions
Basic Extension
// src/Checkout.jsx
import {
reactExtension,
Banner,
useApi,
useTranslate,
} from "@shopify/ui-extensions-react/checkout";
export default reactExtension("purchase.checkout.block.render", () => (
<Extension />
));
function Extension() {
const translate = useTranslate();
return (
<Banner title={translate("welcomeMessage")}>
Thank you for shopping with us!
</Banner>
);
}Extension Targets
Common checkout targets:
// Before shipping options
"purchase.checkout.shipping-option-list.render-before";
// After shipping options
"purchase.checkout.shipping-option-list.render-after";
// Payment method
"purchase.checkout.payment-method-list.render-before";
// Order summary
"purchase.checkout.cart-line-list.render-after";
// Static render (header/footer)
"purchase.checkout.header.render-after";
"purchase.checkout.footer.render-after";
// Block render (anywhere in checkout)
"purchase.checkout.block.render";
// Delivery address
"purchase.checkout.delivery-address.render-before";Using Checkout Data
import {
useCartLines,
useTotalAmount,
useShippingAddress,
useBuyerJourney,
useCustomer,
useDiscountCodes,
} from "@shopify/ui-extensions-react/checkout";
function Extension() {
const cartLines = useCartLines();
const totalAmount = useTotalAmount();
const shippingAddress = useShippingAddress();
const customer = useCustomer();
const discountCodes = useDiscountCodes();
const total = totalAmount?.amount;
const itemCount = cartLines.reduce((sum, line) => sum + line.quantity, 0);
return (
<BlockStack>
<Text>Items in cart: {itemCount}</Text>
<Text>Total: ${total}</Text>
{customer && <Text>Welcome back, {customer.firstName}!</Text>}
</BlockStack>
);
}UI Components
import {
Banner,
BlockStack,
InlineStack,
Text,
Button,
Checkbox,
TextField,
Select,
Image,
Divider,
Heading,
Link,
View,
Grid,
Icon,
} from "@shopify/ui-extensions-react/checkout";
function Extension() {
const [checked, setChecked] = useState(false);
const [note, setNote] = useState("");
return (
<BlockStack spacing="base">
<Heading level={2}>Gift Options</Heading>
<Checkbox checked={checked} onChange={setChecked}>
This is a gift
</Checkbox>
{checked && (
<TextField
label="Gift message"
value={note}
onChange={setNote}
multiline
/>
)}
<InlineStack spacing="tight">
<Button kind="secondary" onPress={() => {}}>
Cancel
</Button>
<Button onPress={() => {}}>Save</Button>
</InlineStack>
</BlockStack>
);
}Custom Fields with Metafields
import {
useApplyMetafieldsChange,
useMetafield,
} from "@shopify/ui-extensions-react/checkout";
function GiftMessage() {
const [message, setMessage] = useState("");
const applyMetafieldsChange = useApplyMetafieldsChange();
const handleChange = async (value) => {
setMessage(value);
await applyMetafieldsChange({
type: "updateMetafield",
namespace: "custom",
key: "gift_message",
valueType: "string",
value,
});
};
return (
<TextField label="Gift message" value={message} onChange={handleChange} />
);
}Buyer Journey Intercept
Block checkout progression with validation:
import { useBuyerJourneyIntercept } from "@shopify/ui-extensions-react/checkout";
function AgeVerification() {
const [verified, setVerified] = useState(false);
useBuyerJourneyIntercept(({ canBlockProgress }) => {
if (!verified && canBlockProgress) {
return {
behavior: "block",
reason: "Age verification required",
errors: [
{
message: "Please verify your age to continue",
},
],
};
}
return { behavior: "allow" };
});
return (
<Checkbox checked={verified} onChange={setVerified}>
I confirm I am 21 or older
</Checkbox>
);
}Post-Purchase Extensions
Create Post-Purchase Extension
shopify app generate extension
# Select: Post-purchase UIPost-Purchase Upsell
// src/PostPurchase.jsx
import {
extend,
render,
useExtensionInput,
BlockStack,
Button,
Text,
Image,
Heading,
CalloutBanner,
Layout,
TextContainer,
} from "@shopify/post-purchase-ui-extensions-react";
extend("Checkout::PostPurchase::ShouldRender", async ({ storage }) => {
// Decide whether to show post-purchase
const upsellProduct = await fetchUpsellProduct();
await storage.update({ upsellProduct });
return { render: true };
});
render("Checkout::PostPurchase::Render", () => <App />);
function App() {
const { storage, done, calculateChangeset, applyChangeset } =
useExtensionInput();
const { upsellProduct } = storage.initialData;
const handleAccept = async () => {
const changeset = await calculateChangeset({
changes: [
{
type: "add_variant",
variantId: upsellProduct.variantId,
quantity: 1,
},
],
});
await applyChangeset(changeset.token);
done();
};
const handleDecline = () => {
done();
};
return (
<BlockStack spacing="loose">
<CalloutBanner title="Special Offer!">
Add this item to your order at 20% off
</CalloutBanner>
<Layout
media={[
{ viewportSize: "small", sizes: [1, 0, 1], maxInlineSize: 0.9 },
{ viewportSize: "medium", sizes: [532, 0, 1], maxInlineSize: 420 },
{ viewportSize: "large", sizes: [560, 38, 340] },
]}
>
<Image source={upsellProduct.imageUrl} />
<View />
<BlockStack spacing="tight">
<Heading>{upsellProduct.title}</Heading>
<TextContainer>
<Text size="medium">${upsellProduct.price} (20% off)</Text>
</TextContainer>
<Button onPress={handleAccept}>Add to Order</Button>
<Button onPress={handleDecline} plain>
No thanks
</Button>
</BlockStack>
</Layout>
</BlockStack>
);
}Customer Account Extensions
// Extend customer account pages
import {
reactExtension,
Page,
Card,
BlockStack,
Text,
} from "@shopify/ui-extensions-react/customer-account";
export default reactExtension("customer-account.page.render", () => (
<CustomAccountPage />
));
function CustomAccountPage() {
return (
<Page title="Rewards">
<Card padding>
<BlockStack spacing="loose">
<Text size="large">Your Points: 500</Text>
<Text>Earn points with every purchase!</Text>
</BlockStack>
</Card>
</Page>
);
}Localization
// locales/en.default.json
{
"welcomeMessage": "Welcome to checkout",
"giftLabel": "Gift options",
"verifyAge": "I confirm I am 21 or older"
}
// locales/fr.json
{
"welcomeMessage": "Bienvenue au paiement",
"giftLabel": "Options cadeau",
"verifyAge": "Je confirme avoir 21 ans ou plus"
}import { useTranslate } from "@shopify/ui-extensions-react/checkout";
function Extension() {
const translate = useTranslate();
return <Text>{translate("welcomeMessage")}</Text>;
}Network Requests
import { useApi } from "@shopify/ui-extensions-react/checkout";
function Extension() {
const { sessionToken } = useApi();
const fetchData = async () => {
const token = await sessionToken.get();
const response = await fetch("https://your-app.com/api/data", {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
};
// Use in useEffect or event handler
}Testing
Local Development
# Start app with preview
shopify app dev
# Extensions will load in checkout previewTesting in Checkout Editor
- Go to Shopify admin > Settings > Checkout
- Click "Customize"
- Add your extension block
- Preview changes
Best Practices
- Keep extensions fast - Minimize API calls
- Handle errors gracefully - Show user-friendly messages
- Support localization - Use translation files
- Test on mobile - Checkout is often mobile
- Follow design guidelines - Match Shopify's checkout style
- Use progressive enhancement - Graceful degradation
Resources
- Checkout UI Extensions
- Checkout UI Components
- Post-Purchase Extensions
- Customer Account Extensions
- Extension Targets Reference
For backend logic, see the shopify-functions skill.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

