Hello Shopify Community,
I’m developing a Post-Purchase extension for my Shopify app, and I’m running into some issues with fetching the shop configuration from my server. I hope someone here can help me figure out what’s going wrong.
Background:
-
I’m creating a Post-Purchase extension that collects customer feedback after checkout.
-
The extension needs to fetch shop-specific configuration data from my server during the
ShouldRenderphase. -
I’m using a Remix app as my server, hosted at
[https://page-patio-confusion-heel.trycloudflare.com](https://page-patio-confusion-heel.trycloudflare.com). -
I’ve set up my
shopify.extension.tomlto allow network access to the required URLs.
The Problem:
When I test the extension, I encounter the following errors in the console:
Error fetching shop configuration: AxiosError {message: 'Network Error', name: 'AxiosError', code: 'ERR_NETWORK', config: {…}, request: XMLHttpRequest, …}
Access to XMLHttpRequest at '[https://plugins-dashboard.coddle.de/zaufane-shopify/api/shop-configuration](https://plugins-dashboard.coddle.de/zaufane-shopify/api/shop-configuration)' from origin '[https://cdn.shopify.com](https://cdn.shopify.com)' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
What I’ve Tried:
-
Ensured that my server endpoints include the necessary CORS headers (
Access-Control-Allow-Origin,Access-Control-Allow-Headers, etc.). -
Updated my
shopify.extension.tomlfile to include theallowed_urls:
[capabilities]
network_access = true
[network_access]
allowed_urls = [
"[https://page-patio-confusion-heel.trycloudflare.com](https://page-patio-confusion-heel.trycloudflare.com)",
"[https://plugins-dashboard.coddle.de](https://plugins-dashboard.coddle.de)"
]
-
Verified that the server is accessible and the endpoints work when tested with
curland Postman. -
Adjusted my code to handle the possibility of CORS errors gracefully, but I still need to fetch the configuration successfully.
My Code:
Here is the relevant part of my extension code (index.tsx):
import React, { useState } from "react";
import {
extend,
render,
BlockStack,
Button,
CalloutBanner,
Layout,
TextBlock,
TextContainer,
TextField,
InlineStack,
Text,
Bookend,
Banner,
useExtensionApi,
} from "@shopify/post-purchase-ui-extensions-react";
import { translations } from "./utils/langs";
import axios from "axios";
const APP_URL = process.env.SHOPIFY_EXTENSION_URL; // Replace with your Remix app URL
// Fetch shop configuration
async function fetchShopConfiguration(shopDomain: string) {
const url = `${APP_URL}/api/shop-configuration`;
try {
const response = await axios.post(
url,
{ shopURL: shopDomain },
{
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
},
);
return response.data;
} catch (error) {
console.error("Error fetching shop configuration:", error);
throw error;
}
}
extend(
"Checkout::PostPurchase::ShouldRender",
async ({
inputData: {
initialPurchase: { lineItems },
shop: { domain: shopDomain },
},
storage,
}) => {
try {
const shopConfiguration = await fetchShopConfiguration(shopDomain);
await storage.update({
initialData: {
rating: 0,
feedback: "",
shopConfiguration,
orderId: lineItems[0].product.id,
},
});
return {
render: shopConfiguration.extensionEnabled === true,
};
} catch {
// If fetching the configuration fails, do not render the extension
return { render: false };
}
},
);
render("Checkout::PostPurchase::Render", ({ storage }) => (
<App initialData={storage.initialData} />
));
function App({ initialData }) {
const { done } = useExtensionApi();
const [rating, setRating] = useState<number>(initialData.rating || 0);
const [feedback, setFeedback] = useState<string>(initialData.feedback || "");
const [loading, setLoading] = useState<boolean>(false);
const [warning, setWarning] = useState<string>("");
const locale = navigator.language.split("-")[0];
const t = translations[locale] || translations["en"];
const handleSubmit = async () => {
setLoading(true);
setWarning("");
if (feedback.trim().length < 3 || rating === 0) {
setWarning("Please fill in all fields");
setLoading(false);
return;
}
const url = `${APP_URL}/api/send-feedback`;
try {
const response = await axios.post(
url,
{
rating,
feedback,
shopConfiguration: initialData.shopConfiguration,
orderId: initialData.orderId,
},
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
},
);
done(); // Redirect to thank-you page
return response.data;
} catch (error) {
console.error("Error submitting feedback:", error);
// Handle error as needed
} finally {
setLoading(false);
}
};
return (
<BlockStack spacing="loose">
<CalloutBanner title={t.title}>{t.feedbackPrompt}</CalloutBanner>
<Layout>
<BlockStack spacing="loose">
<TextContainer>
<TextBlock>
<Text size="large">{t.feedbackInstruction}</Text>
</TextBlock>
</TextContainer>
<BlockStack spacing="tight">
<TextBlock>
<Text size="large">{t.starRatingPrompt}</Text>
</TextBlock>
<InlineStack spacing="xtight">
{[1, 2, 3, 4, 5].map((star) => (
<Button
key={star}
onPress={() => setRating(star)}
subdued={star > rating}
>
<Text size="xlarge">{star <= rating ? "★" : "☆"}</Text>
</Button>
))}
</InlineStack>
<Bookend>
<TextBlock>
<Text size="large">
{t.ratingOutOf} {rating}/5
</Text>
</TextBlock>
</Bookend>
</BlockStack>
<BlockStack spacing="tight">
<TextField
label={t.personalFeedback}
value={feedback}
onChange={setFeedback}
multiline
required
type="text"
/>
</BlockStack>
{warning && <Banner status="critical" title={warning} />}
<Button onPress={handleSubmit} submit loading={loading}>
{t.submitButton}
</Button>
</BlockStack>
</Layout>
</BlockStack>
);
}
export default App;
My shopify.extension.toml Configuration:
name = "reviewfeedback"
type = "checkout_post_purchase"
handle = "my-post-purchase-extension"
[capabilities]
network_access = true
[network_access]
allowed_urls = [
"[https://page-patio-confusion-heel.trycloudflare.com](https://page-patio-confusion-heel.trycloudflare.com)",
"[https://plugins-dashboard.coddle.de](https://plugins-dashboard.coddle.de)"
]
Questions:
-
Why am I still getting CORS errors even after configuring the server to include
Access-Control-Allow-Originheaders? -
Is there something specific I need to do in my extension or server code to allow cross-origin requests from
[https://cdn.shopify.com](https://cdn.shopify.com)? -
How can I successfully fetch the shop configuration during the
ShouldRenderphase without encountering network errors?
Additional Details:
-
My server endpoints correctly respond to
OPTIONSpreflight requests with the necessary CORS headers. -
The network requests work fine when tested outside of the Shopify extension (e.g., using Postman).
-
I’m using
axiosfor making HTTP requests within the extension.
Any help or guidance would be greatly appreciated!
Thank you in advance.