I keep getting CORS policy error in my Shopify App and Theme Template code

Topic summary

A developer is experiencing CORS policy errors when fetching product data from their Shopify store using the GraphQL Admin API (version 2024-04).

Key Details:

  • The code worked successfully until a few days ago, with no changes made before it stopped functioning
  • Error indicates missing ‘Access-Control-Allow-Origin’ header in preflight response
  • Using a CORS proxy (corsproxy.io) to make requests from their frontend domain (kubuk.ro)
  • Fetching products with metafields and variants using POST requests with Shopify access token

Current Status:

  • Issue appears suddenly without code modifications, suggesting potential Shopify-side changes
  • Developer is time-constrained and seeking solutions
  • The discussion remains open with no resolution provided yet

Technical Context:
The code includes reversed/obfuscated sections that handle metafield creation and order data fetching, making full analysis difficult. The CORS error typically occurs when making Admin API calls directly from browser-based applications, which Shopify restricts for security reasons.

Summarized with AI on November 8. AI used: claude-sonnet-4-5-20250929.

So I am developing a custom app to run some algorythm and I need to fetch the products from my store. I’ve used fetch API for that, along with the GraphQL API version 2024-04. It all worked succesfully since a few days, when I suddenly started getting CORS policy errors and I’m kinda running out of time figuring out what could cause the issue. I guess it’s Shopify related since it worked and I didn’t change anything in the code when it stopped working.

Here’s the error I am getting:

Access to fetch at ‘https://corsproxy.io/?https://<MY_STORE_NAME>/admin/api/2024-04/graphql.json’ from origin ‘https://www.kubuk.ro’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

Here’s an example of my function that fetches the products data:

export const gamesFinder = (async () => {
  const games = [];
  const token = MY_TOKEN_FROM_ENV;
  const headers = {
    "X-Shopify-Access-Token": token,
    Accept: "application/json",
    "Content-Type": "application/graphql",
  };

  const url = 'https://corsproxy.io/?https://<MY_STORE_NAME>/admin/api/2024-04/graphql.json';
  let after = null;

  let body = `{
    products(first:10`;

  if (after) {
    body += `, after:"${after}"`;
  }

  body += `) {
          nodes {
              id,
              title,
              metafields(first:20) {
                  nodes {
                      key,
                      value
                  }
              }
              variants(first:20) {
                  nodes {
                      id
                  }
              }
          }
          pageInfo {
              hasNextPage,
              endCursor
          }
      }
  }`;

  const response = await fetch(url, {
    method: "POST",
    headers,
    body,
  });

  let data = await response.json();
  games.push(...data.data.products.nodes);
  
  while (data.data.products.pageInfo.hasNextPage) {
    after = data.data.products.pageInfo.endCursor;
    body = `{
      products(first:10`;
  
    if (after) {
      body += `, after:"${after}"`;
    }
  
    body += `) {
            nodes {
                id,
                title,
                metafields(first:20) {
                    nodes {
                        key,
                        value
                    }
                }
                variants(first:20) {
                  nodes {
                      id
                  }
                }
            }
            pageInfo {
                hasNextPage,
                endCursor
            }
        }
    }`;
    const response = await fetch(url, {
      method: "POST",
      headers,
      body,
    });
    data = await response.json();
    games.push(...data.data.products.nodes);
  }

  return games;
});

Note: I’ve tried using a different proxy, Axios to fetch, even older API versions but didn’t solve anything. Fetching from Postman works just fine, I don’t get any CORS error there.

Also, I’ve developped another feature which demanded me write code in HTML, CSS and JAVASCRIPT in the store’s theme template code in order to leave some ratings on the orders. (this might not be the best workaround or practice but this is the only way I could achieve this). Now here I’ve used the same logic in my javascript I had a fetch function that had sent a request via the GraphQL API so I could fetch and eventually modify some metafields of orders. This used to work aswell but it seems like it stopped working because of CORS policy. I am getting the same error.

Here’s the part of the code that I’ve used in the js:

// function to query data

const fetchOrderData = (
  url,
  token,
  orderId
) => {
  // graphql query for the order
  const orderInfoQuery = `
    {
      order(id: "gid://shopify/Order/${orderId}") {
        id
        metafields(first: 10, namespace: "custom") {
          nodes {
            id
            namespace
            key
            value
            type
            definition{
              id
            }
          }
        }
      }
    }
  `;
  
  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Access-Token': token
    },
    body: JSON.stringify({
      query: orderInfoQuery
    })
  })
    .then(response => {
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    })
    .then(data => {
      const metafields = data.data.order.metafields.nodes;
      let ratingId = metafields.find(metafield => metafield.key === 'ratings');
      let ratingValues = [];
      if (ratingId) {
        ratingId = ratingId.id;
        ratingValues = metafields.find(metafield => metafield.key === 'ratings');
        if (ratingValues) {
          ratingValues = ratingValues.value;
        }
      }
      return {ratingId, ratingValues};
    })
    .catch(error => {
      console.error('Error querying order info:', error);
    });
};

// here is the call to that function
const apiUrl = 'https://corsproxy.io/?https://<MY_STORE_NAME>/admin/api/2024-04/graphql.json';
const accessToken = MY_ACCESS_TOKEN;

fetchOrderData(apiUrl, accessToken, orderId)
    .then(({ ratingId, ratingValues }) => {
      // metafield id and ratings should be got out if they exist
      ratingMetafield = ratingId;

      // if there is no metafield for the ratings (if the ratings metafield hasnt been set yet <=> if its value is empty on shopify admin page) create it and set a default value of []
      if (!ratingMetafield) {
        // call the function to create this metafield
        createOrderMetafield(apiUrl, accessToken, orderId)
          .then(newMetafieldId => {
            // if the new metafield has been created update the metafieldID to make it possible to rate instantly without having to refresh the page
            ratingMetafield = newMetafieldId;
          })
          .catch(error => {
            console.log(error);
          });
      } else {
        ratings = JSON.parse(ratingValues);
        ratings.forEach((rating) => {
          const btnRatingIndex = Array.from(button).findIndex(btn => btn.dataset.gameId === rating.id);
          
          if (btnRatingIndex !== -1) {
            button[btnRatingIndex].innerText = rating.rating; 
          }
        });
      }
      
      // if a star is pressed get the value from the input and set it as the button's text that has been pressed to open the current star rating
      const starRatingForms = document.querySelectorAll('[id^="star-rating-form-"]');
      
      starRatingForms.forEach(form => {
        form.addEventListener("click", function(event) {
          if (event.target.matches("input[type=radio][name=rating]")) {
            const ratingValue = event.target.value;
            const lineItemId = event.target.id.split("-")[0];
            const ratingButton = document.getElementById(`rating-button-${lineItemId}`);
            const gameId = ratingButton.dataset.gameId;
            
            if (ratingButton) {
              // set the label for the button that opens the rating stars
              ratingButton.textContent = ratingValue;
              const ratingStars = ratingButton.parentNode.parentNode.querySelector('.star-rating-wrapper');
      
              // when a value has been selected send a request to the backend to set the rating of the given item in the order
              if (orderId && gameId) {
                const existingRatingIndex = ratings.findIndex(rating => rating.id === gameId);
  
                if (existingRatingIndex !== -1) {
                  // Update the rating value if the element exists
                  ratings[existingRatingIndex].rating = Number(ratingValue);
                } else {
                  // Add a new element if the element doesn't exist
                  ratings.push({ id: gameId, rating: Number(ratingValue) });
                }
      
                updateOrderRatings(ratingMetafield, ratings, orderId, apiUrl, accessToken);
              }
      
              ratingStars.classList.remove('show');
            }
          }
        });
      });
    });
1 Like