Help Needed with Post-Purchase Extension: CORS Errors When Fetching Shop Configuration

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 ShouldRender phase.

  • 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.toml to 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.toml file to include the allowed_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 curl and 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:

  1. Why am I still getting CORS errors even after configuring the server to include Access-Control-Allow-Origin headers?

  2. 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)?

  3. How can I successfully fetch the shop configuration during the ShouldRender phase without encountering network errors?

Additional Details:

  • My server endpoints correctly respond to OPTIONS preflight 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 axios for making HTTP requests within the extension.

Any help or guidance would be greatly appreciated!

Thank you in advance.