Googleタグマネージャー(GTM)を使っていると、トリガーや変数、バージョンの「メモ」欄に十分な説明が記載されていないことが多く、運用や引き継ぎ時に困ることがあります。
そこで、Googleの生成AI「Gemini」を活用し、GTMのトリガーや変数、バージョン履歴の内容から自動でメモを生成・追記するApps Scriptプログラムを作成しました。本記事では、その概要と使い方についてご紹介します。
プログラムの概要
今回ご紹介するスクリプトは、GTMのAPIとGemini APIを組み合わせて、以下の3つの用途で自動的にメモを生成・追記します。
- トリガーのメモ自動生成
- 変数のメモ自動生成
- バージョン履歴の変更サマリー自動生成
それぞれの内容は、GTMの「メモ」欄やバージョンの「説明」欄に直接書き込まれます。
背景・課題
- GTMの運用現場では、トリガーや変数の設定内容が複雑化しがち。
- 「メモ」欄が空欄のまま運用されることが多く、後から内容を把握するのが困難。
- 変更履歴の説明も省略されがちで、何のための変更か分かりづらい。
こうした課題を、AIによる自動要約・説明生成で解決することを目指しました。タグやトリガー、変数のメモ欄はそもそもデフォルトで非表示であり、個別に「メモを表示」を選択しないと表示されないことから、メモ機能が存在することを知らない人も多いかと思います。
今回作成したプログラムの主な機能
1. トリガと変数のメモを自動生成
- GTMのAPIから、トリガーや変数の情報を取得する。
- それぞれの内容をGeminiにプロンプトとして渡し、分かりやすい説明文を生成する。
- 生成した説明文を「メモ」欄に自動で保存する。
※現在、対象とする変数は、情報量が多いタイプ(カスタムJavaScript、DOM要素、ルックアップテーブル等)のみに限定。
2. バージョン履歴の変更サマリーを自動生成
- 最新バージョンと直前バージョンの内容を比較し、変更点の要約をGeminiで生成する。
- その要約をバージョンの「説明」欄に自動で保存する。
3. Gemini APIとの連携
- Google Apps ScriptからGemini APIを呼び出し、自然言語で分かりやすい説明を取得(利用するモデルはプログラムコード冒頭の
GEMINI_MODEL_NAME
を使って変更可能)。 - APIキーはApps Scriptのプロパティストアで管理。
今回のプログラムの使い方
1. 後述のプログラムコードを、Apps Scriptのプログラムコードにコピペする。
2. プログラムコード冒頭のGTM_URL
に、対象GTMワークスペースのURLを設定する。
3. Apps ScriptのプロパティにGemini APIキー(GEMINI_API_KEY
)を登録する。
4. スクリプトを実行する。
スクリプトを実行すると、下記の項目に説明が追記されます。
・トリガー、変数の「メモ」欄
・バージョンの「説明」欄
なお、バージョンは自動では公開されません。変更内容を確認し、ご自身でバージョンの公開を行ってください。
トリガーや変数を確認すると、下記のようなメモが追加されます。
GEMINI_API_KEYの取得方法と設定手順
1. Google AI Studio などからGemini APIキーを取得します。
「【2025年7月版】5分以内に完了! Google AI StudioでGemini APIキーを取得する」を参考にすると、分かりやすいです。
2. Google Apps Scriptのエディタ画面で「プロジェクトのプロパティ」→「スクリプトのプロパティ」タブを開きます。
3. 「+ 新しいプロパティを追加」から、
・プロパティ名:GEMINI_API_KEY
・値:取得したAPIキー
を入力し保存します。
これでスクリプトからGemini APIが利用可能になります。
「スクリプトのプロパティ」の設定画面は以下になります。
定期実行のススメ
週に1回などの頻度でこのスクリプトを実行することで、新しく追加されたトリガーや変数にも自動でメモが追記され、また新しい変更履歴にも要約メモが追加されます。
定期的な実行を運用フローに組み込むことで、GTMのドキュメント整備が自然と進み、属人化防止や引き継ぎの効率化に役立ちます。
プロンプトのカスタマイズについて
プログラムの冒頭に記載されているプロンプト(PROMPT_TRIGGER_NOTES
やPROMPT_VARIABLE_NOTES
など)は、必要に応じて自由に編集できます。より分かりやすく・実務に即したメモが生成されるよう、現場の運用や好みに合わせて調整してみてください。
もし良いプロンプト例ができた場合は、ぜひX(旧Twitter)などで共有いただけると嬉しいです。
工夫したポイント
- 情報量の多い変数タイプに限定:
説明生成の効果が高いと考えられる、情報量の多い変数タイプのみに対象を絞り、効率的な生成を実現しています。 - AI生成の出所を脚注で明記:
AIによる自動生成であることや、どのスクリプトから生成されたかといった情報を、脚注として自動で付け加える仕組みを設けています。 - 既存メモは上書きせず保護:
すでにメモや説明が入力されている項目(トリガー・変数・バージョン)については、上書きを避けてスキップする仕様とし、手動入力の内容が失われないように配慮しています。 - 非エンジニアでも変更しやすい構成:
プロンプトや設定内容はスクリプトの上部にまとめており、非エンジニアの方でも簡単に編集できるようにしています。
サンプルコード
// GTMのワークスペースを開いたときのURLを指定すること。
// 例: https://tagmanager.google.com/#/container/accounts/012345/containers/67890123/workspaces/456789/triggers
const GTM_URL = "https://tagmanager.google.com/#/container/accounts/47703/containers/509315/workspaces/1000357/triggers?orgId=7FHIbm8EQcS7p7ogxm-UEQ";
// メモの末尾に追記する脚注。
const FOOTNOTE = (() => `\n\nGenerated by Gemini, running on Apps Script; https://script.google.com/home/projects/${ScriptApp.getScriptId()}/edit`)();
// 対象とする変数タイプ。他の種類の変数タイプに広げることも可能だが、設定値の情報量が少ない変数(例えば定数)を指定したところで、
// Geminiから有意義なメモを生成することができないため、一部の変数タイプに絞っている。
// jsm: カスタムJavaScript, d: DOM要素, vis: 要素の表示, smm: ルックアップテーブル, remm: 正規表現の表
// 必要に応じて、その他の変数タイプも追加可能。
const TARGET_VARIABLE_TYPE = ["jsm", "d", "vis", "smm", "remm"];
const GEMINI_MODEL_NAME = "gemini-1.5-pro";
// トリガーのメモを生成する際に用いるプロンプト
const PROMPT_TRIGGER_NOTES = `
次のGoogleタグマネージャーのトリガーの挙動を読み解き、変数の「メモ」欄に残すべきテキストをまとめてください。
回答をそのままメモ欄に転記する想定で出力してください。
--------
\`\`\`
__JSON_STRING__
\`\`\`
`;
// 変数のメモを生成する際に用いるプロンプト
const PROMPT_VARIABLE_NOTES = `
次のGoogleタグマネージャーの変数の挙動を読み解き、変数の「メモ」欄に残すべきテキスト(どのような値を取得することができる変数であるか、使うときの注意点など)をまとめてください。
回答をそのままメモ欄に転記する想定で出力してください。
--------
\`\`\`
__JSON_STRING__
\`\`\`
`;
// バージョンの変更履歴のメモを生成する際に用いるプロンプト
const PROMPT_CHANGESET_MEMO = `
次のGoogleタグマネージャーの変更履歴(変更前と変更後)をもとに、どのような目的で変更が行われたかテキストでまとめてください。
回答をそのままバージョンのメモに転記する想定で出力してください。
--------
## 変更前
\`\`\`
__JSON_STRING_BEFORE__
\`\`\`
## 変更後
\`\`\`
__JSON_STRING_AFTER__
\`\`\`
`;
const execute_最新のバージョンのメモに変更サマリーを追記する = () => {
const headers = getVersionHeaders_();
if (headers.length == 0) {
Logger.log("変更履歴が1つも存在しないため、変更サマリーを生成できません。処理を中断します。");
return;
}
const currentVersion = getVersion_(headers[headers.length - 1].containerVersionId);
if (currentVersion.description) {
Logger.log("最新のバージョンにメモが記載されているため、処理を中断します。");
return;
}
const previousVersion = headers.length >= 2 ? getVersion_(headers[headers.length - 2].containerVersionId) : {};
const notes = getGeminiResponse_(
PROMPT_CHANGESET_MEMO
.replace("__JSON_STRING_BEFORE__", JSON.stringify(previousVersion))
.replace("__JSON_STRING_AFTER__", JSON.stringify(currentVersion))
);
TagManager.Accounts.Containers.Versions.update(Object.assign(currentVersion, {
// バージョンのメモだけは、"notes"属性ではなく、"description"属性が使われる様子。
description: `${notes}${FOOTNOTE}`,
}), `accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}/versions/${currentVersion.containerVersionId}`);
}
const execute_トリガーと変数にメモを追記する = () => {
// トリガーの処理
const triggers = getTriggers_();
triggers.filter((trigger) => {
return !trigger.notes;
}).forEach((trigger) => {
Logger.log(`トリガー[${trigger.name}]の処理を行います。`);
const notes = getGeminiResponse_(PROMPT_TRIGGER_NOTES.replace("__JSON_STRING__", JSON.stringify(trigger)));
TagManager.Accounts.Containers.Workspaces.Triggers.update(Object.assign(trigger, {
notes: `${notes}${FOOTNOTE}`,
}), `accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}/workspaces/${getParams_().WORKSPACE_ID}/triggers/${trigger.triggerId}`);
});
// 変数の処理
const variables = getVariables_();
variables.filter((variable) => {
// return ["remm"].includes(variable.type);
return TARGET_VARIABLE_TYPE.includes(variable.type);
}).filter((variable) => {
return !variable.notes;
}).forEach((variable) => {
Logger.log(`変数[${variable.name}]の処理を行います。`);
const notes = getGeminiResponse_(PROMPT_VARIABLE_NOTES.replace("__JSON_STRING__", JSON.stringify(variable)));
TagManager.Accounts.Containers.Workspaces.Variables.update(Object.assign(variable, {
notes: `${notes}${FOOTNOTE}`,
}),`accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}/workspaces/${getParams_().WORKSPACE_ID}/variables/${variable.variableId}`);
});
}
const getVersion_ = (version) => {
return TagManager.Accounts.Containers.Versions.get(`accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}/versions/${version}`)
}
const getVersionHeaders_ = (pageToken = null) => {
const response = TagManager.Accounts.Containers.Version_headers.list(`accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}`, {
pageToken: pageToken,
});
if (response.nextPageToken) {
return (response.containerVersionHeader || []).concat(getVersionHeaders_(response.nextPageToken));
} else {
return response.containerVersionHeader || [];
}
}
const getTriggers_ = (pageToken = null) => {
const response = TagManager.Accounts.Containers.Workspaces.Triggers.list(`accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}/workspaces/${getParams_().WORKSPACE_ID}`, {
pageToken: pageToken,
});
if (response.nextPageToken) {
return (response.trigger || []).concat(getTriggers_(response.nextPageToken));
} else {
return response.trigger || [];
}
}
const getVariables_ = (pageToken = null) => {
const response = TagManager.Accounts.Containers.Workspaces.Variables.list(`accounts/${getParams_().ACCOUNT_ID}/containers/${getParams_().CONTAINER_ID}/workspaces/${getParams_().WORKSPACE_ID}`, {
pageToken: pageToken,
});
if (response.nextPageToken) {
return (response.variable || []).concat(getVariables_(response.nextPageToken));
} else {
return response.variable || [];
}
}
const getParams_ = () => {
const paths = GTM_URL.split(/[#\?]/g)[1].split("/");
if (paths.length < 8) {
throw new Error("GTM_URLの値の形式が正しくありません。GTMのワークスペースを開いたときのURLを指定してください。");
}
return {
ACCOUNT_ID: paths[3],
CONTAINER_ID: paths[5],
WORKSPACE_ID: paths[7],
}
}
const getGeminiResponse_ = (prompt) => {
const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
if (!apiKey) {
throw new Error("GEMINI_API_KEYが設定されていません。Apps Scriptのプロジェクトの設定からGEMINI_API_KEYを設定してください。");
}
const url = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL_NAME}:generateContent?`
const options = {
'method': 'post',
'contentType': 'application/json',
'headers': {
'x-goog-api-key': apiKey
},
'payload': JSON.stringify({
'contents': [{
'parts': [
{ 'text': prompt },
],
}]
})
};
// API呼び出しと結果の取得
try {
const response = UrlFetchApp.fetch(url, options);
const jsonResponse = JSON.parse(response.getContentText());
const answerText = jsonResponse.candidates[0].content.parts[0].text;
return answerText;
}
catch (error) {
throw new Error(`回答を取得できませんでした。${error}`);
}
}
まとめ
本記事では、GTMの運用課題の1つである「ドキュメンテーション不足」を簡易的に解決するためのAI活用プログラムをご紹介しました。定期実行するようにしておくことで、新しく追加されたトリガーや変数にも自動でメモが追記されますし、新規のバージョンにもメモが自動で追加されるようになります。
Googleタグマネージャーの運用でお困りの方は、ぜひお問い合わせフォームからお問い合わせいただけますと幸いです。