For discussing the development and usage of Checkout UI extensions, post-purchase extensions, web pixels, Customer Accounts UI extensions, and POS UI extensions
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`.
- 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' from origin '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`:
```toml
[capabilities]
network_access = true
[network_access]
allowed_urls = [
"https://page-patio-confusion-heel.trycloudflare.com",
"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`):
```typescript
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:**
```toml
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://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`?
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.