Shopify's "Buy Again" Problem - Preserving Line Item Properties
For merchants who sell customizable products, the "Buy Again" button in Shopify's new Customer Account pages presents a significant challenge. When a customer reorders, any custom line item properties—like engravings, monograms, or special add-ons—are lost. This limitation, widely discussed in a Shopify Community thread, forces customers to re-enter their customizations or leaves merchants clueless as to the order specifics.
We've recently encountered the problem with one of our clients, here are all of the solutions to that issue.
The Core Problem
When a customer clicks the default "Buy Again" button, Shopify creates a new cart with the same product variants and quantities. However, it completely ignores the properties
attached to the original line items. As one community member put it, this makes the feature "fairly useless for products that required custom information."
"Buy Again" button in customer accounts screens works by utilizing cart permalinks which can be constructed with different properties. However, when it comes to line item properties, we can only add them to the first item in the cart.
Simplest solution - disable Buy again.
If the "Buy Again" feature is breaking a core part of your store's operations, the most straightforward and immediate solution is often the simplest: disable it.
A partially implemented or broken feature can cause more significant problems—like incorrect orders and customer support issues—than removing the feature altogether. For merchants facing this issue, Shopify has provided the option to hide the "Buy again" button.

To disable buy again button go to your store dashboard navigate to Settings > Checkout, click on Customize then in editor go to Settings and Scroll down to find Buy again button option
This serves as a crucial stopgap, preventing a negative customer experience while you work on a more permanent, custom solution.
Our goal was to create a new "Buy Again with Properties" button that would appear on the order status page for relevant orders. The development process was iterative, exploring several of Shopify's APIs to find the most effective approach.
Creating an alternative with Shopify UI Extensions
Initial Idea: Backend API Routes
Inspired by the community discussion, our first approach involved the customer account extension calling a custom backend API route within our app. This route would then use the Admin API to create a new cart with the correct properties and return a checkoutUrl
. This works, but it adds an extra layer of complexity by involving a server-side component for a task that could potentially be handled on the frontend.
A Simpler Path? Cart Permalinks
Next, we explored Cart Permalinks. This method allows you to construct a URL that, when clicked, directs the user to a pre-filled cart. Line item properties can be included by Base64 URL-encoding a JSON object.
While this method removes the need for a backend call, it has drawbacks:
- Limited to only 1 custom item: This method only allows for a single custom item to have added priorities.
- URL Length Limits: Complex products with many properties could result in URLs that are too long for some browsers.
- Encoding Complexity: Managing the Base64 encoding and URL formatting can be cumbersome.
- Limited Error Handling: It's more difficult to manage errors gracefully if the cart creation fails.
The Winning Solution: Direct Storefront API from the Extension
The most elegant solution came from leveraging the tools provided directly within the Customer Account UI extension framework.
The Storefront API is directly accessible from the extension using the query
function provided by the useApi()
hook. This allows us to create a new cart with all the necessary properties and get a checkoutUrl
in return, all within the frontend extension.
The Final Implementation
Here is a breakdown of the final, working code.
1. Scaffold a new UI Extension
Using shopify's cli scaffold a new extension.
shopify app generate extension
Select Customer Account UI
as extension type, name it and select the scaffolding (I recommend Typescript react). The generated extension configuration file should give what you need.
name = "buy-again-with-properties"
type = "ui_extension"
[[extensions.targeting]]
module = "./src/OrderStatusBlock.tsx"
target = "customer-account.order-status.block.render"
[extensions.capabilities]
api_access = true
network_access = true # Required for the query function
The target we want is customer-account.order-status.block.render
, but you can also consider adding it in payments placement.
2. Develop UI Extension (OrderStatusBlock.tsx
)
The component uses hooks provided by Shopify to access order data and execute GraphQL mutations.
useCartLines()
: This hook is the correct way to get the line items from the current order, including their custom attributes.useApi()
: This provides thequery
function to call the Storefront API, andnavigation
object to redirect once the cart is created
import {
reactExtension,
useCartLines,
Button,
BlockStack,
useApi,
} from "@shopify/ui-extensions-react/customer-account";
import { useState } from "react";
export default reactExtension(
"customer-account.order-status.block.render",
() => <Extension />,
);
const CART_CREATE_MUTATION = `
mutation cartCreate($input: CartInput!) {
cartCreate(input: $input) {
cart {
id
checkoutUrl
}
userErrors {
field
message
}
}
}
`;
function Extension() {
const lines = useCartLines();
const { query, navigation } = useApi();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleBuyAgain = async () => {
if (!lines?.length) return;
setIsLoading(true);
setError(null);
try {
// Prepare cart lines with attributes
const cartLines = lines.map((line: any) => ({
merchandiseId: line.merchandise.id,
quantity: line.quantity,
attributes: line.attributes || [],
}));
// Create cart using Storefront API
const result: any = await query(CART_CREATE_MUTATION, {
variables: {
input: {
lines: cartLines,
},
},
});
if (result.data?.cartCreate?.cart?.checkoutUrl) {
// Redirect to checkout
navigation.navigate(result.data.cartCreate.cart.checkoutUrl);
} else if (result.data?.cartCreate?.userErrors?.length > 0) {
setError(result.data.cartCreate.userErrors[0].message);
console.error(result.data.cartCreate.userErrors[0].message);
} else {
setError("Failed to create cart");
}
} catch (err) {
console.error("Error creating cart:", err);
setError("Failed to create cart. Please try again.");
} finally {
setIsLoading(false);
}
};
// Hide extension for empty orders
if (!lines?.length) return null;
return (
<BlockStack spacing="base">
<Button
kind="secondary"
loading={isLoading}
onPress={handleBuyAgain}
disabled={isLoading}
>
Buy again
</Button>
</BlockStack>
);
}
Why This Approach Works Best
- Security and Reliability: It uses Shopify's authenticated Storefront API, which is more secure and reliable than constructing URLs manually.
- No
window
Object: The solution avoids using thewindow
object for navigation (e.g.,window.open()
), which is not permitted in Customer Account UI extensions. Instead, it uses theto
prop on theButton
component for navigation. - Superior User Experience: The component provides clear loading states and quick redirect. Once the cart is created, the button cleverly transforms into a direct link to the checkout, providing a clean two-step process for the user.
- No Backend Needed: The entire logic is self-contained within the frontend extension, simplifying the architecture of the Shopify app and reducing maintenance.
- What can be improved? The drawback is that we can't redirect to the storefront in this example, just the checkout. However generated cart in this case is a 1-click experience - shipping details are already provided for the customer!
Conclusion
Solving the "Buy Again" problem for customizable products is crucial for a positive customer experience and for your sanity.
If you need a custom Shopify app fine-tuned for your needs, reach out to us today.
By leveraging the power of Customer Account UI Extensions and the direct Storefront API access they provide, we can build a seamless, reliable, and user-friendly solution that correctly preserves all line item properties.
This approach not only fixes the immediate issue but also aligns with Shopify's modern development practices, ensuring a robust and future-proof implementation.