We're moving the community! Starting July 7, the current community will be read-only for approx. 2 weeks. You can browse content, but posting will be temporarily unavailable. Learn more

CORS Issue: from Shopify demo store via my theme extension to Remix App Hosted apis on my VPS

Solved

CORS Issue: from Shopify demo store via my theme extension to Remix App Hosted apis on my VPS

attaboiaj
Shopify Partner
35 4 16
"Shopify demo store via my theme extension like a form submit" : Refer as X
 
I'm encountering a persistent CORS issue when trying to make API calls from my X to a Remix-based backend hosted on Zerops. I've tried several approaches, but none have resolved the issue. Here are the details:
 
Problem:
When I make a POST request from my X (origin: https://MY_DEV_TEST_STROE.myshopify.com) to my API endpoint (example.zerops.app/api/analytic), I receive the following CORS error:

```
Access to fetch at 'example.zerops.app/api/analytic' from origin 'https://MY_DEV_TEST_STROE.myshopify.com' 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.
```

What I've Tried:
1. The API works fine when tested with Postman, Thunder Client, and other HTTP clients/Other Frontends
2. I've successfully set up a similar API using Express and hosted it on Vercel, which works without CORS issues.
3. I've attempted to add CORS headers in my Remix action functions, but the issue persists.
4. Tried everything using this https://community.shopify.com/c/online-store-and-theme/cors-issue-when-requesting-data-from-shopify-...
5. YES I tested Proxy settings but seems like its for "When you wanna call external API from Shopify admin" not from X
6. I tested it with AWS instance same issue 

Tech Stack:
- Frontend: Shopify App
- Backend: Remix
- Hosting: Zerops


Questions:
1. Are there any VPS-specific configurations needed to allow CORS?
2. How can I properly set up CORS in a Remix app hosted on Zerops to allow requests from my Shopify app?
3. Are there any known issues or limitations with CORS when using Remix on Zerops?

Any insights, solutions, or best practices for handling CORS in this scenario would be greatly appreciated. Thank you in advance for your help!




Accepted Solution (1)

attaboiaj
Shopify Partner
35 4 16
Replies 3 (3)

attaboiaj
Shopify Partner
35 4 16

This is an accepted solution.

Adarsh27
Shopify Partner
1 0 0

Hyy @attaboiaj Can you help me to solve the CORS issue, I apply the your solution but still I faced the issue.

 
Below is my action 


export async function action({ request }) {
  console.log("res----->", request)

  if (request.method === "OPTIONS") {
    const response = json({}, { status: 200 });
    return await cors(request, response, corsOptions);
  }

  if (request.method !== "POST") {
    return await cors(
      request,
      json({ success: false, message: "Method not allowed" }, { status: 405 }),
      corsOptions,
    );
  }

  try {
    const body = await request.json();
    const { shopId, offerData } = body;

    if (!shopId || !offerData || typeof offerData !== "object") {
      return await cors(
        request,
        json(
          { success: false, message: "Invalid request data." },
          { status: 400 },
        ),
        corsOptions,
      );
    }

    const session = new Session({
      id: SHOPIFY_CONFIG.sessionId,
      shop: SHOPIFY_CONFIG.shop,
      state: "",
      isOnline: false,
      accessToken: SHOPIFY_CONFIG.accessToken,
    });

    const shopify = shopifyApi({
      apiKey: SHOPIFY_CONFIG.apiKey,
      apiSecretKey: SHOPIFY_CONFIG.apiSecretKey,
      hostName: SHOPIFY_CONFIG.hostName,
      apiVersion: LATEST_API_VERSION,
      isEmbeddedApp: true,
    });

    const admin = new shopify.clients.Graphql({ session });

    let mutation;
    let variables;
    let errorPath;
    let successPath;

    console.log("offerData---->", offerData);

    const percentageDiscountVariable = createBasicCodeDiscount({
      title: offerData.discountText,
      code: generateDiscountCode(
        offerData.discountValue,
        offerData.discountText,
      ),
      percentage: getPercentageFromValue(offerData.discountValue),
      usageLimit: 100,
      appliesOncePerCustomer: true,
      productDiscounts: offerData.discountCombinations.productDiscounts,
      orderDiscounts: offerData.discountCombinations.orderDiscounts,
      shippingDiscounts: offerData.discountCombinations.shippingDiscounts,
    });

    const fixedAmountDiscountVariable = createFixedAmountDiscount({
      title: offerData.discountText,
      code: generateDiscountCode(
        offerData.discountValue,
        offerData.discountText,
      ),
      amount: offerData.discountValue,
      usageLimit: 100,
      appliesOncePerCustomer: true,
      productDiscounts: offerData.discountCombinations.productDiscounts,
      orderDiscounts: offerData.discountCombinations.orderDiscounts,
      shippingDiscounts: offerData.discountCombinations.shippingDiscounts,
    });

    const { lowestProduct, remainingProductIds } =
      getLowestPricedProductWithOthers(offerData.selectedOfferProducts);

    const automaticBxgyDiscountVariable =
      createAutomaticBxgyDiscountCheapestItemFree({
        discountText: offerData.discountText,
        remainingProductIds,
        lowestProductId: lowestProduct?.productId,
        productDiscounts: offerData.discountCombinations.productDiscounts,
        orderDiscounts: offerData.discountCombinations.orderDiscounts,
        shippingDiscounts: offerData.discountCombinations.shippingDiscounts,
      });

    const freeShippingCodeDiscountVariable =
      createFreeShippingCodeDiscountVariable(
        offerData.discountText,
        offerData.discountCombinations.productDiscounts,
        offerData.discountCombinations.orderDiscounts,
      );

    switch (offerData.discountType) {
      case "percentage":
        mutation = AMMOUNT_OFF_PERCENTAGE;
        variables = { basicCodeDiscount: percentageDiscountVariable };
        errorPath = "discountCodeBasicCreate.userErrors";
        successPath = "discountCodeBasicCreate.codeDiscountNode.id";
        break;

      case "fixed":
        mutation = AMMOUNT_OFF_FIXED;
        variables = { basicCodeDiscount: fixedAmountDiscountVariable };
        errorPath = "discountCodeBasicCreate.userErrors";
        successPath = "discountCodeBasicCreate.codeDiscountNode.id";
        break;

      case "cheapest item free":
        mutation = CHEAPEST_ITEM_FREE_MUTATION;
        variables = { automaticBxgyDiscount: automaticBxgyDiscountVariable };
        errorPath = "discountAutomaticBxgyCreate.userErrors";
        successPath = "discountAutomaticBxgyCreate.automaticDiscountNode.id";
        break;

      case "free shipping":
        mutation = FREE_SHIPPING_MUTATION;
        variables = {
          freeShippingCodeDiscount: freeShippingCodeDiscountVariable,
        };
        errorPath = "discountCodeFreeShippingCreate.userErrors";
        successPath = "discountCodeFreeShippingCreate.codeDiscountNode.id";
        break;

      default:
        return await cors(
          request,
          json(
            { success: false, message: "Unsupported discount type." },
            { status: 400 },
          ),
          corsOptions,
        );
    }

    console.log("mutation---->", mutation);

    const response = await admin.query({
      data: {
        query: mutation,
        variables,
      },
    });

    const data = response.body?.data;

    const getFromPath = (obj, path) =>
      path.split(".").reduce((acc, key) => acc && acc[key], obj);

    const userErrors = getFromPath(data, errorPath);

    if (userErrors?.length > 0) {
      return await cors(
        request,
        json({ success: false, errors: userErrors }, { status: 400 }),
        corsOptions,
      );
    }

    const discountId = getFromPath(data, successPath);

    console.log("discountId----->", discountId);

    return await cors(
      request,
      json(
        {
          success: true,
          discountId,
        },
        { status: 201 },
      ),
      corsOptions,
    );
  } catch (error) {
    console.log("error ==== >", error);
    return await cors(
      request,
      json({ success: false, message: error.message }, { status: 500 }),
      corsOptions,
    );
  }



below is my .liquid file from that i call the api 

 
  async addBundleToCart() {
        const selectedProducts = this.offerProducts.selectedOfferProducts;

        try {
          // 1. Add selected products to cart
          const cartAddData = selectedProducts.map((product) => ({
            id: product.variants[0].id.split('/').pop(),
            quantity: 1,
          }));
          console.log('cartAddData---->', cartAddData);

          const addResponse = await fetch('/cart/add.js', {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              Accept: 'application/json',
            },
            body: JSON.stringify({ items: cartAddData }),
          });

          if (!addResponse.ok) throw new Error('Cart add failed');

          console.log('🛒 Products added to cart');

          // 2. Call your discount API
          const shopId = '{{ shop.domain }}';
          {% comment %} const apiURL = 'https://origin-army-meat-basement.trycloudflare.com'; {% endcomment %}
          console.log('reached here 1');
          const discountResponse = await fetch(`https://reggae-rapid-rover-p.trycloudflare.com/api/discountCreate`, {
            method: 'GET',
            headers: {
              'Content-Type': 'application/json',
            }
          });

          console.log(' discountResponse------>', discountResponse);

          if (!discountResponse.ok) {
            throw new Error( 'Discount API failed');
          }

          console.log(' Discount API triggered');
        } catch (error) {
          console.error(' Error during bundle add:', error);
        }
      }


the error is below 

the-compare-at-price-snowboard:1 Access to fetch at 'https://reggae-rapid-rover-p.trycloudflare.com/api/discountCreate' from origin 'https://adarsh-live-store.myshopify.com' 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.





attaboiaj
Shopify Partner
35 4 16

Sorry dude I wasn't really active here for a while, did you find the solutions?

I would highly suggest to put all your errors and ask Claude it has all the answers