Build a Google Workspace add-on with Apps Script

  • This guide details the creation of a Google Workspace add-on that displays a random cat image with text pulled from Gmail, Drive, or Calendar.

  • The add-on uses Apps Script and requires a Google Cloud project for setup and deployment.

  • Users interact with the add-on through cards displayed within Gmail, Drive, and Calendar interfaces.

  • Functionality is triggered by specific actions like opening an email, selecting Drive items, or viewing a calendar event.

  • The guide covers setup, configuration, deployment, and basic usage of the add-on.

This quickstart creates a simple Google Workspace add-on that demonstrates homepages, contextual triggers, and connecting to third-party APIs.

The add-on creates contextual and non-contextual interfaces in Gmail, Calendar, and Drive. The add-on displays a random image of a cat with text overlaying the image. The text is either static for homepages or taken from the host application context for contextual triggers.

Objectives

  • Set up your environment.
  • Set up the script.
  • Run the script.

Prerequisites

To use this sample, you need the following prerequisites:

  • A Google Account (Google Workspace accounts might require administrator approval).
  • A web browser with access to the internet.

  • A Google Cloud project.

Set up your environment

Open your Cloud project in the Google Cloud console

If it's not open already, open the Cloud project that you intend to use for this sample:

  1. In the Google Cloud console, go to the Select a project page.

    Select a Cloud project

  2. Select the Google Cloud project you want to use. Or, click Create project and follow the on-screen instructions. If you create a Google Cloud project, you might need to turn on billing for the project.

Google Workspace add-ons require a consent screen configuration. Configuring your add-on's OAuth consent screen defines what Google displays to users.

  1. In the Google Cloud console, go to Menu > Google Auth platform > Branding.

    Go to Branding

  2. If you have already configured the Google Auth platform, you can configure the following OAuth Consent Screen settings in Branding, Audience, and Data Access. If you see a message that says Google Auth platform not configured yet, click Get Started:
    1. Under App Information, in App name, enter a name for the app.
    2. In User support email, choose a support email address where users can contact you if they have questions about their consent.
    3. Click Next.
    4. Under Audience, select Internal.
    5. Click Next.
    6. Under Contact Information, enter an Email address where you can be notified about any changes to your project.
    7. Click Next.
    8. Under Finish, review the Google API Services User Data Policy and if you agree, select I agree to the Google API Services: User Data Policy.
    9. Click Continue.
    10. Click Create.
  3. For now, you can skip adding scopes. In the future, when you create an app for use outside of your Google Workspace organization, you must change the User type to External. Then add the authorization scopes that your app requires. To learn more, see the full Configure OAuth consent guide.

Set up the script

Create the Apps Script project

  1. To create a new Apps Script project, go to script.new.
  2. Click Untitled project.
  3. Rename the Apps Script project Cats and click Rename.
  4. Next to the Code.gs file, click More > Rename. Name the file Common.
  5. Click Add a file > Script. Name the file Gmail.
  6. Repeat step 5 to create 2 more script files named Calendar and Drive. When you're done you should have 4 separate script files.
  7. Replace the contents of each file with the following corresponding code:

    Common.gs

    /**
    *ThissimpleGoogleWorkspaceadd-onshowsarandomimageofacatinthe
    *sidebar.Whenopenedmanually(thehomepagecard),somestatictextis
    *overlayedontheimage,butwhencontextualcardsareopenedanewcatimage
    *isshownwiththetexttakenfromthatcontext(suchasamessage's subject
    *line)overlayingtheimage.Thereisalsoabuttonthatupdatesthecardwith
    *anewrandomcatimage.
    *
    *Click"File > Make a copy..."tocopythescript,and"Publish > Deploy from
    *manifest > Installadd-on" to install it.
    */
    /**
    *Themaximumnumberofcharactersthatcanfitinthecatimage.
    */
    varMAX_MESSAGE_LENGTH=40;
    /**
    *Callbackforrenderingthehomepagecard.
    *@return{CardService.Card}Thecardtoshowtotheuser.
    */
    functiononHomepage(e){
    console.log(e);
    varhour=Number(Utilities.formatDate(newDate(),e.userTimezone.id,'H'));
    varmessage;
    if(hour>=6 && hour < 12){
    message='Good morning';
    }elseif(hour>=12 && hour < 18){
    message='Good afternoon';
    }else{
    message='Good night';
    }
    message+=' '+e.hostApp;
    returncreateCatCard(message,true);
    }
    /**
    *Createsacardwithanimageofacat,overlayedwiththetext.
    *@param{String}textThetexttooverlayontheimage.
    *@param{Boolean}isHomepageTrueifthecardcreatedhereisahomepage;
    *falseotherwise.Defaultstofalse.
    *@return{CardService.Card}Theassembledcard.
    */
    functioncreateCatCard(text,isHomepage){
    //ExplicitlysetthevalueofisHomepageasfalseifnullorundefined.
    if(!isHomepage){
    isHomepage=false;
    }
    //Usethe"Cat as a service"APItogetthecatimage.Adda"time"URL
    //parametertoactasacachebuster.
    varnow=newDate();
    //Replaceforwardslashesinthetext,astheybreaktheCataaSAPI.
    varcaption=text.replace(/\//g,' ');
    varimageUrl=
    Utilities.formatString('https://cataas.com/cat/says/%s?time=%s',
    encodeURIComponent(caption),now.getTime());
    varimage=CardService.newImage()
    .setImageUrl(imageUrl)
    .setAltText('Meow')
    //Createabuttonthatchangesthecatimagewhenpressed.
    //Note:Actionparameterkeysandvaluesmustbestrings.
    varaction=CardService.newAction()
    .setFunctionName('onChangeCat')
    .setParameters({text:text,isHomepage:isHomepage.toString()});
    varbutton=CardService.newTextButton()
    .setText('Change cat')
    .setOnClickAction(action)
    .setTextButtonStyle(CardService.TextButtonStyle.FILLED);
    varbuttonSet=CardService.newButtonSet()
    .addButton(button);
    //Createafootertobeshownatthebottom.
    varfooter=CardService.newFixedFooter()
    .setPrimaryButton(CardService.newTextButton()
    .setText('Powered by cataas.com')
    .setOpenLink(CardService.newOpenLink()
    .setUrl('https://cataas.com')));
    //Assemblethewidgetsandreturnthecard.
    varsection=CardService.newCardSection()
    .addWidget(image)
    .addWidget(buttonSet);
    varcard=CardService.newCardBuilder()
    .addSection(section)
    .setFixedFooter(footer);
    if(!isHomepage){
    //Createtheheadershownwhenthecardisminimized,
    //butonlywhenthiscardisacontextualcard.Peekheaders
    //areneverusedbynon-contexualcardslikehomepages.
    varpeekHeader=CardService.newCardHeader()
    .setTitle('Contextual Cat')
    .setImageUrl('https://www.gstatic.com/images/icons/material/system/1x/pets_black_48dp.png')
    .setSubtitle(text);
    card.setPeekCardHeader(peekHeader)
    }
    returncard.build();
    }
    /**
    *Callbackforthe"Change cat"button.
    *@param{Object}eTheeventobject,documented{@link
    *https://developers.google.com/gmail/add-ons/concepts/actions#action_event_objects
    *here}.
    *@return{CardService.ActionResponse}Theactionresponsetoapply.
    */
    functiononChangeCat(e){
    console.log(e);
    //Getthetextthatwasshowninthecurrentcatimage.Thiswaspassedasa
    //parameterontheActionsetforthebutton.
    vartext=e.parameters.text;
    //TheisHomepageparameterispassedasastring,soconverttoaBoolean.
    varisHomepage=e.parameters.isHomepage==='true';
    //Createanewcardwiththesametext.
    varcard=createCatCard(text,isHomepage);
    //Createanactionresponsethatinstructstheadd-ontoreplace
    //thecurrentcardwiththenewone.
    varnavigation=CardService.newNavigation()
    .updateCard(card);
    varactionResponse=CardService.newActionResponseBuilder()
    .setNavigation(navigation);
    returnactionResponse.build();
    }
    /**
    *Truncateamessagetofitinthecatimage.
    *@param{string}messageThemessagetotruncate.
    *@return{string}Thetruncatedmessage.
    */
    functiontruncate(message){
    if(message.length > MAX_MESSAGE_LENGTH){
    message=message.slice(0,MAX_MESSAGE_LENGTH);
    message=message.slice(0,message.lastIndexOf(' '))+'...';
    }
    returnmessage;
    }
    

    Gmail.gs

    /**
     * Callback for rendering the card for a specific Gmail message.
     * @param {Object} e The event object.
     * @return {CardService.Card} The card to show to the user.
     */
    functiononGmailMessage(e){
    console.log(e);
    //GettheIDofthemessagetheuserhasopen.
    varmessageId=e.gmail.messageId;
    //GetanaccesstokenscopedtothecurrentmessageanduseitforGmailApp
    //calls.
    varaccessToken=e.gmail.accessToken;
    GmailApp.setCurrentMessageAccessToken(accessToken);
    //Getthesubjectoftheemail.
    varmessage=GmailApp.getMessageById(messageId);
    varsubject=message.getThread().getFirstMessageSubject();
    //Removelabelsandprefixes.
    subject=subject
    .replace(/^([rR][eE]|[fF][wW][dD])\:\s*/,'')
    .replace(/^\[.*?\]\s*/,'');
    //Ifneccessary,truncatethesubjecttofitintheimage.
    subject=truncate(subject);
    returncreateCatCard(subject);
    }
    /**
     * Callback for rendering the card for the compose action dialog.
     * @param {Object} e The event object.
     * @return {CardService.Card} The card to show to the user.
     */
    functiononGmailCompose(e){
    console.log(e);
    varheader=CardService.newCardHeader()
    .setTitle('Insert cat')
    .setSubtitle('Add a custom cat image to your email message.');
    //Createtextinputforenteringthecat's message.
     var input = CardService.newTextInput()
     .setFieldName('text')
     .setTitle('Caption')
     .setHint('Whatdoyouwantthecattosay?');
     // Create a button that inserts the cat image when pressed.
     var action = CardService.newAction()
     .setFunctionName('onGmailInsertCat');
     var button = CardService.newTextButton()
     .setText('Insertcat')
     .setOnClickAction(action)
     .setTextButtonStyle(CardService.TextButtonStyle.FILLED);
     var buttonSet = CardService.newButtonSet()
     .addButton(button);
     // Assemble the widgets and return the card.
     var section = CardService.newCardSection()
     .addWidget(input)
     .addWidget(buttonSet);
     var card = CardService.newCardBuilder()
     .setHeader(header)
     .addSection(section);
     return card.build();
    }
    /**
     * Callback for inserting a cat into the Gmail draft.
     * @param {Object} e The event object.
     * @return {CardService.UpdateDraftActionResponse} The draft update response.
     */
    function onGmailInsertCat(e) {
     console.log(e);
     // Get the text that was entered by the user.
     var text = e.formInput.text;
     // Use the "Cat as a service" API to get the cat image. Add a "time" URL
     // parameter to act as a cache buster.
     var now = new Date();
     var imageUrl = 'https://cataas.com/cat';
     if (text) {
     // Replace forward slashes in the text, as they break the CataaS API.
     var caption = text.replace(/\//g, '');
     imageUrl += Utilities.formatString('/says/%s?time=%s',
     encodeURIComponent(caption), now.getTime());
     }
     var imageHtmlContent = '<imgstyle="display: block; max-height: 300px;"src="'
     + imageUrl + '"/>';
    varresponse=CardService.newUpdateDraftActionResponseBuilder()
    .setUpdateDraftBodyAction(CardService.newUpdateDraftBodyAction()
    .addUpdateContent(imageHtmlContent,CardService.ContentType.MUTABLE_HTML)
    .setUpdateType(CardService.UpdateDraftBodyType.IN_PLACE_INSERT))
    .build();
    returnresponse;
    }
    

    Calendar.gs

    /**
    *CallbackforrenderingthecardforaspecificCalendarevent.
    *@param{Object}eTheeventobject.
    *@return{CardService.Card}Thecardtoshowtotheuser.
    */
    functiononCalendarEventOpen(e){
    console.log(e);
    varcalendar=CalendarApp.getCalendarById(e.calendar.calendarId);
    //Theeventmetadatadoesn't include the event'stitle,sousingthe
    //calendar.readonlyscopeandfetchingtheeventbyit's ID.
    varevent=calendar.getEventById(e.calendar.id);
    if(!event){
    //Thisisaneweventstillbeingcreated.
    returncreateCatCard('A new event! Am I invited?');
    }
    vartitle=event.getTitle();
    //Ifnecessary,truncatethetitletofitintheimage.
    title=truncate(title);
    returncreateCatCard(title);
    }
    

    Drive.gs

    /**
    *CallbackforrenderingthecardforspecificDriveitems.
    *@param{Object}eTheeventobject.
    *@return{CardService.Card}Thecardtoshowtotheuser.
    */
    functiononDriveItemsSelected(e){
    console.log(e);
    varitems=e.drive.selectedItems;
    //Includeatmost5itemsinthetext.
    items=items.slice(0,5);
    vartext=items.map(function(item){
    vartitle=item.title;
    //Ifneccessary,truncatethetitletofitintheimage.
    title=truncate(title);
    returntitle;
    }).join('\n');
    returncreateCatCard(text);
    }
    

  8. Click Project Settings The icon for project settings.

  9. Check the Show "appsscript.json" manifest file in editor box.

  10. Click Editor .

  11. Open the appsscript.json file and replace the contents with the following code, then click Save Save icon.

    appsscript.json

     {
     "timeZone": "America/New_York",
     "dependencies": {
     },
     "exceptionLogging": "STACKDRIVER",
     "oauthScopes": [
     "https://www.googleapis.com/auth/calendar.addons.execute",
     "https://www.googleapis.com/auth/calendar.readonly",
     "https://www.googleapis.com/auth/drive.addons.metadata.readonly",
     "https://www.googleapis.com/auth/gmail.addons.current.action.compose",
     "https://www.googleapis.com/auth/gmail.addons.current.message.readonly",
     "https://www.googleapis.com/auth/gmail.addons.execute",
     "https://www.googleapis.com/auth/script.locale"],
     "runtimeVersion": "V8",
     "addOns": {
     "common": {
     "name": "Cats",
     "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/pets_black_48dp.png",
     "useLocaleFromApp": true,
     "homepageTrigger": {
     "runFunction": "onHomepage",
     "enabled": true
     },
     "universalActions": [{
     "label": "Learn more about Cataas",
     "openLink": "https://cataas.com"
     }]
     },
     "gmail": {
     "contextualTriggers": [{
     "unconditional": {
     },
     "onTriggerFunction": "onGmailMessage"
     }],
     "composeTrigger": {
     "selectActions": [{
     "text": "Insert cat",
     "runFunction": "onGmailCompose"
     }],
     "draftAccess": "NONE"
     }
     },
     "drive": {
     "onItemsSelectedTrigger": {
     "runFunction": "onDriveItemsSelected"
     }
     },
     "calendar": {
     "eventOpenTrigger": {
     "runFunction": "onCalendarEventOpen"
     }
     }
     }
    }
     

Copy the Cloud project number

  1. In the Google Cloud console, go to Menu > IAM & Admin > Settings.

    Go to IAM & Admin Settings

  2. In the Project number field, copy the value.

Set the Apps Script project's Cloud project

  1. In your Apps Script project, click Project Settings The icon for project settings.
  2. Under Google Cloud Platform (GCP) Project, click Change project.
  3. In GCP project number, paste the Google Cloud project number.
  4. Click Set project.

Install a test deployment

  1. In your Apps Script project, click Editor .
  2. Click Deploy > Test deployments.
  3. Click Install > Done.

Run the script

  1. Go to Gmail.
  2. To open the add-on, in the right side panel, click Cats .
  3. If prompted, authorize the add-on.
  4. The add-on displays a cat image and text. To change the image, click Change cat.
  5. If you open an email while the add-on is open, the image refreshes and the text changes to the subject line of the email (truncated if it's too long).

You can see similar functionality in Calendar and Drive. You don't need to reauthorize the add-on to use it in those host apps.

Next steps

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025年10月13日 UTC.