CORS issue when connecting React form to Google Sheets API

I built a web form using React and deployed it on Vercel. I want to send the form data to a Google Sheets spreadsheet but I keep getting a CORS error.

The error message says:

Access to fetch at ‘URL’ from origin 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.

Here’s my Google Apps Script code:

function doPost(request) {
  var corsHeaders = {
    "Access-Control-Allow-Origin": "*", 
    "Access-Control-Allow-Methods": "POST", 
    "Access-Control-Allow-Headers": "Content-Type", 
  };

  if (request.postData.contents) {
    var payload = JSON.parse(request.postData.contents); 
    
    var spreadsheet = SpreadsheetApp.openById('your-sheet-id').getSheetByName('Sheet1');
    
    var fullName = payload.fullName || '';  
    var userEmail = payload.userEmail || '';  
    var userMessage = payload.userMessage || ''; 
    
    spreadsheet.appendRow([fullName, userEmail, userMessage]); 
    return ContentService.createTextOutput("Data saved successfully!")
      .setMimeType(ContentService.MimeType.JSON)
      .setHeaders(corsHeaders);
  } else {
    return ContentService.createTextOutput("Failed to receive data.")
      .setMimeType(ContentService.MimeType.JSON)
      .setHeaders(corsHeaders);
  }
}

And here’s my React fetch request:

try {
  const result = await fetch("https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(userData),
  });

  if (result.ok) {
    const responseData = await result.json();
    console.log("API Response:", responseData);
    alert("Form submitted successfully!");
  } else {
    const errorInfo = await result.json();
    console.error("Response error:", errorInfo);
    alert("Submission failed: " + (errorInfo.message || "Unknown error"));
  }
} catch (err) {
  console.error("Request failed:", err);
  alert("An error occurred during submission.");
}

I’ve tried different approaches to fix the CORS headers but nothing works. Is there a solution for this or should I try a different method?

Hit this exact issue last month building a contact form. The doOptions function mentioned above works, but there’s another gotcha that ate hours of my debugging time. Your Google Apps Script needs to be deployed with execution permissions set to “Anyone” and access set to “Anyone, even anonymous”. I had doOptions working but my deployment settings were still blocking access - caused random CORS failures. Redeployed with the right permissions and preflight requests started working every time. Also heads up - Google’s servers sometimes take a few minutes to update the new deployment settings, so don’t freak out if it doesn’t work right away.

ur missing the doOptions function for preflight requests. Google Apps Script needs both doPost and doOptions to handle CORS properly. Add this to ur script:

function doOptions(request) {
  return ContentService.createTextOutput()
    .setMimeType(ContentService.MimeType.TEXT)
    .setHeaders({
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST',
      'Access-Control-Allow-Headers': 'Content-Type'
    });
}

This should fix the preflight issue ur getting.

Had the same issue 6 months ago. Beyond doOptions and deployment settings, ensure you’re using the web app URL, not the script editor URL. The web app URL should end with /exec and looks like https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec. Additionally, every time you change your Apps Script code, you need to create a new deployment version or update the existing one. I spent hours thinking it was a CORS issue when my changes weren’t live because I forgot to redeploy. Getting these three things right - doOptions function, proper deployment settings, and correct web app URL - will resolve your CORS issues.

I am having same problem getting cors error:
please help me

function doPost(e) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const data = JSON.parse(e.postData.contents);

sheet.appendRow([
new Date(),
data.name,
data.mobile,
data.email,
data.social,
data.company,
data.website,
data.joinAs,
data.support ? data.support.join(', ') : ‘’
]);

return ContentService.createTextOutput(JSON.stringify({ result: ‘success’ }))
.setMimeType(ContentService.MimeType.JSON)
.setHeader(‘Access-Control-Allow-Origin’, ‘*’)
.setHeader(‘Access-Control-Allow-Headers’, ‘Content-Type’);
}

function doGet(e) {
return ContentService.createTextOutput(“This endpoint is for POST requests only.”);
}

function doOptions(e) {
return ContentService.createTextOutput(‘’)
.setMimeType(ContentService.MimeType.TEXT)
.setHeader(‘Access-Control-Allow-Origin’, ‘*’)
.setHeader(‘Access-Control-Allow-Methods’, ‘POST, OPTIONS’)
.setHeader(‘Access-Control-Allow-Headers’, ‘Content-Type’);
}

const onFinish = async (values: any) => {
setLoading(true);

try {
  const response = await axios.post('https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec', values , {
headers: {
  'Content-Type': 'application/json',
}

});

  if (response.data.result === 'success') {
    message.success('Form submitted successfully!');
    setIsModalOpen(false);
  } else {
    message.error('Submission failed.');
  }
} catch (error) {
  console.error(error);
  message.error('Error submitting form');
} finally {
  setLoading(false);
}

};