「Chrome拡張機能からGAS(Google Apps Script)にデータを送りたいだけなのに、CORSエラーで無限に弾かれる…」
そんな経験、ありませんか? 私はこれで貴重な休日を半日溶かしました😇

特にpreflightリクエスト周りは、GAS側の仕様もあってハマりやすいポイントです。
この記事では、Content-Typeをtext/plainに変更するだけで、面倒なpreflightリクエストを回避してCORSエラーを解決する方法を解説します。
難しい設定は不要。コードを数行書き換えるだけでサクッと解決しましょう!
結論:JSONではなく「text/plain」として送る

先に答えを書いておきます。 application/jsonとして送るからブラウザが気を利かせて「確認(preflight)」しちゃうんです。 「これはただのテキストですよ~」と嘘をついて(?)送れば、ブラウザは確認なしで通してくれます。
修正前(❌ Error)
JavaScript
headers: {
'Content-Type': 'application/json' // ← これが原因でpreflightが飛ぶ
},
修正後(✅ OK)
JavaScript
headers: {
'Content-Type': 'text/plain;charset=utf-8' // ← これで解決!
},
なぜCORSエラーが発生するのか?
エラーの正体
Chrome拡張機能からfetchした瞬間、こんな赤い文字がコンソールに出て絶望しますよね。
Access to fetch at 'https://script.google.com/...' from origin 'chrome-extension://...'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check...
原因は「丁寧すぎるブラウザ」
Content-Type: application/jsonを指定すると、ブラウザはセキュリティのために「このサーバー、JSON送っても大丈夫?」という確認(preflightリクエスト/OPTIONSメソッド)を先に飛ばします。
しかし、GASのウェブアプリ仕様だと、このOPTIONSリクエストに対して適切な「いいよ!(Access-Control-Allow-Origin)」を返すのが難しかったりします。結果、門前払いされてエラーになるわけです。
解決策:GASとChrome拡張機能の実装コード
解決策はシンプル。「preflight(確認)が発生しない形式(Simple Request)」で送ればいいのです。その代表格がtext/plainです。
1. Chrome拡張機能側(送信側)
fetchの部分を以下のように書き換えます。
JavaScript
/**
* GASにデータを送信する関数
*/
async function sendDataToGAS(gasUrl, data) {
try {
const response = await fetch(gasUrl, {
method: 'POST',
mode: 'cors', // CORSモードは必須
headers: {
// 【重要】ここで application/json ではなく text/plain を指定!
// これにより preflight リクエストが発生せず、素通りできます。
'Content-Type': 'text/plain;charset=utf-8'
},
// 中身はJSON文字列のままでOK
body: JSON.stringify(data),
credentials: 'omit'
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const result = await response.json();
console.log('送信成功!:', result);
return result;
} catch (error) {
console.error('送信失敗...:', error);
throw error;
}
}
2. GAS側(受信側)
受け取る側も少し工夫が必要です。「テキストとして届いたJSON」をパースしてあげましょう。
JavaScript
function doPost(e) {
try {
// 【重要】送られてきたデータは postData.contents に入っています。
// text/plain として受け取っても、中身は JSON 文字列なので parse 可能です。
const data = JSON.parse(e.postData.contents);
// ▼ ここから先はいつもの処理 ▼
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheets()[0];
sheet.appendRow([
data.date,
data.sales,
new Date() // タイムスタンプとか
]);
// 成功レスポンスを作成
const output = ContentService.createTextOutput(JSON.stringify({
status: "success",
message: "Data received!"
}));
// レスポンスのMimeTypeはJSONにしておく(拡張機能側で受け取りやすくするため)
output.setMimeType(ContentService.MimeType.JSON);
return output;
} catch (error) {
// エラー時の処理
const errorOutput = ContentService.createTextOutput(JSON.stringify({
status: "error",
message: error.toString()
}));
errorOutput.setMimeType(ContentService.MimeType.JSON);
return errorOutput;
}
}
3. デプロイ時の注意点(ここも罠!)
コードが合っていても、デプロイ設定を間違えると403エラーなどで弾かれます。
- GASエディタ右上の「デプロイ」→「新しいデプロイ」をクリック。
- 種類の選択:「ウェブアプリ」。
- アクセスできるユーザー:「全員」を選択してください。
- ここが「自分のみ」だと、拡張機能からのアクセスが弾かれます。
- 「デプロイ」ボタンを押し、発行されたURLをコピー。
まとめ:シンプルイズベスト

CORSエラーは真面目に戦うと泥沼にハマりますが、「Content-Typeをtext/plainにする」という抜け道を使えば一瞬で解決します。
- Chrome側:ヘッダーを
text/plain;charset=utf-8にする。 - GAS側:
JSON.parse(e.postData.contents)で受け取る。 - デプロイ設定:アクセス権限を「全員」にする。
これで、無事にデータがスプレッドシートに飛んでいくはずです。 浮いた時間で、美味しいコーヒーでも淹れて休憩しましょう☕
💻 開発環境(参考)
この記事のコードは以下の環境で動作確認しています。
- Editor: Cursor (VS Code base)
- Environment: Google Apps Script / Chrome Extension Manifest V3
- Keyboard: REALFORCE R3(快適な打鍵感でデバッグも捗ります…!)
