GASで自作のカスタム関数を作る方法
スプレッドシートで=MYFUNC()
毎回同じ長い数式をコピーしている——それは自作の関数1本にまとめられます。GASでfunctionを書くだけで、=TAXIN(A2)のようにセルから呼べるカスタム関数(自作のシート関数)が作れます。基本の書き方から、範囲の受け取り、JSDocでの補完表示、そして「外部APIが呼べない」という重要な制約と回避策まで動くコードで解説します。
Table of Contents
カスタム関数とは|セルから呼べる自作関数
カスタム関数(custom function)とは、スプレッドシートのセルに=関数名(引数)と書いて呼び出せる自作の関数です。作り方は簡単で、スプレッドシートのメニュー「拡張機能 › Apps Script」を開き、functionを定義してreturnで値を返すだけ。保存すれば、すぐそのシートで使えます。
向いているのは、社内独自の計算ルールや、標準関数の組み合わせが長くなりすぎる整形処理です。数式が=IF(ISBLANK(A2),"",ROUNDDOWN(A2*1.1))のように長くなってきたら、関数1本にまとめる合図です。
カスタム関数が向く処理
独自の計算ルール、文字列の整形、日付の変換、複雑な条件分岐。権限のいらない「計算だけ」の処理。
向かない処理
外部API・Gmail・Driveの呼び出し、セルへの書き込み、数十秒かかる重い集計。通常の関数として実行する。
最初の1本を作る|=TAXIN()で税込計算
まずは税抜金額から税込金額を返す関数を作ります。引数はセルの値がそのまま渡ってきます。省略可能な引数はtypeofで判定して既定値を入れておくと、=TAXIN(A2)でも=TAXIN(A2, 0.08)でも動きます。
/**
* 税抜金額から税込金額を計算する。
* セルには =TAXIN(A2) または =TAXIN(A2, 0.08) と書く。
*/
function TAXIN(price, rate) {
if (price === "" || price === null) return ""; // 空セルは空のまま返す
if (typeof price !== "number") {
throw new Error("金額は数値で指定してください"); // セルに #ERROR! と理由が出る
}
const taxRate = typeof rate === "number" ? rate : 0.1; // 既定は10%
return Math.floor(price * (1 + taxRate)); // 端数は切り捨て
}ポイントは2つです。1つは空セル対策で、何も入っていないセルを渡すと空文字が来るため、そのまま空を返します。もう1つはエラーの伝え方で、throw new Error()を使うとセルに#ERROR!が表示され、カーソルを合わせるとメッセージが読めます。
関数名は大文字にする決まりはありませんが、シート関数と並べたときに見分けやすいので大文字が慣習です。SUMなどの既存関数と同じ名前は使えません。また末尾がアンダースコアの関数(calc_)はGAS内部でプライベート扱いになり、セルから呼べない点に注意してください。
範囲を受け取って一気に処理する
カスタム関数は、引数に範囲(A2:A100)を渡すこともできます。その場合、引数には二次元配列(行と列の入れ子になった配列)が入ってきます。1セルなら値そのもの、範囲なら配列——この2パターンに対応しておくと、どちらの書き方でも動く関数になります。
/**
* 引数が1セルでも範囲でも動くカスタム関数。
* =TAXIN_RANGE(A2:A100) のように範囲をまとめて渡せる。
*/
function TAXIN_RANGE(input, rate) {
const taxRate = typeof rate === "number" ? rate : 0.1;
// 範囲が渡されると input は二次元配列になる
if (Array.isArray(input)) {
return input.map((row) =>
row.map((cell) => (typeof cell === "number" ? Math.floor(cell * (1 + taxRate)) : ""))
);
}
// 単一セルのときは値がそのまま渡ってくる
return typeof input === "number" ? Math.floor(input * (1 + taxRate)) : "";
}範囲でまとめて渡す書き方は、行ごとに数式をコピーするより高速です。カスタム関数は1つずつ独立して実行されるため、1000行に1000個の数式を置くと呼び出しも1000回になります。1つの数式で範囲を受け取り、二次元配列を返す形にすれば呼び出しは1回で済みます。
複数セルに展開する|二次元配列で返す
戻り値を二次元配列にすると、1つの数式から隣接する複数セルへ結果が自動で展開されます。住所を「都道府県」と「それ以降」に分ける例で見てみます。
/**
* 住所文字列から「都道府県 / 市区町村以降」の2列に分けて返す。
* =SPLIT_ADDRESS(A2) と1セルに書くと、右隣のセルまで自動で展開される。
*/
function SPLIT_ADDRESS(address) {
if (typeof address !== "string" || address === "") return [["", ""]];
const matched = address.match(/^(.+?[都道府県])(.*)$/);
if (!matched) return [["", address]];
// 二次元配列で返すと、そのまま複数セルに展開される
return [[matched[1], matched[2]]];
}[["東京都", "千代田区…"]]のように「1行2列」の配列を返すと、数式を入れたセルとその右隣に値が入ります。展開先のセルに既にデータがあると#REF!エラーになるため、出力範囲は空けておいてください。
返せる値・返せない値
返せるのは数値・文字列・真偽値・日付、そしてそれらの配列です。オブジェクト({ a: 1 })はそのままでは表示できないため、JSON.stringify()で文字列にするか、必要な値だけ取り出して返します。また、カスタム関数の中からsetValueで他のセルを書き換えることはできません。あくまで「値を返すだけ」の関数です。
JSDocで補完と説明を出す
関数の上にJSDoc(/** ... */形式のコメント)を書き、@customfunctionを付けると、セルで関数名を打ったときに標準関数と同じように候補と説明が表示されます。自分以外も使うシートでは、これがあるだけで問い合わせが減ります。
/**
* 社員番号を「LS-0001」の形式に整形します。
*
* @param {number|string} id 社員番号(例: 1)
* @param {string} [prefix] 接頭辞(省略時は "LS")
* @return {string} 整形後の社員コード
* @customfunction
*/
function EMPLOYEE_CODE(id, prefix) {
const head = typeof prefix === "string" && prefix !== "" ? prefix : "LS";
const num = String(id).replace(/[^0-9]/g, "");
if (num === "") return "";
return head + "-" + ("0000" + num).slice(-4);
}1行目の説明文がそのまま関数の説明として、@paramが各引数の説明として表示されます。角かっこ付きの[prefix]は「省略可能な引数」を意味します。
使えないサービスがある|30秒制限と回避策
ここが最大のつまずきどころです。カスタム関数は「ユーザーの認可(許可)が必要なサービス」を呼び出せません。具体的にはUrlFetchApp(外部API呼び出し)、GmailApp、DriveApp、CalendarAppなどです。カスタム関数は匿名のユーザーとして実行されるため、権限を必要とする処理が許可されていません。
// 動かない例: カスタム関数からは認可が必要なサービスを呼べない
function NG_FETCH(url) {
const res = UrlFetchApp.fetch(url); // ← 権限エラーになる
return res.getContentText();
}
function NG_MAIL_COUNT(label) {
return GmailApp.search("label:" + label).length; // ← これも不可
}
// 使えるもの: 計算・文字列処理・Utilities・日付など、権限のいらない処理
function FORMAT_YMD(date) {
if (!(date instanceof Date)) return "";
return Utilities.formatDate(date, "Asia/Tokyo", "yyyy/MM/dd"); // これはOK
}つまり「セルに=AI(A2)と書いたらAIが要約してくれる関数」は、素直な実装では作れません。実行時間も約30秒で打ち切られるため、外部APIの応答を待つ用途とは相性が悪いのが実情です。
現実的な回避策は、カスタム関数をやめて「メニューやトリガーから通常の関数を実行し、結果を値としてセルに書き込む」方式にすることです。この形ならUrlFetchAppも使え、6分の実行時間を使えて、結果が値として残るので再計算で消える心配もありません。
// カスタム関数の代わりに、メニューから実行して結果を「値」で書き込む
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("独自ツール")
.addItem("要約を一括生成", "fillSummaries")
.addToUi();
}
function fillSummaries() {
const sheet = SpreadsheetApp.getActiveSheet();
const values = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1).getValues();
const out = values.map((row) => {
const text = row[0];
if (!text) return [""];
return [callExternalApi(text)]; // ここではUrlFetchAppを使える
});
sheet.getRange(2, 2, out.length, 1).setValues(out); // 結果は値として保存される
}
function callExternalApi(text) {
const res = UrlFetchApp.fetch("https://example.com/api", {
method: "post",
contentType: "application/json",
payload: JSON.stringify({ text: text }),
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) return "エラー";
return JSON.parse(res.getContentText()).result;
}見た目は数式ほど手軽ではありませんが、実務ではこちらの方が安定します。API呼び出しの結果が「毎回再計算される数式」ではなく「確定した値」として残るのは、コストの面でも大きな利点です。
再計算されないときの対処
「コードを直したのにセルの値が変わらない」——これもよくある相談です。スプレッドシートは引数の値が変わったときにカスタム関数を再計算します。逆に言えば、引数が同じままなら、関数の中身を書き換えても再計算されません。
対処法は3つあります。(1)対象セルを選んで数式バーでEnterを押し直す、(2)数式をいったん削除して貼り直す、(3)更新用のダミーセルを引数に加えておく、です。3つ目は定期的に再計算させたい関数で特に有効です。
/**
* 再計算のクセに対応する例。
* 第2引数に更新用のダミーセル(例: $Z$1)を渡し、
* その値を変えると強制的に再計算される。
*/
function RANDOM_PICK(range, refresh) {
const items = (Array.isArray(range) ? range : [[range]])
.flat()
.filter((v) => v !== "" && v !== null);
if (items.length === 0) return "";
const index = Math.floor(Math.random() * items.length);
return items[index];
}
// セルには =RANDOM_PICK(A2:A20, $Z$1) と書く。
// $Z$1 を書き換えるたびに引数が変わるため、再計算される。なお、引数を取らない関数(=MY_NOW()のような現在時刻を返すもの)は、ファイルを開き直すまで更新されないことがあります。時刻が必要なら標準のNOW()を引数として渡すか、時間主導トリガーで値を書き込む方式を選んでください。
まとめ
カスタム関数は、functionを書いてreturnするだけで作れる、いちばん手軽なGASの入口です。範囲を受け取って二次元配列で返せば大量行も1回の呼び出しで処理でき、JSDocに@customfunctionを付ければ標準関数のように候補も出ます。
一方で「認可が必要なサービスは呼べない」「約30秒で打ち切られる」「引数が変わらないと再計算されない」という3つの制約は必ず押さえてください。外部APIやAIを絡めたい場合は、カスタム関数ではなくカスタムメニューやトリガーから実行し、結果を値として書き込む設計に切り替えるのが確実です。用途に合わせて使い分ければ、シートの数式はぐっと短く、読みやすくなります。
よくある質問
スプレッドシートのセルに「=関数名(引数)」と書いて呼び出せる、自作の関数のことです。GASのエディタでfunctionを定義し、returnで値を返すだけで作れます。SUMやVLOOKUPと同じ感覚で使え、社内独自の計算ルールや整形処理を1つの関数にまとめられます。
使えません。カスタム関数は「認可(ユーザーの許可)が必要なサービス」を呼び出せない仕様のため、GmailApp・UrlFetchApp・DriveAppなどはエラーになります。外部APIやAIを呼びたい場合は、カスタム関数ではなくカスタムメニューやトリガーから通常の関数を実行し、結果をセルに書き込む方式にします。
あります。カスタム関数は約30秒を超えると「内部エラー」や実行中の表示のまま止まります。通常のGAS関数の6分制限より短いため、重い集計や大量ループはカスタム関数に向きません。処理が重いときは通常の関数として実行し、結果を値としてセルに書き込む方が確実です。
スプレッドシートは「引数の値が変わったとき」だけカスタム関数を再計算するためです。引数を取らない関数や、関数の中身だけを書き換えた場合は再計算されません。セルを編集し直す、引数にダミーのセル参照を持たせる、いったん数式を削除して入れ直す、といった方法で再計算されます。
SUMやIFなど既存のシート関数と同じ名前は使えません。また末尾がアンダースコア(例: myFunc_)の関数はGAS内部でプライベート扱いになり、セルからは呼び出せません。A1やR1C1のようにセル参照と読める名前も避けてください。英字で始まる分かりやすい名前を付けるのが安全です。
出せます。二次元配列([[値, 値], [値, 値]]の形)をreturnすると、隣接するセルへ自動的に展開されます。展開先にデータが入っていると「#REF!」になるため、出力範囲を空けておく必要があります。1行だけ返したいときも[[a, b, c]]のように二次元で返します。
GAS開発・業務Webシステムを
相談する。
独自の計算ルールを関数化したい、長くなりすぎた数式を整理したい、AI連携までまとめて自動化したい——実務で使えるGAS開発をご相談いただけます。