Reading Google Chat Spaces and Messages in Google Apps Script just got significantly easier!

In Episode 31 of the Google Workspace Developer News, an interesting announcement removes a major administrative hurdle for Google Apps Script developers interacting with Google Chat.

“You no longer need to manually configure a Google Chat app in the Google Cloud console for integrations that only use the Chat API to read data on behalf of a user using OAuth. For read-only actions, such as getting a space or listing messages, you now only need to enable the Chat API and create an OAuth client.”

The simplified Chat API setup means developers no longer need to configure a full Google Chat App in a standard Google Cloud project just to perform read-only actions. Previously, reading space messages via the Chat Advanced Service required detaching your script from the default GCP project, linking a standard GCP project, configuring an OAuth consent screen and formally enabling the Google Chat API.

Now, retrieving data is immediate. To list your Chat Spaces and read messages, you can add the Chat Advanced Service in your script editor and declare the read-only scopes in your appsscript.json manifest:

{
  "timeZone": "Europe/London",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Chat",
        "serviceId": "chat",
        "version": "v1"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/chat.messages.readonly",
    "https://www.googleapis.com/auth/chat.spaces.readonly"
  ]
}


Once your manifest is configured, you can use the Chat service to list the spaces you are a member of and the most recent messages in a specified space. Here are two example functions you can use:

/**
 * Lists the Google Chat spaces the authenticated user is a member of.
 * * Prerequisites:
 * 1. Enable the Google Chat API in Apps Script Advanced Services.
 * 2. Add the following OAuth scope to your appsscript.json manifest:
 * "https://www.googleapis.com/auth/chat.spaces.readonly"
 */
function listMySpaces() {
  try {
    let pageToken = null;
    do {
      // Call the Chat Advanced Service to list spaces
      const response = Chat.Spaces.list({
        pageSize: 50, // Adjust to retrieve more or fewer spaces per page
        pageToken: pageToken
      });

      if (!response.spaces || response.spaces.length === 0) {
        console.log("No spaces found.");
        break;
      }

      // Iterate through and log the retrieved spaces
      for (const space of response.spaces) {
        console.log(`Space Name: ${space.displayName || 'Unnamed Space'}`);
        console.log(`Resource ID: ${space.name}`);
        console.log(`Type: ${space.spaceType}`);
        console.log("-----------------------------------------");
      }
      
      // Update the page token to retrieve the next page of results
      pageToken = response.nextPageToken;
    } while (pageToken);
    
  } catch (err) {
    // Log any errors that occur during the API call
    console.error("Failed to fetch spaces with error: %s", err.message);
  }
}

/**
 * Lists the most recent messages in a Google Chat space.
 * * Prerequisites:
 * 1. Enable the Google Chat API in Apps Script Advanced Services.
 * 2. Add the following OAuth scope to your appsscript.json manifest:
 * "https://www.googleapis.com/auth/chat.messages.readonly"
 * * @param {string} spaceName The resource name of the space (e.g., "spaces/AAAAAAAAAAA").
 */
function listSpaceMessages(spaceName = "spaces/YOUR_SPACE_ID") {
  try {
    // Call the Chat Advanced Service to list messages
    const response = Chat.Spaces.Messages.list(spaceName, {
      pageSize: 10 // Adjust to retrieve more or fewer messages
    });

    // Check if the space has any messages
    if (!response.messages || response.messages.length === 0) {
      console.log("No messages found in this space.");
      return;
    }

    // Iterate through and log the retrieved messages
    for (const message of response.messages) {
      // Note: When authenticating as a user rather than a Chat app, 
      // the API only returns the sender's 'name' (resource ID) and 'type'.
      // Cross-reference this ID using the Admin SDK Directory or People API to retrieve display names.
      console.log(`Sender ID: ${message.sender ? message.sender.name : 'Unknown'}`);
      console.log(`Text: ${message.text}`);
      console.log(`Created At: ${message.createTime}`);
      console.log("-----------------------------------------");
    }
    
  } catch (err) {
    // Log any errors that occur during the API call
    console.error("Failed to fetch space messages with error: %s", err.message);
  }
}


This change drastically reduces the setup time for getting personal access to your Space data and messages. Happy scripting!!!

Navigating the Gatekeepers: A guide to Google OAuth verification for Google Workspace Add-on developers

How to pass Google’s OAuth verification process for your Workspace add-on—branding requirements, privacy policy, terms of service, scopes justification, and the demo video.

Over the years on Pulse, we have featured a number of guides and resources aimed at helping developers navigate the complexities of Google’s authorisation requirements. However, given it has been a while since we last touched on this topic, and considering how frequently the process can be a stumbling block for the community, I thought it was a subject worth revisiting.

If you have ever attempted to publish a Google Workspace add-on, you likely know the specific mix of excitement and trepidation that comes with the submission process. While building the functionality is one hurdle, ensuring your application meets Google’s rigorous security and branding standards is quite another. One area that frequently trips up even seasoned developers is OAuth verification.

In a recent post on the ShipAddons blog, Manuel Napoli shares a step-by-step guide to getting through Google’s OAuth verification. Having navigated this process multiple times to launch his own tools, Manuel provides a comprehensive look at what Google reviewers actually expect, offering a practical checklist to help you get verified on your first attempt.

The guide breaks down the requirements into four primary areas: branding, privacy policy, terms of service, and scope justification. Rather than rehashing the technical minutiae here, Manuel’s post serves as a comprehensive “pre-flight” checklist for anyone ready to hit the submit button.

It is also interesting to see this content coming from ShipAddons. For those unfamiliar, ShipAddons is a production-ready service designed for building premium Google Workspace add-ons with React and Supabase user management integrations. It aims to solve many of the recurring “pain points” we often discuss in the community, specifically handling the “plumbing” of a commercial application.

For developers looking to transition from a single-purpose script to a monetised solution, having these systems pre-configured can significantly reduce the time spent on infrastructure.

Source: Google OAuth Verification: A Step-by-Step Guide for Add-on Developers

Beyond external libraries: Google Workspace document and data processing with Gemini Code Execution

Why use a dedicated app when you can simply ask Gemini to write and run the Python code for you? A look at the power of Google Apps Script and GenAI

For many Google Workspace developers, handling complex file formats or performing advanced data analysis has traditionally meant navigating the limitations of Apps Script’s built-in services. We have previously featured solutions for these challenges on Pulse, such as merging PDFs and converting pages to images or using the PDFApp library to “cook” documents. While effective, these methods often rely on loading external JavaScript libraries like pdf-lib, which can be complex to manage and subject to the script’s memory and execution limits.

While users of Gemini for Google Workspace may already be familiar with its ability to summarise documents or analyse data in the side panel, those features are actually powered by the same “Code Execution” technology under the hood. The real opportunity for developers lies in using this same engine within Apps Script to build custom, programmatic workflows that go far beyond standard chat interactions.

A recent project by Stéphane Giron highlights this path. By leveraging the Code Execution capability of the Gemini API, it is possible to offload intricate document and data manipulation to a secure Python sandbox, returning the results directly to Google Drive.

Moving beyond static logic

The traditional approach to automation involves writing specific code for every anticipated action. The shift here is that Gemini Code Execution does not rely on a pre-defined set of functions. Instead, when provided with a file and a natural language instruction, the model generates the necessary Python logic on the fly. Because the execution environment includes robust libraries for binary file handling and data analysis, the model can perform varied tasks without the developer needing to hardcode each individual routine. Notably, the model can learn iteratively which means if the generated code fails, it can refine and retry the script up to five times until it reaches a successful output.

While basic data analysis is now a standard part of Gemini for Workspace, having direct access to the library list in the Gemini sandbox opens up additional specialised, developer-focused avenues:

  • Dynamic Document Generation: Using python-docx and python-pptx, you can programmatically generate high-fidelity Office documents or presentations based on data from Google Workspace, bridging the gap between ecosystems without manual copy-pasting. [Here is a container bound script based on Stéphane code for Google Docs that generates a summary PowerPoint file]
  • Programmatic Image Inspection: Using Gemini 3 Flash, you can build tools that inspect images at a granular level. For example, a script could process a batch of site inspection photos, using Python code to “zoom and inspect” specific equipment labels or gauges, and then log those values directly into a database.

The mechanics and constraints

The bridge between Google Drive and this dynamic execution environment follows a straightforward pattern:

  1. File Preparation: The script retrieves the target file from Drive and converts the blob into a format compatible with the Gemini API.
  2. Instruction & Execution: Along with the file, a prompt is sent describing the desired outcome.
  3. The Sandbox: Gemini writes and runs the Python code required to fulfil the request.
  4. Completion: Apps Script receives the modified file or data and saves it back to the user’s Drive.

However, there are important boundaries to consider. The execution environment is strictly Python-based and has a maximum runtime of 30 seconds. Furthermore, developers should be mindful of the billing model: you are billed for “intermediate tokens,” which include the generated code and the results of the execution, before the final summary is produced.

Get started

For those interested in the implementation details, Stéphane has shared a repository containing the Apps Script logic and the specific configuration needed to enable the code_execution tool.

Source: Stéphane Giron on Medium | Code: GitHub Repository

Identifying the “human in the machine”: Advanced email reply detection in Gmail with Google Apps Script

Learn how to check if an email has been replied to in Gmail using Google Apps Script.

“Gmail doesn’t have a built-in feature to check for replies, but you can use Google Apps Script to create a custom script that will automatically check for replies to your emails.”

Amit Agarwal has shared another practical tutorial for anyone looking to add more intelligence to their automated Gmail workflows. While Gmail organises messages into threads, it does not provide a simple out-of-the-box method to programmatically determine if a specific email has received a human response.

In his latest post, Amit explores the technical side of email headers: specifically how the Message-Id of an original email is referenced in the In-Reply-To and References headers of subsequent replies. This logic forms the basis for his Apps Script solution, which helps developers identify valid replies while ignoring automated clutter.

One aspect I found particularly interesting is the filtering logic used to try and separate human interaction from the rest. The script accounts for several common hurdles as it ignores out-of-office auto-responses, delivery failure notifications, and messages sent from the user’s own account. By checking the auto-submitted header and using regex to filter common bot addresses like no-reply or mailer-daemon, the solution ensures you only track meaningful engagement.

Crucially, this logic is not confined to the Google ecosystem. Because these headers follow standard email protocols, the detection works seamlessly even when recipients reply from external clients like Microsoft Outlook or Apple Mail.

As Amit notes, if you want to use this logic effectively, you should store the rfcMessageId and threadId in a Google Sheet or database immediately after sending. This approach allows you to run your reply-checking scripts at any time without losing the original context.

You can find the full code and detailed header explanations in Amit’s blog post.

Source: How to Check for Email Replies in Gmail

Creating a ‘Knowledge Doc’ for Gemini: Consolidating Google Workspace Flows Documentation

How to build a server-less crawler using Google Apps Script to create a unified knowledge base for AI-assisted development.

The Goal: A Unified Context for Gemini

When building Google Workspace add-ons, particularly those using newer features like Workspace Flows, the documentation is often spread across dozens of nested pages. While excellent for browsing, this structure presents a challenge when working with Large Language Models (LLMs) like Gemini.

The primary motivation for this project was to create a single, consolidated “Knowledge Doc” containing the complete technical specification for Workspace Flows. By feeding this unified document into Gemini, I can:

  • Control the Context: Ensure the model answers based on the specific, official documentation rather than outdated training data or hallucinated APIs.
  • Accelerate Development: Ask complex architectural questions (“How do I build a flow that integrates X and Y?”) and get answers grounded in the full API reference without the latency of multiple web searches.
  • Bridge the Knowledge Gap: Work effectively with brand-new products and services that haven’t yet been absorbed into the LLM’s core training set.

This article details how I built the tool to create this document—a server-less crawler that runs entirely within Google Apps Script. If you are less concerned about the how and more about the end result here is the [Shared] Google Workspace Flows Guide

The Technical Challenge: Apps Script vs. The DOM

My initial plan was straightforward:

  1. Fetch the HTML of each documentation page.
  2. Parse it into Markdown (using a library like Turndown or Showdown).
  3. Convert that Markdown into a Google Doc.

However, I hit a major roadblock immediately. Most JavaScript parsing libraries rely on the DOM (window, document, DOMParser). Google Apps Script runs in a server-side environment (similar to a Node.js runtime but without standard packages) where no DOM exists. Libraries like Turndown crashed instantly with ReferenceError: document is not defined.

The Solution: A Hybrid HTML Pipeline

Instead of fighting to make a browser library work on the server, I pivoted to a hybrid approach:

  1. Cheerio for Parsing: I used the Cheerio library (which parses HTML using pure JavaScript strings, no DOM required) to “clean” the content.
  2. Direct HTML Injection: Instead of converting to Markdown (which lost formatting for tables and images), I switched to the Google Drive API which can natively convert HTML files into Google Docs.

This pipeline proved superior because it preserved:

  • Code Blocks: <pre> tags with specific formatting.
  • Tables: Complex data tables rendered perfectly.
  • Images: <img> tags with absolute URLs were automatically fetched and embedded by Google Drive.

Important Note: This script was developed and tested specifically for Google Workspace Flows documentation. Web scraping inherently carries the risk that the target website’s HTML structure may change over time. Other documentation sites will use different CSS classes and HTML structures. While the core logic (crawling + HTML cleaning + Docs conversion) remains valid, you will need to inspect the source of your target site and update the Cheerio selectors (like .devsite-article-body or .devsite-book-nav) to match its specific layout.

Step 1: The Crawler

First, we needed to find all the relevant pages. Traversing the DOM tree proved fragile—class names change, and nesting levels vary.

The robust solution was Path Filtering. We fetch the main navigation bar and simply filter for any link that starts with our target root URL.

function extractLinksByPath(html, rootUrl) {
  const $ = Cheerio.load(html);
  const links = new Set();

  $('.devsite-book-nav a').each(function() {
    let href = $(this).attr('href');
    if (href) {
      if (href.startsWith('/')) href = CONFIG.BASE_URL + href;
      if (href.startsWith(rootUrl) && !href.includes('#')) links.add(href);
    }
  });
  return Array.from(links);
}

Step 2: The HTML Cleaner

We couldn’t just dump the raw page HTML into a Doc; it would include navigation bars, “Feedback” buttons, and footer links. We used Cheerio to strip these out.

We also encountered a specific challenge with Definition Lists (<dl>, <dt>, <dd>). Google Docs doesn’t have a native “Definition List” element, so they often flattened into messy text.

The fix was to transform them into semantic HTML paragraphs with inline styling to force the visual layout we wanted:

// Transform <dt> (Terms) into bold/italic paragraphs
$body.find('dl').each((i, dl) => {
    let newContent = '';
    $(dl).children().each((j, child) => {
      const $child = $(child);
      if ($child.is('dt')) {
        newContent += `<p style="font-weight:bold; font-style:italic; margin-top:12pt; margin-bottom:4pt; font-size:11pt; color:#202124;">${$child.html()}</p>`;
      } else if ($child.is('dd')) {
        newContent += `<p style="margin-left:24pt; margin-bottom:8pt; font-size:11pt; line-height:1.15;">${$child.html()}</p>`;
      }
    });
    $(dl).replaceWith(`<div>${newContent}</div>`);
  });

Step 3: Post-Processing with the Docs API

Even with clean HTML, the Google Docs converter can be stubborn. It sometimes ignores semantic headings (<h1>) or allows images to overflow the page width.

To fix this, we added a post-processing step. After the HTML file is converted to a Google Doc, we open it with DocumentApp to programmatically enforce styles and constraints.

Fixing Headings: We scan for our specific “Title” styling (24pt Bold) and force the Heading 1 style.

function applyHeadingsToDoc(docId) {
  const doc = DocumentApp.openById(docId);
  const body = doc.getBody();
  const paragraphs = body.getParagraphs();

  paragraphs.forEach(p => {
    const text = p.getText();
    if (text.length === 0) return;

    const fontSize = p.getAttributes().FONT_SIZE;
    const isBold = p.getAttributes().BOLD;
    // If it looks like a H1 (24pt + Bold), make it a H1!
    if (fontSize >= 24 && isBold) {
      p.setHeading(DocumentApp.ParagraphHeading.HEADING1);
    }
  });

  doc.saveAndClose();
}

Resizing Images: We also scan for images wider than the standard page width (e.g., 600px) and scale them down to fit, ensuring the document layout remains printable and clean.

function resizeImagesInDoc(docId) {
  const doc = DocumentApp.openById(docId);
  const body = doc.getBody();
  const MAX_WIDTH = 600;

  body.getImages().forEach(image => {
    const width = image.getWidth();
    const height = image.getHeight();
    if (width > MAX_WIDTH) {
      image.setWidth(MAX_WIDTH);
      image.setHeight((height / width) * MAX_WIDTH);
    }
  });
  doc.saveAndClose();
}

The Workflow in Action

Once generated, this document becomes the cornerstone of my development workflow:

  1. Consolidate: I run the script to pull the latest documentation into a fresh Google Doc.
  2. Enrich: I use tools like Gemini Deep Research to find community patterns, or architectural best practices and paste them directly into the same doc.
  3. Contextualise: This single document is then uploaded to Gemini (or another LLM workspace), providing a focused, hallucination-resistant context for answering questions and generating code.

By combining the UrlFetchApp for crawling, Cheerio for parsing, and the Drive API for document generation, we built a custom scraping pipeline in under 200 lines of code without spinning up a single server. If you’d like to make your own feel free to copy this script project which comes with a compiled Apps Script friendly version of Cheerio

Community Tip: Restrict a Google Form to Your Domain with Apps Script

How to publish a Google Form so that it can only be completed by people in your domain.

Screenshot of code for publishing a Google Form
Screenshot of code for publishing a Google Form

This was developed in anticipation of the changes from 31 March 2026 with the Forms API.

In a helpful post on “The Gift of Script,” Phil Bainbridge tackles the common need to publish a Google Form so it can only be completed by users within your organisation.

He shares the complete Apps Script snippet, highlighting the crucial (and often missed) step of first setting the form as published (.setPublished(true)) before using the Drive API to apply domain-wide “reader” permissions. It’s a quick, effective solution for securing internal-only forms.

Source: The Gift of Script: Publish a Google Form to your domain

Automating Personalised Gmail Drafts with the AI Formula and Apps Script

The AI formula in Google Sheets is a powerful and easy way to use Gemini AI directly inside of your Google Sheets. In this example, we use the AI formula to generate draft emails providing feedback on student exam scores. Then we use AI to write Apps Script that moves the draft emails from our Sheet into drafts in our Gmail, ready to send.

For many, the process of drafting personalised emails from a list of contacts in Google Sheets is a tedious, manual task. It involves switching between tabs, copying and pasting data, and carefully composing each message. But what if you could automate the entire workflow, generating perfectly tailored draft emails in Gmail with the click of a button?

In a recent video tutorial, Google Developer Expert Ben Collins demonstrates a powerful and efficient method to achieve exactly this. He showcases how to combine the intelligence of the new AI() formula in Google Sheets with the automation capabilities of Google Apps Script to turn structured data into ready-to-review email drafts.

What makes this solution so compelling is that it’s more than just a clever solution; it’s a glimpse into the future of work in Google Workspace.  As Google continues to embed AI and end-to-end automations like Workspace Flows, the ability to effectively guide these systems becomes paramount. Ben’s solution provides a perfect, practical sandbox for honing a crucial new skill of prompt engineering and mastering the art of communicating with Large Language Models.

To see the full demonstration and get started on your own prompting journey, be sure to watch Ben’s complete video.

Source: Turn Sheets Data into Gmail Drafts with the AI Formula and Apps Script 

Automate Your Organisational Chart with Apps Script & Google Sheets

In this guide, I’ll show you how to build a data-driven organizational chart based on Google Sheets data, using Apps Script and the OrgChart JavaScript library.

Recently, I used Gemini Deep Reacher to “find the most popular or useful Google Apps Script tutorials?”. There was no mention of AppsScriptPulse (rude, but if you are curious, here is the report :), but it did introduce me to the work of Joseph Petty on the GreenFlux Blog.

One tutorial, in particular, stood out for its practical application for many organisations: “Building A Data-Driven Organisational Chart In Apps Script”.

Creating diagrams, particularly organisational charts, often requires laborious design work and manual revisions. Petty’s tutorial directly addresses this common frustration by demonstrating how Google Sheets, Apps Script, and the OrgChart JavaScript library can produce dynamic, data-driven organisational charts that automatically stay current.

The Core Idea: From Spreadsheet Data to Visual Chart

The beauty of this approach lies in its simplicity and efficiency. Most organisations already maintain employee data, including supervisor information, in spreadsheets or databases. Petty’s method cleverly taps into this existing data source.

The tutorial guides you through several key steps:

  • Foundation Setup: Deploy a basic Apps Script web app, creating an index.html for the frontend and Code.gs for server-side logic (including a doGet() function).
  • OrgChart Library Integration: Introduce the open-source OrgChart JavaScript library, which uses a nested JSON structure to define the visual hierarchy.
  • Data Transformation: Write an Apps Script function (getEmployeeData) to pull data from Google Sheets (employee IDs, names, titles, supervisor IDs) and convert it into the required nested JSON format for the chart.
  • Dynamic Connection: Pass the generated JSON data from Code.gs to the OrgChart library in index.html using google.script.run.withSuccessHandler() to render the chart.

The Result? An Always Up-to-Date Org Chart

By following these steps, you can create an organisational chart that’s directly linked to your Google Sheet. When an employee changes roles, or a new team member joins, simply update the spreadsheet, and your web app will reflect these changes automatically. No more manual graphic updates!

For the complete code and detailed setup instructions, please follow the link to the original tutorial on GreenFlux Blog.

[Update 2025-05-16: A great tip from Pablo Felip Monferrer: “might be worth mentioning that the good old Sheets has a native organizational chart that can be easily published, too (hint: employee names in a third column can be displayed as tooltips)”.

Image credit: Pablo Felip Monferrer

Source: Building A Data-Driven Organizational Chart In Apps Script

Streamlining Google Workspace Development: Editing JSON files in Google Drive with Apps Script (and a little help from AI)

Storing JSON files in Google Drive offers a practical approach for Google Workspace developers, especially when combined with Google Apps Script. While Drive lacks a built-in JSON editor, AI tools like Gemini can rapidly prototype solutions, such as web-based editors. However, creating a robust application requires more than just a quick fix. It involves understanding syntax highlighting, managing dependencies, and navigating the constraints of the Apps Script platform, highlighting the importance of both rapid prototyping and robust engineering skills.

I recently explored how to effectively manage JSON configuration files within Google Drive using Google Apps Script, and the journey from quick AI-assisted prototypes to robust solutions.

As part of this I delve into the benefits of storing JSON in Drive, the challenges of editing, and how AI tools like Gemini can provide a great starting point. However, as I discovered, building a truly polished tool requires deeper technical knowledge and problem-solving.

Check out the article for insights on navigating the development process and, if you want one, an Apps Script powered JSON Editor for Google Drive.

Source: Streamlining Google Workspace Development: Editing JSON files in Google Drive with Apps Script (and a little help from AI)

Programmatically Iterating on Images with Imagen 3, Vertex AI, and Apps Script

Use Gemini and Imagen 3 on Vertex AI to create the images you envision. Generate tailored images from reference sources with Apps Script.

Have you ever generated an image using Gemini in Google Workspace and wished you could easily tweak or iterate on it? While Gemini for Workspace is great for initial creation, iterating on those images directly isn’t currently straightforward. A recent post by Stéphane Giron highlights a programmatic approach using Google Apps Script, Vertex AI (with Imagen 3 and Gemini models) to achieve the goal of generating image variations based on a source image.

Stéphane’s method uses a source image (which could be one previously generated or found elsewhere) which is provided to the Vertex AI API for Gemini (e.g., gemini-2.0-pro) along with text instructions for the required changes. The Gemini model analyzes the image and the request to generate a new prompt. This new prompt is then used with the Imagen 3 model (e.g., imagen-3.0-generate-001) via Vertex AI to generate the final image variation.

It’s interesting to contrast this with another solution we’ve previously featured from Kanshi Tanaike on ‘Iterative Image Generation with the Gemini API and Google Apps Script‘. While Tanaike’s method uses the Gemini API’s chat history to iteratively build an image from sequential text prompts, Stéphane’s focuses on reinterpreting a source image with specific modifications via a newly generated prompt.

You can check out Stéphane Giron’s source post for the complete code and setup instructions.

Source: Similar Image Generation with Imagen 3 on Vertex AI with Google Apps Script