【GAS×Chrome拡張】CORSエラーでfetchが弾かれる…。Content-Typeを「text/plain」に変えるだけで解決した話

  • URLをコピーしました!

「Chrome拡張機能からGAS(Google Apps Script)にデータを送りたいだけなのに、CORSエラーで無限に弾かれる…」

そんな経験、ありませんか? 私はこれで貴重な休日を半日溶かしました😇

特にpreflightリクエスト周りは、GAS側の仕様もあってハマりやすいポイントです。

この記事では、Content-Typetext/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エラーなどで弾かれます。

  1. GASエディタ右上の「デプロイ」→「新しいデプロイ」をクリック。
  2. 種類の選択:「ウェブアプリ」。
  3. アクセスできるユーザー:「全員」を選択してください。
    • ここが「自分のみ」だと、拡張機能からのアクセスが弾かれます。
  4. 「デプロイ」ボタンを押し、発行されたURLをコピー。

まとめ:シンプルイズベスト

CORSエラーは真面目に戦うと泥沼にハマりますが、「Content-Typeをtext/plainにする」という抜け道を使えば一瞬で解決します。

  1. Chrome側:ヘッダーをtext/plain;charset=utf-8にする。
  2. GAS側:JSON.parse(e.postData.contents)で受け取る。
  3. デプロイ設定:アクセス権限を「全員」にする。

これで、無事にデータがスプレッドシートに飛んでいくはずです。 浮いた時間で、美味しいコーヒーでも淹れて休憩しましょう☕


💻 開発環境(参考)

この記事のコードは以下の環境で動作確認しています。

  • Editor: Cursor (VS Code base)
  • Environment: Google Apps Script / Chrome Extension Manifest V3
  • Keyboard: REALFORCE R3(快適な打鍵感でデバッグも捗ります…!)
よかったらシェアしてね!
  • URLをコピーしました!
目次