TL;DR
Google SheetsのカスタムFunctionは、制限されたサンドボックス内で実行されます。そのサンドボックスはOAuthを必要とする操作をすべてブロックします — SpreadsheetApp.openById()はまさにその対象です。=MY_FUNCTION()の数式からは呼び出せません、それが現実です。ロジックは通常のApps Script関数、インストール可能なトリガー、またはメニューから呼び出す関数に移してください。
エラーの詳細
Exception: You do not have permission to call SpreadsheetApp.openById. (line X)
このエラーは、関数を呼び出したセルに直接表示されます — 値の代わりにこのメッセージが表示されます。セルにカーソルを合わせると、赤い三角形のツールチップとして確認できます。
なぜこのエラーが発生するのか
=MYFUNCTION(A1)のようにセルから関数を呼び出すと、Googleはそれを「カスタムファンクションサンドボックス」と呼ばれる環境で実行します。読み取り専用かつ認証なしのゾーンだと考えてください。Googleがこのように設計したのは意図的です — 数式は常にサイレントに再計算されるため、完全なOAuthアクセスを与えると、セルが変更されるたびにスプレッドシートがデータを密かに外部に送信できてしまいます。
サンドボックスは、動作しそうに見えて実は使えない多くの機能をブロックします:
- 他のスプレッドシートを開く操作(
SpreadsheetApp.openById()、openByUrl()) - OAuthを必要とする外部サービス(
GmailApp、DriveApp、認証が必要なUrlFetchAppの呼び出し) - スプレッドシートの構造変更(行の挿入、書式設定)
- UI関連のあらゆる操作(
SpreadsheetApp.getUi())
わかりやすい判断基準として:OAuthの同意ダイアログが表示されるような操作は、カスタムファンクション内では動作しません。Apps Scriptのドキュメントに完全なリストがありますが、このルールで90%はカバーできます。
修正方法1 — 代わりにgetActiveSpreadsheet()を使う(同じスプレッドシート内のみ)
数式が存在する同じスプレッドシートのデータが必要な場合は、openById()をgetActiveSpreadsheet()に変えてください。こちらはカスタムファンクション内で許可されています。
// ❌ カスタムファンクション内では失敗する
function getSheetValue(sheetName, row, col) {
const ss = SpreadsheetApp.openById('YOUR_SPREADSHEET_ID');
const sheet = ss.getSheetByName(sheetName);
return sheet.getRange(row, col).getValue();
}
// ✅ 動作する — 現在のスプレッドシートから読み取る
function getSheetValue(sheetName, row, col) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
if (!sheet) return 'Sheet not found';
return sheet.getRange(row, col).getValue();
}
注意点:この方法ではデータの読み取りのみ可能です。カスタムファンクション内からシートへの書き込みもブロックされています。
修正方法2 — ロジックをメニュー関数やボタンに移す
別のスプレッドシートを開く必要がある場合は、ロジックをカスタムファンクションのサンドボックスの外に移す必要があります。メニュー項目に紐付けるのが最もすっきりした方法です — 数行のボイラープレートで完了します。
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('My Tools')
.addItem('Pull Data from Other Sheet', 'pullFromOtherSheet')
.addToUi();
}
function pullFromOtherSheet() {
// 通常の関数 — 完全な権限、サンドボックスなし
const ss = SpreadsheetApp.openById('YOUR_SPREADSHEET_ID');
const data = ss.getSheetByName('Data').getRange('A1:B10').getValues();
SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Results')
.getRange(1, 1, data.length, data[0].length)
.setValues(data);
}
流れとしては:ユーザーがメニュー項目をクリック → 完全な権限で関数が実行される → データがシートに反映される → 数式が通常通りそれを参照する。サンドボックスは一切関与しません。
修正方法3 — インストール可能な時間ベースのトリガーを使う
ユーザーのクリックではなくスケジュールで実行したい場合は、インストール可能なトリガーを設定してください。シンプルトリガー(onOpen、onEdit)とは異なり、インストール可能なトリガーは作成者の完全な権限で実行されます。
// トリガーを登録するために一度だけ実行する
function createTrigger() {
ScriptApp.newTrigger('syncFromOtherSheet')
.timeBased()
.everyHours(1)
.create();
}
function syncFromOtherSheet() {
const source = SpreadsheetApp.openById('SOURCE_SPREADSHEET_ID');
const target = SpreadsheetApp.getActiveSpreadsheet();
const values = source.getSheetByName('Export')
.getDataRange()
.getValues();
target.getSheetByName('Import')
.clearContents()
.getRange(1, 1, values.length, values[0].length)
.setValues(values);
}
拡張機能 → Apps Script → トリガーから登録するか、エディタでcreateTrigger()を一度実行してください。それ以降は、ユーザーの操作なしに毎時間自動で実行されます。
修正方法4 — データを関数の引数として渡す設計にリファクタリングする
根本的な設計を見直すことが本当の解決策になる場合もあります。カスタムファンクションが別のスプレッドシートにアクセスするのではなく、必要なデータを事前にインポート(トリガーや手動インポート経由)してローカルにキャッシュしておきます。すると数式は近くのセルを参照するだけで済みます。
// これの代わりに:
// =GET_REMOTE_PRICE("AAPL")
// これは内部でopenById()を呼び出そうとする...
// こうする:
// 1. トリガーが15分ごとに非表示シートのB列に価格を取得して保存する
// 2. 数式はそのキャッシュデータをVLOOKUPで参照する
// =VLOOKUP("AAPL", PriceCache!A:B, 2, FALSE)
このパターンはより堅牢です。データ取得と数式の評価を分離することで、再計算のたびにシートが外部サービスにアクセスしなくなります — 数百の数式を持つ大きなシートでは、この違いが顕著に現れます。
確認 — 修正が機能していることを確かめる
- 修正を適用したら、スプレッドシートをリロードしてください(
Ctrl+R/Cmd+R)。 - ロジックをメニューに移した場合は、まずApps Scriptエディタから関数を手動で実行してください。メニュー項目でテストする前に、正常に完了することを確認してください。
- トリガーを使用している場合は、拡張機能 → Apps Script → トリガーに移動し、実行後に「実行数」タブを確認してください。緑のチェックマークが表示されれば成功です。
getActiveSpreadsheet()に切り替えた場合は、セルにエラーではなく値が表示されるはずです。それでも失敗する場合は、実行ログ(表示 → 実行数)を開いてください — その下に別のエラーが隠れている可能性があります。
クイックリファレンス — カスタムファンクションで許可される操作
許可される操作許可されない操作
`SpreadsheetApp.getActiveSpreadsheet()``SpreadsheetApp.openById()`
セルの値の読み取りシートへの書き込み・変更
数学演算、文字列操作`GmailApp`、`DriveApp`
公開された`UrlFetchApp`の呼び出し(認証なし)認証が必要なAPIの呼び出し
`CacheService`、`Utilities``SpreadsheetApp.getUi()`
補足
カスタムファンクションとバックグラウンド同期を組み合わせたスプレッドシートツールを作成すると、すぐに複雑になります。どこで何が実行されるか — サンドボックスか完全な権限か — という明確なメンタルモデルを持っておくと、多くの試行錯誤を省けます。このような統合作業でデータ構造をデバッグする際には、ToolCraftを使ってJSONペイロードをローカルで検証することがあります。スプレッドシートのデータが機密情報である場合に重要な点として、何もアップロードされません。
すべてを貫くルール:ユーザーがOAuth同意ダイアログで「許可」をクリックする必要があるような操作は、カスタムファンクション内には置けません。その制約を前提に設計してください。制約と戦うのではなく、制約に沿った設計を心がけましょう。

