Universal actions

Universal actions are menu item elements that allow a user to open a new web page, display new UI cards, or run a specific Apps Script function when selected. In operation they are very similar to card actions, except that universal actions are always placed on every card in your add-on, regardless of the current add-on context.

By using universal actions, you can make sure the user always has access to certain functionality, regardless of which part of your add-on they interact with. Here are some example use cases for universal actions:

  • Open a settings web page (or display a settings card).
  • Show help information to the user.
  • Start a new workflow, such as 'Add new customer'.
  • Display a card that lets a user send feedback about the add-on.

Whenever you have an action that does not depend on the current context, you should consider making it a universal action.

Using universal actions

Universal actions are configured in your add-on's project manifest. Once you've configured a universal action, it is always available to users of your add-on. If the user is viewing a card, the set of universal actions you've defined always appears in the card menu, after any card actions you've defined for that card. Universal actions appear in the card menus in the same order in which they are defined in the add-on's manifest.

Configuring universal actions

You configure universal actions in your add-on's manifest; see Manifests for more details.

For each action, you specify the text that should appear in the menu for that action. You can then specify an openLink field indicating that the action should directly open a web page in a new tab. Alternatively, you can specify a runFunction field that specifies an Apps Script callback function to execute when the universal action is selected.

When runFunction is used, the callback function specified usually does one of the following:

  • Builds UI cards to display immediately by returning a built UniversalActionResponse object.
  • Opens a URL, perhaps after doing other tasks, by returning a built UniversalActionResponse object.
  • Conducts background tasks that do not switch to a new card or open a URL. In this case the callback function returns nothing.

When called, the callback function is passed an event object containing information about the open card and add-on context.

Example

The following code snippet shows an example manifest excerpt for a Google Workspace Add-on that uses universal actions while extending Gmail. The code explicitly sets a metadata scope so that the add-on can determine who sent the open message.

  "oauthScopes": [
    "https://www.googleapis.com/auth/gmail.addons.current.message.metadata"
  ],
  "addOns": {
    "common": {
      "name": "Universal Actions Only Addon",
      "logoUrl": "https://www.example.com/hosted/images/2x/my-icon.png",
      "openLinkUrlPrefixes": [
        "https://www.google.com",
        "https://www.example.com/urlbase"
      ],
      "universalActions": [{
          "label": "Open google.com",
          "openLink": "https://www.google.com"
        }, {
          "label": "Open contact URL",
          "runFunction": "openContactURL"
        }, {
          "label": "Open settings",
          "runFunction": "createSettingsResponse"
        }, {
          "label": "Run background sync",
          "runFunction": "runBackgroundSync"
      }],
      ...
    },
    "gmail": {
      "contextualTriggers": [
        {
          "unconditional": {},
          "onTriggerFunction": "getContextualAddOn"
        }
      ]
    },
    ...
  },
  ...

The three universal actions defined in preceding example do the following:

  • Open google.com opens https://www.google.com in a new tab.
  • Open contact URL runs a function that determines what URL to open and then opens it in a new tab using an OpenLink object. The code builds the URL using the sender's email address.
  • Open settings runs the createSettingsCards() function defined in the add-on script project. This function returns a valid UniversalActionResponse object containing a set of cards with add-on setting and other information. After the function finishes building this object, the UI displays the list of cards (see Returning multiple cards).
  • Run background sync runs the runBackgroundSync() function defined in the add-on script project. This function does not build cards; instead it performs some other background tasks that do not change the UI. Since the function doesn't return a UniversalActionResponse, the UI does not display a new card when the function finishes. Instead the UI displays a loading indicator spinner while the function is running.

Here is an example of how you might construct the openContactURL(), createSettingsResponse(), and runBackgroundSync() functions:

/**
 * Open a contact URL.
 * @param {Object} e an event object
 * @return {UniversalActionResponse}
 */
function openContactURL(e) {
  // Activate temporary Gmail scopes, in this case so that the
  // open message metadata can be read.
  var accessToken = e.gmail.accessToken;
  GmailApp.setCurrentMessageAccessToken(accessToken);

  // Build URL to open based on a base URL and the sender's email.
  // This URL must be included in the openLinkUrlPrefixes whitelist.
  var messageId = e.gmail.messageId;
  var message = GmailApp.getMessageById(messageId);
  var sender = message.getFrom();
  var url = "https://www.example.com/urlbase/" + sender;
  return CardService.newUniversalActionResponseBuilder()
      .setOpenLink(CardService.newOpenLink()
          .setUrl(url))
      .build();
}

/**
 * Create a collection of cards to control the add-on settings and
 * present other information. These cards are displayed in a list when
 * the user selects the associated "Open settings" universal action.
 *
 * @param {Object} e an event object
 * @return {UniversalActionResponse}
 */
function createSettingsResponse(e) {
  return CardService.newUniversalActionResponseBuilder()
      .displayAddOnCards(
          [createSettingCard(), createAboutCard()])
      .build();
}

/**
 * Create and return a built settings card.
 * @return {Card}
 */
function createSettingCard() {
  return CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader().setTitle('Settings'))
      .addSection(CardService.newCardSection()
          .addWidget(CardService.newSelectionInput()
              .setType(CardService.SelectionInputType.CHECK_BOX)
              .addItem("Ask before deleting contact", "contact", false)
              .addItem("Ask before deleting cache", "cache", false)
              .addItem("Preserve contact ID after deletion", "contactId", false))
          // ... continue adding widgets or other sections here ...
      ).build();   // Don't forget to build the card!
}

/**
 * Create and return a built 'About' informational card.
 * @return {Card}
 */
function createAboutCard() {
  return CardService.newCardBuilder()
      .setHeader(CardService.newCardHeader().setTitle('About'))
      .addSection(CardService.newCardSection()
          .addWidget(CardService.newTextParagraph()
              .setText('This add-on manages contact information. For more '
                  + 'details see the <a href="https://www.example.com/help">'
                  + 'help page</a>.'))
      // ... add other information widgets or sections here ...
      ).build();  // Don't forget to build the card!
}

/**
 * Run background tasks, none of which should alter the UI.
 * Also records the time of sync in the script properties.
 *
 * @param {Object} e an event object
 */
function runBackgroundSync(e) {
  var props = PropertiesService.getUserProperties();
  props.setProperty("syncTime", new Date().toString());

  syncWithContacts();  // Not shown.
  updateCache();       // Not shown.
  validate();          // Not shown.

  // no return value tells the UI to keep showing the current card.
}