Do we have documentation to build Post Purchase Extensions In Typescript, because currently, the code added from the documentation ( Build a post-purchase product offer checkout extension ) is giving red squiggly lines
Hey @Namish_Kapoor
- at the moment, we donât have specific documentation on how to build Post Purchase extensions in Typescript (just our general tutorial here: Build a post-purchase product offer checkout extension ), but if you set up the extension using our CLI by running shopify app generate extensionor upon initialization (more info here), it should create a boilerplate typescript extension file:
There are some pre-built tips in the boilerplate notes that may help, but let me know if I can clarify anything or if youâre still encountering any blockers and Iâd be happy to take a look.
Thank you so much @Alan_G, for the info. Yes, I am slowly building my muscle memory to develop a post-purchase extension in Typescript. The boilerplate gives very minimal typescript, and I believe most of the types that are highlighted by the IDE are not being exported from @shopify/post-purchase-ui-extensions. Some of my concerns are listed below
Blocker:
-
I am building an extension-only app, and there is no support or documentation for building a post-purchase extension with it. Can you please share me the docs or any reference repo if available
-
I am facing CORS errors because I am trying to fetch dynamic product data in Checkout::PostPurchase::ShouldRender phase by making a GraphQL request
-
As there is no backend for an extension-only app, I have created my tiny backend for signing JWTs, and once I accept the offer and click on continue, I am not redirected to the checkout again for the offer; instead, I am redirected to the Thank you page with the product already added to the cart. Is this a desired behaviour in the development stores, or am I missing something?
Also, there are a few interesting questions that I would love your valuable feedback on
- What will happen if the user adds the upsell from the post-purchase extension to the cart, and before making a payment, he closes the tab? Will the order be incomplete in this case, or will the added upsell be excluded from the order after some time
- If I am not choosing the post-purchase extension, Can I build custom components on the thank-you page and somehow redirect the user to the checkout with the added product to the cart or something like that, which should be treated as a separate order and not a modification to the existing one? Is there anything like that supported for any extension
I would really appreciate it if you could resolve my queries above. Thank you, and looking forward to it ![]()
Hi again @Namish_Kapoor, happy to help out with this where I can.
The postâpurchase packages should come with type definitions. A common vanilla import is import {extend, BlockStack, Text} from '@shopify/post-purchase-ui-extensions'; and you should be able to reference something like extend('Checkout::PostPurchase::ShouldRender', () => ({render: true})); Thereâs a bit more info here:
That CORS issue is likely popping up since the Admin GraphQL API canât be called directly from the extension. You should be able to use the Checkout::PostPurchase::ShouldRender functionality to pull product information rather than using the Admin API though (bit more info here). I did a bit of testing using our sample code from the tutorial and hopefully Iâm looking at your code correctly there, but this is how youâd want to implement the query (youâd want to implement it as its own module (app/offer.server.ts per our example here):
export type OfferDiscount = {
value: number;
valueType: "percentage" | "fixed_amount";
title: string;
};
export type OfferChange = {
type: "add_variant";
variantID: number;
quantity: number;
discount: OfferDiscount;
};
export type Offer = {
id: number;
title: string;
productTitle: string;
productImageURL: string;
productDescription: string[];
changes: OfferChange[];
};
const OFFERS: Offer[] = [
{
id: 1,
title: "One time offer",
productTitle: "Featured Product",
productImageURL: "https://cdn.shopify.com/s/files/1/placeholder-image-url.jpg",
productDescription: [
"This product is a great addition to your purchase.",
],
changes: [
{
type: "add_variant",
variantID: id-here,
quantity: 1,
discount: {
value: 15,
valueType: "percentage",
title: "15% off",
},
},
],
},
];
export function getOffers(): Offer[] {
return OFFERS;
}
export function getSelectedOffer(offerId: number): Offer | undefined {
return OFFERS.find((offer) => offer.id === offerId);
}
export function getOffersForInitialVariants(initialVariantIds: number[]): Offer[] {
const triggerVariant = 44376765038898;
const isTriggered = initialVariantIds?.includes(triggerVariant);
if (!isTriggered) return [];
return OFFERS;
}
function formatCurrency(amount?: string | number | null): string {
if (amount === undefined || amount === null) {
return "Free";
}
const numeric = typeof amount === "number" ? amount : parseInt(amount, 10);
if (!Number.isFinite(numeric) || numeric === 0) {
return "Free";
}
return `$${typeof amount === "number" ? amount.toString() : amount}`;
}
Basically, what should happen here is that the extension calls your /api/offer route, which uses offer.server to build the response, and then, passes it back to ShouldRender which writes that response into extension storage for Render to use (hope this makes sense!)
For your question about what happens after an offer is accepted, buyers donât return to checkout. Essentially, what happens is that Shopify attempts the additional charge in the background and then proceeds to the Thank you page. On a success, the upsell is just added to the existing order, and on failure, the Thank you page still loads without the upsell.
If the buyer closes the tab during postâpurchase, the original order is complete and the upsell is attempted asynchronously if they accepted it before closing the tab, resulting either in an added line item or no change, thereâs no âincomplete upsellâ state if that makes sense.
The existing order canât be modified on the Thank you page either, but if you wanted you could present UI that links to a cart permalink or a new checkout to start a separate order there through Thank You page UI extensions. If youâre still running into errors though and can share the specific missing types and one failing CORS request (URL, request and response headers, and the reported origin), Iâll more than happy to see if we can pinpoint the typing and CORS issues. Hope this helps!
@Alan_G Thank you so much for the detailed explanation. I spent my whole Saturday watching your comment and building
Some things are definitely cleared now.
Keeping your code in mind. Iâve made a fully working Post Purchase UI Extension without the Remix app, and here is the Repo for it GitHub - Namish-Kapoor/post-purchase. Can you please give a quick review of the code
Now the only thing that scares me is the minimal backend (not efficient as I am a core Front End Developer) that I have set up for the purpose of the app functionality, which you can give a look at here GitHub - Namish-Kapoor/nodejs-serverless-function-express
The current Problems that I am stuck on to build the post-purchase extension in an extension-only app (without the backend) rather than the remix app are:
- I am not able to authenticate the request, whether itâs coming from Shopify in my backend. How should I effectively do this?
- How should we fetch dynamic products from my own backend/serverless function, and how will Shopify authenticate it?
- Is it necessary that I need to add âAccess-Control-Allow-Originâ: â*â to my offer.ts because it was throwing a CORS error without it?
âAccess to fetch at âhttps://nodejs-serverless-function-express-tau-pied.vercel.app/api/offerâ from origin âhttps://cdn.shopify.comâ has been blocked by CORS policy: No âAccess-Control-Allow-Originâ header is present on the requested resource.â
I appreciate your help with this, and it means a great deal to me. After this is resolved, I will proceed to build an Upsell on the thank-you page. So please donât get mad if I ping you again for concerns on that page
Hey @Namish_Kapoor , no worries, happy to help! For the CORS error, you should be able to follow the steps here to grant your extension network access which should resolve the CORS error:
If that doesnât resolve things though, just let me know and I can look into this further with you for sure. We have a decent guide here on how to authenticate Shopifyâs request as well:
I think the guide there should answer your last two questions, but essentially, your backend will authenticate with a session token, and you can verify that requests are coming from Shopify by decoding the JWT (JSON web token) that we send with our requests like this:
{header}.{payload}.{signature}
Hope this makes sense/helps out, let me know if I can clarify anything as always ![]()
@Alan_G Itâs me again. Can you please look into the issue that I am facing while querying dynamic product data for a separate backend server
Hey @Namish_Kapoor - sure, happy to take a look! Just wanted to confirm if we can close this thread out as well?
Iâll respond in that other thread as soon as I have a chance ![]()
Yes, Sure @Alan_G I can mark the best answer to my question as resolved. Thank you so much for your support


