Version 2025-07 is the last API version to support React-based UI components. Later versions use web components, native UI elements with built-in accessibility, better performance, and consistent styling with Shopify's design system. Check out the upgrade guide to upgrade your extension.
Abandoned checkouts
Abandoned checkout pages display information about checkouts where customers didn't complete their purchase. Extensions on these pages help merchants recover lost sales or analyze abandonment patterns.
Anchor to Use casesUse cases
- Recovery workflows: Launch automated or manual follow-up campaigns to re-engage customers who abandoned their checkout.
- Customer insights: Display additional context about the customer's browsing history, preferences, or engagement patterns to inform recovery strategies.
- Inventory alerts: Show real-time stock availability for items in the abandoned cart to help merchants prioritize follow-up.
- Third-party integrations: Sync abandoned checkout data with external marketing, CRM, or analytics platforms.
- Custom analytics: Display specialized metrics, conversion predictions, or abandonment reasons from your app's analysis.

Anchor to Abandoned checkouts targetsAbandoned checkouts targets
Use action and block targets to extend the abandoned checkout details page with workflows and contextual information that help merchants recover sales.
Action targets open as modal overlays from the More actions menu, while block targets display as inline cards. The examples demonstrate fetching data from Shopify's direct API or your app's backend.
Anchor to Abandoned checkout details action ,[object Object]Abandoned checkout details action target
admin.abandoned-checkout-details.action.render
Renders an admin action extension on the abandoned checkout details page. Merchants can access this extension from the More actions menu. Use this target to provide workflows that operate on the abandoned checkout data, such as sending custom recovery emails, creating follow-up tasks, or syncing with external systems.
Extensions at this target can access information about the abandoned checkout through the data property in the Action Extension API. The action renders in a modal overlay, providing space for multi-step workflows, forms, and confirmations.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Examples
Description
Add an action extension that helps merchants send a personalized recovery email to the customer. This example shows how to create a modal workflow with form inputs and primary actions that operate on the abandoned checkout data.
React
import React from 'react'; import {useState} from 'react'; import { reactExtension, useApi, AdminAction, Banner, Section, TextArea, Button, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.abandoned-checkout-details.action.render'; export default reactExtension(TARGET, () => <App />); function App() { const {data, close} = useApi(TARGET); const [loading, setLoading] = useState(false); const [message, setMessage] = useState(''); const [success, setSuccess] = useState(false); const [error, setError] = useState(false); const handleSendRecovery = async () => { setLoading(true); setSuccess(false); setError(false); const checkoutId = data.selected[0].id; try { // Send recovery email through your app's backend const response = await fetch('https://your-app.com/api/send-recovery', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ checkoutId, customMessage: message, }), }); if (response.ok) { setSuccess(true); // Close modal after a brief delay to show success message close(); } else { setError(true); } } catch (err) { setError(true); } finally { setLoading(false); } }; return ( <AdminAction title="Send Recovery Email" primaryAction={ <Button onPress={handleSendRecovery} disabled={loading || success}> {loading ? 'Sending...' : 'Send Recovery Email'} </Button> } secondaryAction={<Button onPress={close}>Cancel</Button>} > {success && ( <Banner tone="success"> Recovery email sent successfully! </Banner> )} {error && ( <Banner tone="critical"> Failed to send recovery email. Please try again. </Banner> )} <Section heading="Customize recovery message"> <TextArea label="Custom message" value={message} onChange={setMessage} helpText="Add a personalized message to encourage the customer to complete their purchase" rows={4} /> </Section> </AdminAction> ); }TS
import { extension, AdminAction, Banner, Section, TextArea, Button, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.action.render', (root, api) => { let loading = false; let message = ''; let success = false; let error = false; const handleSendRecovery = async () => { loading = true; success = false; error = false; updateUI(); const checkoutId = api.data.selected[0].id; try { // Send recovery email through your app's backend const response = await fetch('https://your-app.com/api/send-recovery', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ checkoutId, customMessage: message, }), }); if (response.ok) { success = true; updateUI(); // Close modal after a brief delay to show success message api.close(); } else { error = true; updateUI(); } } catch (err) { error = true; updateUI(); } finally { loading = false; updateUI(); } }; const primaryAction = root.createFragment(); const secondaryAction = root.createFragment(); const content = root.createFragment(); const updateUI = () => { content.replaceChildren(); if (success) { content.appendChild( root.createComponent( Banner, {tone: 'success'}, 'Recovery email sent successfully!' ) ); } if (error) { content.appendChild( root.createComponent( Banner, {tone: 'critical'}, 'Failed to send recovery email. Please try again.' ) ); } const section = root.createComponent(Section, {heading: 'Customize recovery message'}); const textArea = root.createComponent(TextArea, { label: 'Custom message', value: message, onChange: (value) => { message = value; }, helpText: 'Add a personalized message to encourage the customer to complete their purchase', rows: 4, }); section.appendChild(textArea); content.appendChild(section); }; primaryAction.appendChild( root.createComponent( Button, { onPress: handleSendRecovery, disabled: loading || success, }, loading ? 'Sending...' : 'Send Recovery Email' ) ); secondaryAction.appendChild( root.createComponent(Button, {onPress: () => api.close()}, 'Cancel') ); updateUI(); const adminAction = root.createComponent( AdminAction, { title: 'Send Recovery Email', primaryAction, secondaryAction, } ); adminAction.appendChild(content); root.appendChild(adminAction); root.mount(); } );Description
Add an action extension that generates and applies a discount code to incentivize checkout completion. This example shows how to use the [GraphQL Admin API](/docs/api/admin-graphql) to fetch checkout details and create targeted discounts.
React
import React from 'react'; import {useState} from 'react'; import { reactExtension, useApi, AdminAction, Banner, Section, ChoiceList, Button, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.abandoned-checkout-details.action.render'; export default reactExtension(TARGET, () => <App />); function App() { const {data, close} = useApi(TARGET); const [loading, setLoading] = useState(false); const [discountPercent, setDiscountPercent] = useState(['10']); const [success, setSuccess] = useState(false); const [error, setError] = useState(false); const handleCreateDiscount = async () => { setLoading(true); setSuccess(false); setError(false); const checkoutId = data.selected[0].id; try { // Create discount code through your app's backend const response = await fetch('https://your-app.com/api/create-discount', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ checkoutId, percentOff: parseInt(discountPercent[0], 10), }), }); if (response.ok) { setSuccess(true); close(); } else { setError(true); } } catch (err) { setError(true); } finally { setLoading(false); } }; return ( <AdminAction title="Create Recovery Discount" primaryAction={ <Button onPress={handleCreateDiscount} disabled={loading || success}> {loading ? 'Creating...' : 'Create Discount Code'} </Button> } secondaryAction={<Button onPress={close}>Cancel</Button>} > {success && ( <Banner tone="success"> Discount code created and sent to customer! </Banner> )} {error && ( <Banner tone="critical"> Failed to create discount code. Please try again. </Banner> )} <Section heading="Discount settings"> <ChoiceList name="discount-percent" value={discountPercent} onChange={setDiscountPercent} choices={[ {label: '10% off', id: '10'}, {label: '15% off', id: '15'}, {label: '20% off', id: '20'}, ]} /> </Section> </AdminAction> ); }TS
import { extension, AdminAction, Banner, Section, ChoiceList, Button, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.action.render', (root, api) => { let loading = false; let discountPercent = ['10']; let success = false; let error = false; const handleCreateDiscount = async () => { loading = true; success = false; error = false; updateUI(); const checkoutId = api.data.selected[0].id; try { // Create discount code through your app's backend const response = await fetch('https://your-app.com/api/create-discount', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ checkoutId, percentOff: parseInt(discountPercent[0], 10), }), }); if (response.ok) { success = true; updateUI(); api.close(); } else { error = true; updateUI(); } } catch (err) { error = true; updateUI(); } finally { loading = false; updateUI(); } }; const primaryAction = root.createFragment(); const secondaryAction = root.createFragment(); const content = root.createFragment(); const updateUI = () => { content.replaceChildren(); if (success) { content.appendChild( root.createComponent( Banner, {tone: 'success'}, 'Discount code created and sent to customer!' ) ); } if (error) { content.appendChild( root.createComponent( Banner, {tone: 'critical'}, 'Failed to create discount code. Please try again.' ) ); } const section = root.createComponent(Section, {heading: 'Discount settings'}); const choiceList = root.createComponent(ChoiceList, { name: 'discount-percent', value: discountPercent, onChange: (value) => { discountPercent = value; }, choices: [ {label: '10% off', id: '10'}, {label: '15% off', id: '15'}, {label: '20% off', id: '20'}, ], }); section.appendChild(choiceList); content.appendChild(section); }; primaryAction.appendChild( root.createComponent( Button, { onPress: handleCreateDiscount, disabled: loading || success, }, loading ? 'Creating...' : 'Create Discount Code' ) ); secondaryAction.appendChild( root.createComponent(Button, {onPress: () => api.close()}, 'Cancel') ); updateUI(); const adminAction = root.createComponent( AdminAction, { title: 'Create Recovery Discount', primaryAction, secondaryAction, } ); adminAction.appendChild(content); root.appendChild(adminAction); root.mount(); } );
Anchor to Abandoned checkout details action (should render) ,[object Object]Abandoned checkout details action (should render) target
admin.abandoned-checkout-details.action.should-render
Controls the render state of an admin action extension on the abandoned checkout details page. Use this target to conditionally show or hide your action extension based on the abandoned checkout's properties, such as cart value, customer status, or time since abandonment.
This target returns a boolean value that determines whether the corresponding action extension appears in the More actions menu. The extension is evaluated each time the page loads.
Supported components
Available APIs
Supported components
Available APIs
Examples
Description
Conditionally display a recovery action only for abandoned checkouts exceeding a certain value. This example demonstrates how to use the `should-render` target to control extension visibility based on business logic.
React
import {extension} from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.action.should-render', async (root, api) => { const checkoutId = api.data.selected[0].id; try { // Fetch checkout details from GraphQL Admin API const response = await fetch( 'shopify:admin/api/graphql.json', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ query: ` query GetCheckout($id: ID!) { node(id: $id) { ... on Checkout { totalPriceV2 { amount } } } } `, variables: {id: checkoutId}, }), } ); const {data} = await response.json(); const totalAmount = parseFloat(data.node.totalPriceV2.amount); // Only show action for checkouts over $100 return {display: totalAmount > 100}; } catch (err) { console.error('Error fetching checkout:', err); return {display: false}; } } );TS
import {extension} from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.action.should-render', async (root, api) => { const checkoutId = api.data.selected[0].id; try { // Fetch checkout details from GraphQL Admin API const response = await fetch( 'shopify:admin/api/graphql.json', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ query: ` query GetCheckout($id: ID!) { node(id: $id) { ... on Checkout { totalPriceV2 { amount } } } } `, variables: {id: checkoutId}, }), } ); const {data} = await response.json(); const totalAmount = parseFloat(data.node.totalPriceV2.amount); // Only show action for checkouts over $100 return {display: totalAmount > 100}; } catch (err) { console.error('Error fetching checkout:', err); return {display: false}; } } );Description
Conditionally display the recovery action only for checkouts abandoned within the last 24 hours when recovery rates are highest. This example demonstrates time-based filtering using the [GraphQL Admin API](/docs/api/admin-graphql).
React
import {extension} from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.action.should-render', async (root, api) => { const checkoutId = api.data.selected[0].id; try { // Fetch checkout details including abandonment time const {data} = await api.query( ` query GetCheckout($id: ID!) { node(id: $id) { ... on Checkout { updatedAt } } } `, {variables: {id: checkoutId}} ); const updatedAt = new Date(data.node.updatedAt); const now = new Date(); const hoursSinceAbandonment = (now - updatedAt) / (1000 * 60 * 60); // Only show action for checkouts abandoned in the last 24 hours return {display: hoursSinceAbandonment < 24}; } catch (err) { console.error('Error fetching checkout:', err); return {display: false}; } } );TS
import {extension} from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.action.should-render', async (root, api) => { const checkoutId = api.data.selected[0].id; try { // Fetch checkout details including abandonment time const {data} = await api.query( ` query GetCheckout($id: ID!) { node(id: $id) { ... on Checkout { updatedAt } } } `, {variables: {id: checkoutId}} ); const updatedAt = new Date(data.node.updatedAt); const now = new Date(); const hoursSinceAbandonment = (now - updatedAt) / (1000 * 60 * 60); // Only show action for checkouts abandoned in the last 24 hours return {display: hoursSinceAbandonment < 24}; } catch (err) { console.error('Error fetching checkout:', err); return {display: false}; } } );
Anchor to Abandoned checkout details block ,[object Object]Abandoned checkout details block target
admin.abandoned-checkout-details.block.render
Renders an admin block extension inline on the abandoned checkout details page. Use this target to display contextual information, analytics, or status updates related to the abandoned checkout without requiring merchants to open a modal.
Extensions at this target appear as cards on the page and can show real-time data, insights, or quick actions. Blocks provide persistent visibility and are ideal for displaying information merchants need to see at a glance.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Examples
Description
Create a block extension that shows contextual insights about the abandoned checkout, such as customer engagement score, likelihood of recovery, and recommended actions. This example demonstrates how to present valuable information inline on the page.
React
import React from 'react'; import {useState, useEffect} from 'react'; import { reactExtension, useApi, AdminBlock, BlockStack, Box, Heading, InlineStack, Badge, Text, Divider, Button, ProgressIndicator, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.abandoned-checkout-details.block.render'; export default reactExtension(TARGET, () => <App />); function App() { const {data, navigation} = useApi(TARGET); const [insights, setInsights] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { const fetchInsights = async () => { const checkoutId = data.selected[0].id; try { // Fetch insights from your app's backend const response = await fetch( `https://your-app.com/api/checkout-insights?id=${checkoutId}` ); const data = await response.json(); setInsights(data); } catch (err) { console.error('Error fetching insights:', err); } finally { setLoading(false); } }; fetchInsights(); }, [data]); if (loading) { return ( <AdminBlock title="Recovery Insights"> <ProgressIndicator size="small-200" /> </AdminBlock> ); } if (!insights) { return ( <AdminBlock title="Recovery Insights"> <Text>Unable to load insights</Text> </AdminBlock> ); } return ( <AdminBlock title="Recovery Insights"> <BlockStack gap> <Box> <Heading>Recovery Likelihood</Heading> <InlineStack blockAlignment="center" gap> <Badge tone={insights.likelihood > 70 ? 'success' : 'warning'}> {insights.likelihood}% </Badge> <Text> Based on customer engagement patterns </Text> </InlineStack> </Box> <Divider /> <Box> <Heading>Customer Engagement</Heading> <Text> Last active: {insights.lastActive} </Text> <Text> Email open rate: {insights.emailOpenRate}% </Text> </Box> <Divider /> <Box> <Heading>Recommended Action</Heading> <Text>{insights.recommendation}</Text> <Button onPress={() => navigation.navigate('extension://send-recovery')} > Send Recovery Email </Button> </Box> </BlockStack> </AdminBlock> ); }TS
import { extension, AdminBlock, BlockStack, Box, Heading, InlineStack, Badge, Text, Button, ProgressIndicator, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.block.render', async (root, api) => { const checkoutId = api.data.selected[0].id; const adminBlock = root.createComponent(AdminBlock, {title: 'Recovery Insights'}); // Show loading state adminBlock.appendChild(root.createComponent(ProgressIndicator, {size: 'small-200'})); root.appendChild(adminBlock); root.mount(); try { // Fetch insights from your app's backend const response = await fetch( `https://your-app.com/api/checkout-insights?id=${checkoutId}` ); const insights = await response.json(); // Clear loading state and show insights adminBlock.replaceChildren(); const blockStack = root.createComponent(BlockStack, {gap: true}); // Recovery Likelihood section const likelihoodBox = root.createComponent(Box); likelihoodBox.appendChild(root.createComponent(Heading, {}, 'Recovery Likelihood')); const likelihoodStack = root.createComponent(InlineStack, { blockAlignment: 'center', gap: true, }); likelihoodStack.appendChild( root.createComponent( Badge, {tone: insights.likelihood > 70 ? 'success' : 'warning'}, `${insights.likelihood}%` ) ); likelihoodStack.appendChild( root.createComponent(Text, {}, 'Based on customer engagement patterns') ); likelihoodBox.appendChild(likelihoodStack); blockStack.appendChild(likelihoodBox); // Divider blockStack.appendChild(root.createComponent('Divider')); // Customer Engagement section const engagementBox = root.createComponent(Box); engagementBox.appendChild(root.createComponent(Heading, {}, 'Customer Engagement')); engagementBox.appendChild( root.createComponent(Text, {}, `Last active: ${insights.lastActive}`) ); engagementBox.appendChild( root.createComponent(Text, {}, `Email open rate: ${insights.emailOpenRate}%`) ); blockStack.appendChild(engagementBox); // Divider blockStack.appendChild(root.createComponent('Divider')); // Recommended Action section const actionBox = root.createComponent(Box); actionBox.appendChild(root.createComponent(Heading, {}, 'Recommended Action')); actionBox.appendChild(root.createComponent(Text, {}, insights.recommendation)); actionBox.appendChild( root.createComponent( Button, {onPress: () => api.navigation.navigate('extension://send-recovery')}, 'Send Recovery Email' ) ); blockStack.appendChild(actionBox); adminBlock.appendChild(blockStack); } catch (err) { console.error('Error fetching insights:', err); adminBlock.replaceChildren( root.createComponent(Text, {}, 'Unable to load insights') ); } } );Description
Create a block extension that shows the timeline of recovery attempts for the abandoned checkout. This example demonstrates how to present historical data that helps merchants avoid over-contacting customers.
React
import React from 'react'; import {useState, useEffect} from 'react'; import { reactExtension, useApi, AdminBlock, BlockStack, Box, InlineStack, Badge, Text, ProgressIndicator, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.abandoned-checkout-details.block.render'; export default reactExtension(TARGET, () => <App />); function App() { const {data} = useApi(TARGET); const [attempts, setAttempts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const fetchAttempts = async () => { const checkoutId = data.selected[0].id; try { // Fetch recovery attempts from your app's backend const response = await fetch( `https://your-app.com/api/recovery-attempts?checkoutId=${checkoutId}` ); const data = await response.json(); setAttempts(data.attempts || []); } catch (err) { console.error('Error fetching attempts:', err); } finally { setLoading(false); } }; fetchAttempts(); }, [data]); if (loading) { return ( <AdminBlock title="Recovery Attempts"> <ProgressIndicator size="small-200" /> </AdminBlock> ); } if (attempts.length === 0) { return ( <AdminBlock title="Recovery Attempts"> <Text>No recovery attempts yet</Text> </AdminBlock> ); } return ( <AdminBlock title="Recovery Attempts"> <BlockStack gap> {attempts.map((attempt) => ( <Box key={attempt.id}> <BlockStack gap> <InlineStack blockAlignment="center" gap> <Badge tone={attempt.status === 'sent' ? 'success' : 'warning'}> {attempt.type} </Badge> <Text>{attempt.date}</Text> </InlineStack> <Text>{attempt.description}</Text> </BlockStack> </Box> ))} </BlockStack> </AdminBlock> ); }TS
import { extension, AdminBlock, BlockStack, Box, InlineStack, Badge, Text, ProgressIndicator, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.abandoned-checkout-details.block.render', async (root, api) => { const checkoutId = api.data.selected[0].id; const adminBlock = root.createComponent(AdminBlock, {title: 'Recovery Attempts'}); // Show loading state adminBlock.appendChild(root.createComponent(ProgressIndicator, {size: 'small-200'})); root.appendChild(adminBlock); root.mount(); try { // Fetch recovery attempts from your app's backend const response = await fetch( `https://your-app.com/api/recovery-attempts?checkoutId=${checkoutId}` ); const data = await response.json(); const attempts = data.attempts || []; // Clear loading state adminBlock.replaceChildren(); if (attempts.length === 0) { adminBlock.appendChild( root.createComponent(Text, {}, 'No recovery attempts yet') ); } else { const blockStack = root.createComponent(BlockStack, {gap: true}); for (const attempt of attempts) { const box = root.createComponent(Box); const attemptStack = root.createComponent(BlockStack, {gap: true}); const inlineStack = root.createComponent(InlineStack, { blockAlignment: 'center', gap: true, }); inlineStack.appendChild( root.createComponent( Badge, {tone: attempt.status === 'sent' ? 'success' : 'warning'}, attempt.type ) ); inlineStack.appendChild( root.createComponent(Text, {}, attempt.date) ); attemptStack.appendChild(inlineStack); attemptStack.appendChild( root.createComponent(Text, {}, attempt.description) ); box.appendChild(attemptStack); blockStack.appendChild(box); } adminBlock.appendChild(blockStack); } } catch (err) { console.error('Error fetching attempts:', err); adminBlock.replaceChildren( root.createComponent(Text, {}, 'Unable to load recovery attempts') ); } } );
Anchor to Best practicesBest practices
- Prioritize high-value recovery opportunities: Use the
should-rendertarget to show recovery actions only for abandoned checkouts that meet specific criteria, such as cart value thresholds, returning customers, or carts with specific product types that warrant recovery efforts. - Display time-sensitive information: Show how long ago the checkout was abandoned in your block extensions to help merchants prioritize recent abandonments when recovery rates are typically higher.
- Enrich with customer context: Pull in additional customer data like past purchase history, email engagement rates, or loyalty status to help merchants personalize their recovery approach and gauge recovery likelihood.
- Account for inventory changes: When displaying abandoned cart contents, indicate if products are still in stock or if pricing has changed since abandonment, as this affects recovery strategy.
- Respect recovery fatigue: Consider tracking how many recovery attempts have already been made for a checkout to avoid over-contacting customers and provide this context to merchants in your extensions.
Anchor to LimitationsLimitations
- Single target per module: Each
[[extensions.targeting]]entry in your TOML configuration maps one target to one module file. - Checkout data retention: Abandoned checkouts are automatically removed when they're created more than three months ago, haven't been updated in one month, and have no associated transaction or order.
- Customer email limitations: Abandoned checkout data might not always include customer email addresses if the customer didn't provide one before abandoning. Your extensions should handle cases where contact information is incomplete or missing. The
customer,billingAddress, andshippingAddressfields are nullable. - Block target visibility: Block extensions must be manually added and pinned by merchants before they appear.
- Block collapse behavior: Returning
nullfrom a block extension collapses the block rather than removing it from the page. Blocks can't be fully hidden at runtime.