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_NOTESPROMPT_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タグマネージャーの運用でお困りの方は、ぜひお問い合わせフォームからお問い合わせいただけますと幸いです。

お気軽にご質問、ご相談ください

関連タグ

山田良太

テクノロジー開発室長。チーフテクノロジーマネージャー。10年以上のプログラミング経験を活かして、Webマーケティングのテクノロジー領域(APIを使ったシステム開発や、タグ実装など)を中心に取り組む。

関連資料

【GA4/GTM】 正しいデータの取得やさらなる活用のためにできること

【GA4/GTM】 正しいデータの取得やさらなる活用のためにできること

  • GA4
  • GTM

関連サービス

関連ブログ