Workspace Flows のカスタムステップを試す 〜 Google Apps Script による機能拡張 〜

Flows

2025年4月の Google Cloud Next 2025で発表され、大きな話題を呼んだ「 Workspace Flows 」。ノーコードで Google Workspace 中心の業務フローを自動化できるこの機能が、発表から約7ヶ月を経て、ついにアルファ版として(管理者が有効化したドメインで)利用可能になりました。

Cloud Next 2025 はラスベガスの会場で参加したのですが、現地での Google Workspace マニアの仲間の間の会話は「Flows アツい」と話題で持ちきりでした。 当時の名称は「 Workspace 」なしの「 Flows 」でしたよね。AI 映像制作ツールの「 Flow 」というサービスが後日発表されて、どうなるのかと思いましたが、今のところ「 Workspace Flows 」で落ち着いているようですね。

Cloud Next で公開されたコンセプト映像だけで「早く触ってみたい!」と待ち望んでいた方も多いのではないでしょうか。

Google Cloud Next 2025 直後の興奮のレポートはこちらのブログをご覧ください。

MBSでは早いタイミングで検証する機会がありました。 今回は、単なるノーコードのフロー作成だけでなく、テックブログらしく(?) Gemini や Google Apps Script (以降 GAS) を連携させた「カスタムステップ」 の実装まで、踏み込んでレビューします。

Workspace Flows とは?

Workspace Flows は、Google Workspace ( Forms, Drive, Chat, Gmail 等 ) の操作をトリガーに、一連の業務プロセスを自動化するツールです。

これまでも、GAS のトリガーや AppSheet の Automation などで同様のことは実現できましたが、Flows の特徴は、コードを書かずにGUI でフローを視覚的に構築できる点にあります。これにより、いわゆる「市民開発者」でも業務自動化に着手しやすくなることが期待されています。 また、Asana、Mailchimp、Salesforce といったサードパーティーのサービスとのコネクタも用意されており、今後も拡充していくとのことです。 ( Gemini Enterprise に近づいていく感じもありますね。)

【基本編】ノーコードでフローを作ってみる

まずは基本操作です。
「 Google Forms で申請があったら、Chat に通知する」という典型的なフローを例にとってみましょう。

これまでの課題:

従来、Google Forms の通知は、標準の拡張機能では「申請があったこと」しか通知されませんでした。 申請内容(例:どの会議室が予約されたか)まで通知に含めたい場合、フォームごとに GAS を記述し、回答データを手動で整形してメールや Chat に送る必要があり、非常に面倒でした。

Workspace Flowsの優位点:

Workspace Flows では、このプロセスが劇的に改善されます。
これまで GAS の知識が必要だった「申請内容の詳細を通知に含める」作業が、コードを一切書かずに以下のステップだけで実現できます。

ここでは応用編で作成する Forms を利用して、社内表彰の申請があったら、Chat に「申請者名」と「申請内容」を送信するフローを作成します。

  • Starter [Step 1](トリガー) : 「 Google Forms 」の "When a form response comes in (フォーム送信時)" を選択。トリガーに利用する既存のフォームを指定します。
  • Actions [Step 2](アクション) : 「 Google Chat 」の "Send a message(メッセージを送信)" を選択。
  • Message : [ + Variables ] よりトリガー(Forms)の回答内容(「申請者名」や「申請内容」)を、 変数としてChatの本文に挿入。

たったこれだけで、GAS を書けない非エンジニアの方でも、フォームごとにカスタマイズされたリッチな通知フローを自分で構築できます。
これまでエンジニアに依頼する必要があった作業が現場で完結するようになる、この手軽さこそが Workspace Flows の大きな優位点です。

(実行結果 :)

【応用編】Gemini と GAS で「カスタムステップ」を開発する

基本編だけでは物足りません。 Workspace Flows の真価は、標準機能で足りない処理を GAS で自作できる「カスタムステップ」 にあると我々は考えています。

今回は、「ノーコードではできないこと」を盛り込むために、以下の技術を組み合わせた高度なシナリオに挑戦しました。

シナリオ :

これまで:フォームで社内表彰の申請をすると、申請内容を担当者に Chat で送信するフローを Workspace Flows で自動化。

これから:フォームで社内表彰の申請をすると、 カスタム Gem で申請理由を基に正式な「表彰文」を作成。 GAS がその表彰文を使ってスライドから「表彰状画像」を動的に生成し、 Chat に Webhook でリッチなカード通知を送る

最終的な処理フロー

STEP 1 : トリガー(Google Forms)の準備

Workspace Flows に渡すためのデータ(名前・理由)を受け取るシンプルなフォームを用意します。

  • 質問1: 表彰者の名前 (Name) - 記述式
  • 質問2: 表彰理由 (Reason) - 記述式(箇条書きでOK、など)

(Tips: 以下のGASをフォームのスクリプトエディタで実行すれば、フォームを自動生成できます)

(参考)フォームセットアップ用 GAS コード
      
/**
 * Workspace Flowsテスト用のフォーム項目をセットアップします。
 */
function setupForm() {
  const form = FormApp.getActiveForm();
  
  // フォームのタイトルと説明を設定
  form.setTitle('表彰状 発行申請フォーム');
  form.setDescription('Workspace Flows と GAS連携のテスト用フォームです。');
  
  // 既存の項目を一旦クリア(必要に応じて)
  const items = form.getItems();
  for (let i = items.length - 1; i >= 0; i--) {
    form.deleteItem(items[i]);
  }
  
  // 1. 表彰者の名前 (Name)
  form.addTextItem()
    .setTitle('表彰者の名前 (Name)')
    .setHelpText('表彰状に記載するフルネームを入力してください。')
    .setRequired(true);
    
  // 2. 表彰理由 (Reason)
  form.addTextItem()
    .setTitle('表彰理由 (Reason)')
    .setHelpText('「〇〇のプロジェクト貢献」など、具体的な理由を入力してください。')
    .setRequired(true);
    
  Logger.log('フォームのセットアップが完了しました。');
  FormApp.getUi().alert('フォームのセットアップが完了しました。');
}
      

STEP 2 : カスタム Gem の準備

フォームから送られた簡単な「表彰理由」を元に「表彰分」を作成するカスタム Gem を Gemini アプリで作成します。

(参考)Gem カスタム指示 与えられた内容をもとにして、表彰状に相応しい、褒め称える文章の本文を簡潔に作成してください。以下のルールに従って応答してください。 目的と目標: * ユーザーが提供した情報に基づき、表彰状の本文として適切で、簡潔かつ称賛に満ちた文章を作成すること。 * 提供されたキーワードや情報を最大限に活用し、感動的で心に残る表彰状の文章を提供すること。 * ユーザーからの追加の質問なしに、即座に表彰状の本文を提案すること。 行動と規則: 1) 入力処理: a) ユーザーから提供される内容は、表彰する対象、功績、または感謝の意を示すための簡単なキーワードやフレーズであることを想定してください。 b) ユーザーからの入力に対して、追加の質問(例:「誰に送るのですか?」「功績の詳細は何ですか?」)は一切行わないでください。 c) 入力が不足している場合は、表彰状の文脈に合うように適切な言葉や詳細を想像し、補完してください。 2) 表彰状の本文の作成: a) 作成する文章は、表彰状(または感謝状)の本文として相応しい、格式と称賛のトーンを持つものにしてください。 b) 本文は簡潔に、しかし感動的に、対象の功績や貢献を称える内容にしてください。 c) 一般的な表彰状の構成(例:冒頭の称賛、具体的な功績の説明、結びの言葉)を意識して作成してください。 d) 応答は、表彰状の本文のみを提示し、それ以外の前置きや挨拶、説明などは一切含まないでください。 全体的なトーン: * 厳粛で敬意を表すトーンを使用してください。 * 功績や努力を心から称賛する熱意を表現してください。 * 日本語の美しさと格式を重視した表現を用いてください。

STEP 3 : GAS カスタムステップの実装

ここが今回の記事の核となる部分です。 GAS を Workspace Flows の「ステップ」として認識させるには、特定のお作法に従う必要があります。

1. マニフェストファイル (appsscript.json)

最も重要なのがマニフェストファイルです。 addOns.flows.workflowElements を定義することで、自作の GAS が Workspace Flows の GUI に表示されるようになります。

    
{
  "timeZone": "Asia/Tokyo",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/presentations",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/script.locale"
  ],
  "addOns": {
    "common": {
      "name": "MBS Tech Nexus Tools",
      "logoUrl": "https://www.gstatic.com/images/branding/product/1x/googleg_48dp.png",
      "useLocaleFromApp": true
    },
    "flows": {
      "workflowElements": [
        {
          "id": "generateCertificate",
          "state": "ACTIVE",
          "name": "表彰状を作成 (Generate Certificate)",
          "description": "スライドテンプレートから表彰状画像を生成します。",
          "workflowAction": {
            "inputs": [
              {
                "id": "name",
                "description": "表彰者の名前 (Recipient's Name)",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              },
              {
                "id": "reason",
                "description": "表彰理由 (Reason for Award)",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "outputs": [
              {
                "id": "fileId",
                "description": "生成された画像のID (Generated Image ID)",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              },
              {
                "id": "generatedAt",
                "description": "生成日時 (Timestamp of Generation)",
                "cardinality": "SINGLE",
                "dataType": {
                  "basicType": "STRING"
                }
              }
            ],
            "onConfigFunction": "configureCertificateStep",
            "onExecuteFunction": "generateCertificateStep"
          }
        }
      ]
    }
  }
}  
    

ポイント :

  • カスタムステップ開発の最大の特徴でありポイントは、このマニフェストファイル にあります。
  • inputs / outputs: ここで、このステップが Workspace Flows から受け取る変数(name, reason)と、Workspace Flows に返す変数( File ID )を明確に定義します。
  • onConfigFunction: Workspace Flows のエディタ上で、ユーザーが設定(変数のマッピング)を行うための設定画面( UI )を生成する関数( configureCertificateStep )を指定します。
  • onExecuteFunction: フローが実行された時に、実際にロジック(画像生成など)を動かす実行関数( generateCertificateStep )を指定します。

このように、GAS 側で「入出力」「設定 UI 」「実行ロジック」の3つを紐付けて定義しておくことで、Workspace Flows 側はこれを一つの独立した「部品(ステップ)」として扱えるようになるのです。

2. GASコード ( certificate_flow.gs )

appsscript.json で指定した関数を実装します。

表彰状画像作成用 GAS コード
      
/**
 * ------------------------------------------------------------------
 * 実行関数 (onExecuteFunction)
 * ------------------------------------------------------------------
 * スライドから画像を生成し、Driveに保存後、そのFile IDをFlowsに返します。
 * appsscript.json の 'onExecuteFunction' に指定されています。
 *
 * @param {Object} event - Flowsから渡される実行イベントオブジェクト
 * @return {Object} - 出力変数を含むRenderActionオブジェクト
 */
function generateCertificateStep(event) {
  
  // 1. Flowsからの入力値を受け取る
  // event.workflow.actionInvocation.inputs から取得
  const inputs = event.workflow.actionInvocation.inputs;
  const name = inputs["name"].stringValues[0] || '名無し';
  const reason = inputs["reason"].stringValues[0] || '貢献への感謝';
  
  // ▼▼▼ 読者が設定する箇所 ▼▼▼
  const TEMPLATE_ID = '【ここにスライドのテンプレートID】';
  const FOLDER_ID = '【ここに保存先フォルダID】';
  // ▲▲▲ 設定ここまで ▲▲▲
  
  // 2. スライドをコピーして編集
  const folder = DriveApp.getFolderById(FOLDER_ID);
  const templateFile = DriveApp.getFileById(TEMPLATE_ID);
  const newFile = templateFile.makeCopy('表彰状_' + name, folder);
  const slideId = newFile.getId();
  
  const presentation = SlidesApp.openById(slideId);
  presentation.replaceAllText('{{name}}', name);
  presentation.replaceAllText('{{reason}}', reason);
  presentation.saveAndClose();
  
  // 3. スライドを画像(PNG)としてFetchし、Blobを取得
  const url = `https://docs.google.com/presentation/d/${slideId}/export/png`;
  const options = { headers: { Authorization: `Bearer ${ScriptApp.getOAuthToken()}` } };
  const blob = UrlFetchApp.fetch(url, options).getBlob().setName('Certificate.png');

  // 4. 画像ファイルをDriveに(共有せずに)保存
  const imageFile = folder.createFile(blob);
  
  // 5. 生成したファイルの「ID」を取得
  const fileId = imageFile.getId();

  // 6. 後始末(スライドの元ファイルはゴミ箱へ)
  newFile.setTrashed(true);

  // 7. Flowsに出力変数を構築
  // appsscript.json の "outputs" で定義したキーと一致させる
  const variableDataMap = {
    "fileId": AddOnsResponseService.newVariableData()
        .addStringValue(fileId), // File ID を返す
        
    "generatedAt": AddOnsResponseService.newVariableData()
        .addStringValue(new Date().toISOString())
  };

  // 8. ヘルパー関数を使って正式な形式で戻り値を返す
  return outputVariables(variableDataMap);
}


/**
 * ------------------------------------------------------------------
 * 設定関数 (onConfigFunction)
 * ------------------------------------------------------------------
 * Flowsのエディタでステップを追加・編集する際の設定UI(カード)を生成します。
 * appsscript.json の 'onConfigFunction' に指定されています。
 *
 * @param {Object} event - Flowsから渡される設定イベントオブジェクト
 * @return {Object} - UI(カード)をプッシュするアクションオブジェクト
 */
function configureCertificateStep(event) {
  
  // カードをJSONオブジェクトとして直接構築
  var card = {
    "sections": [
      {
        "header": "ステップ設定: 表彰状を作成",
        "widgets": [
          {
            // 1. 入力項目: 名前
            "textInput": {
              "name": "name", // appsscript.json の input.id と一致
              "label": "表彰者の名前 (Name)",
              "hostAppDataSource" : {
                "workflowDataSource" : {
                  "includeVariables" : true // 変数ピルの入力を許可
                }
              }
            }
          },
          {
            // 2. 入力項目: 理由
            "textInput": {
              "name": "reason", // appsscript.json の input.id と一致
              "label": "表彰理由 (Reason)",
              "hostAppDataSource" : {
                "workflowDataSource" : {
                  "includeVariables" : true // 変数ピルの入力を許可
                }
              }
            }
          },
          {
            // 3. 保存ボタン (設定画面には必須)
            "buttonList": {
              "buttons": [
                saveButton() // ヘルパー関数を呼び出し
              ]
            }
          }
        ]
      }
    ]
  };
  
  // ヘルパー関数を使い、カードをプッシュして返します
  return pushCard(card);
}


/*
 * ===================================================================
 * ヘルパー関数 (Flows Add-on のお作法)
 * ===================================================================
 */

/**
 * [ヘルパー] 実行ステップの出力変数をFlowsに返すためのレスポンスを構築します。
 * @param {Object} variableDataMap - 出力する変数のマップ
 * @return {Object} - RenderActionオブジェクト
 */
function outputVariables(variableDataMap) {
  const workflowAction = AddOnsResponseService.newReturnOutputVariablesAction()
    .setVariableDataMap(variableDataMap);

  const hostAppAction = AddOnsResponseService.newHostAppAction()
    .setWorkflowAction(workflowAction);

  const renderAction = AddOnsResponseService.newRenderActionBuilder()
    .setHostAppAction(hostAppAction)
    .build();

  return renderAction;
}

/**
 * [ヘルパー] 設定UIとして新しいカードをプッシュするレスポンスを構築します。
 * @param {Object} card - JSONで定義されたカードオブジェクト
 * @return {Object} - アクションオブジェクト
 */
function pushCard(card) {
  return {
      "action": {
        "navigations": [{
            "push_card": card
          }
        ]
      }
  };
}

/**
 * [ヘルパー] 設定UIの「保存」ボタンを構築します。
 * @return {Object} - ボタンウィジェットのJSONオブジェクト
 */
function saveButton() {
  return {
      "text": "Save",
      "onClick": {
        "hostAppAction" : {
          "workflowAction" : {
            "saveWorkflowAction" : {}
          }
        }
      },
    };
}
      

コードのポイント:

  • generateCertificateStep: メインの処理関数。 event.workflow.actionInvocation.inputs で入力値を受け取り、画像生成後、AddOnsResponseService を使って File ID を返します。
  • configureCertificateStep: 設定 UI を JSON で定義します。 hostAppDataSource を使うことで、Workspace Flows の変数をマッピングできる入力欄になります。

3. カスタムステップのデプロイ

コードが完成したら、Workspace Flows から認識させるために「デプロイ」が必要です。
(今回はアルファ版の検証として「テストデプロイ」機能を使います。)

  • GAS エディタの上部にある「 デプロイ > デプロイをテスト 」を選択します。
  • インストール 」をクリックします。

    STEP 4: Chat Webhook の設定

    GAS から返ってきた File ID を Chat に Webhook で通知します。

    • まず、通知を送りたい Google Chat のスペースを開き、「スペースの設定」から「アプリと統合」を選択し、「 Webhook を管理」から新しい Webhook URL を作成・コピーしておきます。

    STEP 5: Workspace Flows 側での組み立て

    GAS をデプロイ(テストデプロイ)すると、Workspace Flows のエディタの「ステップを追加」メニューに、自作の「表彰状を作成 ( Generate Certificate )」が表示されるようになります。

    • Starter(トリガー)に Google Forms "When a form response comes in" を選択。参照するフォームを指定。
    • Actions に "Ask a Gem" を追加。Step 2 で作成した Gem を指定。フォームの「表彰理由」を渡す。
    • 「表彰状を作成」ステップを追加。
    • 設定画面で、フォームからの「表彰者」 Gem の出力の「表彰文」 を関数の引数としてマッピングします。
    • "Send a webhook" ステップを追加します。
    (参考)Webhook Payload
      {
      "cardsV2": [
        {
          "cardId": "certificate-card-link",
          "card": {
            "header": {
              "title": "🏆 表彰状が発行されました!",
              "subtitle": "おめでとうございます!",
              "imageUrl": "https://fonts.gstatic.com/s/i/googlematerialicons/emoji_events/v6/black-24dp/1x/gm_emoji_events_black_24dp.png",
              "imageType": "CIRCLE"
            },
            "sections": [
              {
                "widgets": [
                  {
                    "textParagraph": {
                      "text": "Great Job!
    フォームからの申請に基づき、表彰状が発行されました。" } }, { "buttonList": { "buttons": [ { "text": "表彰状ファイルを開く", "onClick": { "openLink": { "url": "https://docs.google.com/file/d/【Step3:生成された画像のID(Generated Image ID)】/view" } } } ] } } ] } ] } } ] }

    (実行結果 :)

    まとめ

    Workspace Flows は、ノーコードの GUI による手軽さと、GAS による高度な拡張性を併せ持つ、非常に強力なプラットフォームだと感じました。

    特に、「カスタムステップ」は、エンジニアが「便利な部品」(今回は Gemini 連携+画像生成 )を作成し、それを現場の担当者が Workspace Flows で自由に組み合わせる、という理想的な「市民開発」の姿を実現してくれそうです。

    アルファ版のため、まだ動作が不安定な部分や、ドキュメントが追いついていない部分も見受けられましたが、今後の正式リリースが非常に楽しみです。

    MBS Tech Nexus では、引き続き最新の Workspace 機能を検証し、実践的な知見を発信していきます。

Next Post Previous Post