CORS Issue in Checkout UI Extension with App Proxy | Remix JS

Hi Developer,

I would really appreciate it if I could get your help regarding my concern.

I’m encountering a CORS error when making a fetch request from my Checkout UI Extension to my app proxy endpoint (/apps/pobox).

Below is my extension toml configuration:

[extensions.capabilities]
block_progress = true
api_access = true
network_access = true

Below is my app.proxy.tsx:

import { json, LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import prisma from "../db.server"; // Import Prisma client

export const loader = async ({ request }: LoaderFunctionArgs) => {
  // Authenticate the request as a public app proxy
  await authenticate.public.appProxy(request);

  // Fetch the first record from the `poboxToggle` table
  const poboxToggle = await prisma.poboxToggle.findFirst();
  console.log("Loader function: Toggle status fetched"); // Less verbose log

  // Return the fetched record or a default object if no record is found
  return json(poboxToggle || { active: false });
};

Below is the output for this proxy, proxy has been setup correctly, store url is https://{shopify-store}.myshopify.com/apps/pobox and below is the output:
{
“id”: “default-toggle”,
“active”: true,
“createdAt”: “2024-12-16T04:02:30.300Z”
}

However, whenever I try to fetch this proxy data in Checkout extension, i get CORS error, below is my code in Checkout.jsx:

  const { address1 } = useShippingAddress() || {};
  const [isPOBoxBlockingActive, setIsPOBoxBlockingActive] = useState(false);
  const [fetchError, setFetchError] = useState(null);
  const extensionApi = useExtensionApi(); // Get the full object
  const shop = extensionApi.shop.myshopifyDomain; // Use the `myshopifyDomain` property

  useEffect(() => {
    const fetchToggleState = async () => {
      try {
        const apiUrl = `https://${shop}/apps/pobox`; // Use the shop domain dynamically
        console.log("Fetching from:", apiUrl); // Log the fetch URL
        const response = await fetch(apiUrl, {
          headers: { Accept: "application/json" },
        });

        console.log("Fetch response:", response); // Log the response object

        if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);

        const data = await response.json();
        console.log("Toggle state fetched successfully:", data);

        setIsPOBoxBlockingActive(data.active);
        setFetchError(null);
      } catch (error) {
        console.error("Failed to fetch toggle status:", error);
        setFetchError("Failed to load PO Box settings. Please try again later.");
      }
    };

    fetchToggleState();
  }, [shop]); // Add shop to the dependency array

However, I am still getting CORS error message in console log as per attached:

Thank you for your time, I would really appreciate your help and information.

Many thanks

Hi @Ankit_Khand

You’re so close to performing the request.

CORS is a security measure to keep unauthorized websites from performing frontend AJAX requests against your API. It also helps protect users from phishing sites, by blocking unauthorized POST requests to sensitive APIs like payments, logins, etc.

You’ll need to update your server side loader (aka endpoint) to allow OPTIONS requests from extensions.shopifycdn.com as the error points out.

An OPTIONS request is a pre-cursor to a GET/POST request, and your loader is rejecting that OPTIONS request because the requester’s domain is not allowed.

I don’t use the Shopify remix template myself, but you can use the Remix CORS configuration to add Shopify’s domain to the allowed list of domains for OPTIONS requests:

1 Like

Hi @Dylan

Thank you for your help and information, I did try tremix-utils but I did not get any luck. As per now I am using temporary cloudflare link in my extension(Checkout.jsx) as below and it is working:


  const useApiUrl = 'https://immigration-sections-entries-peace.trycloudflare.com';

  useEffect(() => {
    const fetchToggleStatus = async () => {
      try {
        // Replace with your app's public URL
        const apiUrl = useApiUrl + `/app/proxy`;

        console.log("Fetching data from:", apiUrl);
        const response = await fetch(apiUrl, {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
          },
        });

        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();
        console.log("Fetched data:", data);
        setIsPOBoxBlockingActive(data.active); // Assuming 'active' is a boolean
      } catch (error) {
        console.error("Failed to fetch toggle status:", error);
        setFetchError("Failed to load PO Box settings. Please try again later.");
      }
    };

    fetchToggleStatus();
  }, []);

And in my below proxy file => app.proxy.tsx, i have to bypass authentication in order to fetch value in checkout.jsx:

import { json, LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import prisma from "../db.server";

// Utility function to add CORS headers
const applyCors = (response) => {
  response.headers.set("Access-Control-Allow-Origin", "*"); // Allow all origins for testing
  response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
  return response;
};

export const loader = async ({ request }: LoaderFunctionArgs) => {
  try {
    // Temporary bypass for authentication
    // const { admin } = await authenticate.public.appProxy(request);
    console.log("Skipping authentication for debugging");

    const poboxToggle = await prisma.poboxToggle.findFirst(); 
    const active = poboxToggle?.active || false;
    return json({ active });
  } catch (error) {
    console.error("Error in loader function:", error.message);
    return json({ error: "An error occurred." }, { status: 500 });
  }
};

I would really appreciate your suggestion and help.

Many thanks

Hello, did you manage to fix it? am working on it 2 days and nothing. It is really frustrating that you cannot simply fetch data by app extension from app endpoint. Why it is just not already set by shopify when initialize the app ?

Yes it is really frustrating when there is not enough documentation regarding CORS. Since I built this app custom app for one of my merchants, hence i solved this issue by simply changing assigning proxy url, but i would not recommend it if you are working in production level. Since i hosted this app in fly.io, below is the example how i fixed this cors issue as per below checkout extension. Hope this might help.

  const useApiUrl =
    "https://{flyio-urlname}.fly.dev";

  useEffect(() => {
    const fetchToggleStatus = async () => {
      try {
        const apiUrl = useApiUrl + `/app/proxy`;

        console.log("Fetching data from:", apiUrl);
        const response = await fetch(apiUrl, {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
            Accept: "application/json",
          },
        });

        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }

        const data = await response.json();
        console.log("Fetched data:", data);
...
...
}, [])