GAS『Script Function Not Found』の原因と対処法

GAS『Script Function Not Found』の原因と対処法

Google Apps Scriptで「Script function not found: xxx」は、呼び出した関数名が実行時に見つからないときに出る。原因は関数名の不一致、グローバルで定義されていない、ウェブアプリのエントリ(doGet/doPost)未定義、トリガーやデプロイの参照先ずれ、保存漏れ・版ずれなど。発生場面ごとの直し方と再発防止のコツをまとめる。

エラーの意味と主な発生場面

・エディタの実行/Runメニューで指定した関数が存在しない
・ウェブアプリURLにアクセスしたが doGet/doPost が無い
・HTMLサービスの google.script.run で呼んだサーバ関数が無い
・シンプルトリガー(onOpen/onEditなど)の名前が不正
・インストール型トリガーが削除/改名済みの関数を指している
・ライブラリ/デプロイ(clasp含む)が古い版を参照している

まず確認(最短チェック)

□ 呼び出し名と定義名は完全一致か(大文字/小文字/全角無し)
□ 関数はトップレベル(グローバル)で定義されているか(IIFE/{} 内ではない)
□ 全ファイルを保存したか(⌘/Ctrl+S)/構文エラーが残っていないか
□ ウェブアプリは doGet/doPost を定義し、新バージョンでデプロイ済みか
□ トリガーが存在し、正しい関数名を指しているか(削除した関数名になっていないか)
□ ライブラリ/バージョン/スクリプトIDが想定のものか

ケース1:関数名の不一致(タイポ・全角・大文字小文字)

// NG:大文字小文字が違う
function myFunc() {}
function run() { myfunc(); }  // Script function not found: myfunc

// OK:完全一致
function myFunc() {}
function run() { myFunc(); }

// NG:全角混入(見た目が似ていても別文字)
function doPost(e) {}
function test() { doPost({}); } // P が全角 → not found

// 対処:呼び出し箇所をコピペで置換、全角・空白を排除

ケース2:ウェブアプリの doGet/doPost が無い/旧デプロイを参照

// NG:doGet 未定義でURLにアクセス → Script function not found: doGet
// OK:エントリを定義
function doGet(e) {
  return HtmlService.createHtmlOutput('OK');
}
function doPost(e) {
  return ContentService.createTextOutput('POST OK');
}

// 手順
// 1) 関数を定義 → 2) ファイル保存 → 3) 「新バージョンとしてデプロイ」→ 4) 最新URLで確認

・デプロイ更新を忘れると古いコードが動くため、新バージョンで公開し直す。

ケース3:HTMLサービスの google.script.run でサーバ関数名不一致

// Code.gs(サーバ)
function addRow(values) {
  const sh = SpreadsheetApp.getActiveSheet();
  sh.appendRow(values);
}

// index.html(クライアント)
<script>
  function submit() {
    const values = ['A','B','C'];
    google.script.run
      .withSuccessHandler(() => alert('done'))
      .addRow(values);     // ← addRow と完全一致
  }
</script>

// NG例:.addrow と書く/IIFE内の関数を呼ぶ → not found

・google.script.run から呼べるのは .gs のトップレベル関数のみ。

ケース4:トリガー(シンプル/インストール型)が不正な関数名を指す

// NG:シンプルトリガー名が誤り(onedit など)
function onedit(e) {} // 実行されず、期待動作にならない

// OK:正しい関数名
function onEdit(e) { /* ... */ }

// インストール型トリガーが古い関数名を指している場合の修正
function listTriggers() {
  ScriptApp.getProjectTriggers().forEach(t =>
    console.log(t.getHandlerFunction(), t.getEventType()));
}
function resetTrigger() {
  // 古いトリガーを削除して再作成
  ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
  ScriptApp.newTrigger('batchJob').timeBased().everyMinutes(5).create();
}
function batchJob() { /* 本処理 */ }

・関数を改名/削除したら、紐づくトリガーも更新する。

ケース5:グローバルで定義されていない(IIFE/ブロック/モジュール風)

// NG:IIFEの内側 → グローバルから見えない
(() => {
  function hidden() {}
})();
function run() { hidden(); } // not found

// OK:トップレベル関数として定義
function visible() {}
function run() { visible(); }

// NG:ブロックスコープ内
{
  function localOnly() {}
}
function run2() { localOnly(); } // not found

・Apps Scriptで呼び出し対象はトップレベルに置く。

ケース6:ライブラリ/clasp/別プロジェクトIDの版ずれ

// 診断:実行中プロジェクトのスクリプトIDを確認
function whoAmI() {
  console.log('scriptId:', ScriptApp.getScriptId());
}

// 対処の方針
// ・ライブラリ:プロジェクト設定→ライブラリ→参照バージョンを最新に
// ・ウェブアプリ:新バージョンとしてデプロイ(URLは同じでも中身は更新が必要)
// ・clasp:push 後にエディタで反映を確認、必要なら再デプロイ

・「あるはずの関数」が古い版には無いケースが典型。

ケース7:保存漏れ/構文エラーで最新が反映されていない

// 1) すべてのタブを保存(⌘/Ctrl+S)
// 2) エディタの問題タブ/コンソールで構文エラーがないか確認
// 3) 実行ログ(Executions)で失敗原因を確認

・保存できていないファイルが1つでもあると、実行時に旧コードが走ることがある。

迅速な切り分けユーティリティ(グローバル関数の見える化)

function listGlobals() {
  const names = Object.keys(this).filter(k => typeof this[k] === 'function');
  names.sort();
  console.log(names.join(', ')); // ここに呼びたい関数名が出ているか確認
}
function assertFunction(name) {
  if (typeof this[name] !== 'function') {
    throw new Error('関数が見つかりません: ' + name);
  }
}

・not found が出る名称と、この一覧の差を埋めると早い。

通し例1:ウェブアプリ(doGet未定義→修正)

// NG
// (doGetが無い状態でURLアクセス)→ Script function not found: doGet

// OK
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('index');
}

通し例2:HTMLサービス(サーバ関数名ズレ→修正)

// Code.gs(OK版)
function saveRow(v) {
  SpreadsheetApp.getActiveSheet().appendRow(v);
}
// index.html(OK版)
<script>
  function send() {
    google.script.run.withSuccessHandler(() => alert('saved')).saveRow(['a','b']);
  }
</script>

通し例3:トリガー(改名後の指し替え)

// 旧:function job() {}
// 新:function batchJob() {}
// → 旧トリガーは 'job' を指し続けるので not found

function migrateTrigger() {
  ScriptApp.getProjectTriggers().forEach(t => ScriptApp.deleteTrigger(t));
  ScriptApp.newTrigger('batchJob').timeBased().everyMinutes(10).create();
}

再発防止の運用ルール

・エントリポイント(doGet/doPost/onEdit/onOpen/バッチ関数)は1ファイルに集約
・改名時は「検索置換 → トリガー再作成 → 新バージョンで再デプロイ」をセット運用
・google.script.run で呼ぶ関数は .gs のトップレベルのみを約束
・レビュー時に listGlobals() の出力で存在確認
・clasp/ライブラリは参照バージョンとスクリプトIDをREADMEに明記

最終チェックリスト

□ 呼び出し名=定義名が完全一致(大/小/全角なし)
□ トップレベル関数として定義(IIFE/ブロック外)
□ すべて保存済み、構文エラーなし
□ ウェブアプリの doGet/doPost を定義して「新バージョンでデプロイ」
□ トリガーが正しい関数名を指している(不要なものは削除)
□ ライブラリ/デプロイの参照バージョンが最新
□ listGlobals() に目的の関数名が現れる