Everything you always wanted to know about Google Apps Script Manifest Files (but were afraid to ask…)

On October 24, 2017 Google released Google Apps Script project manifests. The manifest is used to control script project properties and are also increasingly featured in new Google Apps Script services, in particular, Gmail Add-ons and Data Studio Community Connectors. Given their potential increasing importance this post explains manifest features and also highlights current opportunities for improving the user authentication flow and managing manifest files to aid project management including library versioning. If you prefer the ‘talky’ version of this information here is the recording from the Totally Unscripted episode on manifest files (also embedded below):

Manifest Files

The Google Apps Script documentation describes manifests as:

a special JSON file that specifies a basic project information that Apps Script needs to run the script successfully

It goes on to say:

Apps Script automatically creates and updates the project manifest as you create your script project and make changes in the Apps Script editor. In most cases you never need to view or edit the manifest directly; however, in certain situations it may be beneficial or required.

Given this the remainder of this post will be looking at when it is required or useful to know your way around and use manifest files. The post is broken down into the following sections:

Viewing/editing manifest files

It’s first worth noting that manifest files cannot be deleted. By default manifest files (always named appsscript.json) are hidden. They can viewed/edited from the Script editor via the View > Show manifest file. Google recommend:

Note: Hide the manifest when you are done editing. Your manifest may already contain a number of key:value pairs; these represent the current configuration of your Apps Script project. Exercise care when editing the manifest, as you may inadvertently alter how your script project operates.

The manifest file can be hidden by using View > Show manifest file (this unticks the Show manifest file).

Hide manifest files

For standalone script projects the manifest files can also be viewed/edited via the Drive API. This can be useful if you programmatically want to make changes to a script project (this is not possible for container bound scripts such as those created via the Tools > Script editor menu in Sheets, Docs, Forms and Slides). More details on accessing and manipulating manifest files in included at the end of this post.

Manifest structure

The current manifest JSON file structure and property values are detailed in the developer documentation. A copy of the general manifest structure is included below:

{
  "timeZone": string,
  "oauthScopes": [
    string
  ],
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": string,
        "serviceId": string,
        "version": string,
      }
    ],
    "libraries": [
      {
        "userSymbol": string,
        "libraryId": string,
        "version": string,
        "developmentMode": boolean,
      }
    ]
  },
  "exceptionLogging": string,
  "webapp": {
    "access": string,
    "executeAs": string,
  },
  "executionApi": {
    "access": string,
  },
  "urlFetchWhitelist": [
    string
  ],
  "gmail": gmail Resource
}

All the properties in the manifest file are optional. By default manifest files are automatically populated with timeZone, dependencies set as empty object and exceptionLogging set to Stackdriver logging. You will find as you use the Script editor menu options the manifest file will automatically update.

Timezone

The timeZone property is used to set the script timezone. Timezones are recorded as ZoneId values. Whilst the reference documents link to the ZoneId Java documentation a list of valid ids isn’t included so here a ZoneId list extracted from the Script editor. Setting a script timezone is useful for setting clock triggers to fire at the expected time. Note: For container bound scripts the script and container timezone can be different (often the cause of many headaches).

Scopes

Scopes are part of the Google authorisation flow. Previously when adding Google services to your script project Google would add the required authorisations needed. For example, adding SlidesApp.getActivePresentation() would mean when the script would run the user would be prompted to give permission to: create new presentations; view modify existing presentations; and share presentations with others:

Prompt to create new presentations; view modify existing presentations; and share presentations with others

Limiting Scope

These are very broad permissions/scope. For container bound projects the scope could be limited with @OnlyCurrentDoc, which permitted read/write access to the container bound project. Within the manifest scopes can now also be defined with the oauthScopes property. For example, instead of using @OnlyCurrentDoc you you can use the .currentonly value available in a number of the core apps script services. In the case of SlidesApp you would use:

{
   "oauthScopes": ["https://www.googleapis.com/auth/presentations.currentonly"]
}

If you are distributing container bound script projects you might want to explore @OnlyCurrentDoc/.currentonly as using this currently means App Verification is not required. With the manifest we can go further and limit the scope to read-only. For example, to limit SlidesApp to read only in theory you can use:

{
   "oauthScopes": ["https://www.googleapis.com/auth/presentations.readonly"]
}

This would result in the following prompt to the user when they authorised the script:

View your slide presentations

In practice you’ll however discover that explicit .readonly scopes currently don’t work with some Google Apps Script Service like SpreadsheetApp, SlidesApp services and a ‘you don’t have permission’ error will be thrown, but they do however work for Advanced Services and direct API calls e.g. Slides.Presentations.get(). Personally I think this is a bug and have opened an issue ticket.

Adding Google scopes to the authentication flow

As well as limiting scope you can use the manifest to add additional Google service scopes to the authentication flow. This is useful if your script project is using other Google services which are not included in Apps Script. For example, if you have a project which is using the Picker API to let a user select images from their Google Photos you can specify:

{
   "oauthScopes": ["https://www.googleapis.com/auth/photos"]
}

This means when the user authenticates the script as well as any other permissions they’ll be prompted to: view and manage your Google photos and videos; and view and manage your photo and video tags and comments. The returned access token as a result can be made to make calls with this scope.

This ability has been used by Romain Vialard in the libraries he has published for Picasa (used to access Google Photos) and Firebase.

[image credit: Romain Vialard]

Inheritance of OAuth Scopes defined in libraries

In the case of the Picasa and Firebase libraries the OAuth scopes are defined in the manifest for the library. When these libraries are used in a script project the required scopes are automatically detected and included in the script project. Adding oauthScopes to a manifest overrides scopes automatically added. If using libraries you can break them if using a manifest without all the scopes required included. Removing oauthScopes from manifest will revert to automatic scope detection.

The overriding advice is to define oauthScopes if required but in most cases automatic detection/use is probably most appropriate. If you are interested in finding out more about the scopes used by Google service most of them are listed here (H/T Dimu Designs).

Note: Specifying scopes in a manifest file is only applicable to the authorisation flow of Google services.

Whitelisting URLs

The Google Apps Script UrlFetch service is essential for connecting with and using external services. Specifying a urlFetchWhitelist in the manifest limits the URLs your script project can access with UrlFetch. Currently I’ve only seen a requirement to include whitelisted URLs for Gmail Add-ons, but would not be surprised if this was rolled out to other add-ons (and may even already used as an indicator in the Google verification process).

Whitelisted URLs – User Experience

In terms of the end user experience if whitelisted URLs have been specified in a manifest these aren’t shown when the user goes through authentication:

Whitelisted URLs not currently shown in authorisation flow

If your script manifest has defined urlFetchWhitelist values and attempts to call a URL not listed a ‘Request to URL failed because the URL has not been whitelisted in the script manifest’ error is thrown:

‘Request to URL failed because the URL has not been whitelisted in the script manifest’ error

Whitelisted URLs not inherited from libraries

Unlike oauthScopes if a urlFetchWhitelist is defined in a library the script project using that library doesn’t inherit the whitelist from the manifest.

Whitelisted URLs format

The developer documentation has clear guidance on the format of whitelisting URLs. In summary URLs are specified as a prefix which begins https:// and URLs that begin http:// or include wildcards are not permitted.

Dependencies

There are currently two types of dependencies included in the manifest enabledAdvancedServices and libraries. An example extract from a manifest file that has had the Sheets Advanced Service and the Picasa library added is included below:

{
  "dependencies": {
    "enabledAdvancedServices": [{
      "userSymbol": "Sheets",
      "serviceId": "sheets",
      "version": "v4"
    }],
    "libraries": [{
      "userSymbol": "PicasaApp",
      "libraryId": "1RHjbqdEN-WvQxqS…RX-dyg0dS7",
      "version": "22"
    }]
  },
}

For most script projects there is not much to be concerned with as managing dependencies via the existing Resources > Libraries and Resources > Advanced Google services menus is probably more practical and will automatically update the manifest.

Advanced Services

In the case of Advanced Services the manifest can currently only be used to enable and set the basic properties for the user symbol and version. Using Advanced Services also requires enabling them in an associated Cloud Platform project, which cannot be controlled by the manifest file. It is already possible to switch to a different Google Cloud Platform project but this is not currently recorded in the manifest. Enabling this would make it easier for developers to distribute scripts with Advanced Services pre-enabled (this might be something Google implement at a later date when they figure out how to do it securely).

Libraries

Libraries have similar properties to Advanced Services. Depending on the permissions the library uses there is more potential to find uses for it. One option would be version control of a library deployed to other standalone script projects that you have edit access to. It could also be a method for installing libraries in standalone script projects. Here is the source code for a demo application that installs Picasa on a selected script project (deployed here as an unverified app).

I had an idea for a community library installer project along the lines of the demo above but with a list of libraries. The advantage of doing it through a web app would it could capture an email address and then the script owner could be notified of version updates … the app could also do auto updates as an option … … if you also know advanced services dependencies they could be enabled but a console project would still have to manually be setup. I currently don’t have time to do this project but would be happy to support someone else if they are interested.

Web app and Execution API

When you deploy a web app or Execution API executable the access level (and ‘execute as’ in the case of web apps) is recorded in the manifest. Below is an example of the data recorded for a web app accessible to ANYONE and executes as the USER_ACCESSING (all the properties for .access and .executeAs are listed in the manifest structure documentation):

{
  "webapp": { 
    "access": "ANYONE",
    "executeAs": "USER_ACCESSING"
  }
}

Changing the property values in the manifest appears to have to affect on the deployment settings so this property appears to only be useful for information only.

For web apps, add-ons and Execution API there is also an option created deployments via Publish > Deploy from manifest…. This is a new feature in Apps Script and the developer documentation has more details on deployments.

Gmail

These are used specifically as part of Gmail Add-on deployments and used to define the name, logo URL, colors, and other setting. Going through these properties probably needs a dedicated post so in the meantime I’ll leave you with the Gmail Add-ons Manifest Concepts.

Managing manifest files with ManifestApp

There may be occasions when it is useful to make changes to a manifest file programmatically, for example, managing multiple standalone script projects. As with the standalone Apps Script project code the manifest file is also accessible using the Drive API. As the manifest is a JSON object it is easy to manipulate but if you’d like a nice wrapper to read/write manifest properties Kanshi Tanaike has published the ManifestApp library.

Note: This only works on standalone scripts (container bound are not accessible).

For our scenario let’s assume we have developed several script projects that use the Picasa library. There was recently a new version of the library published and we want to update this in all our standalone script projects. Fortunately we have a spreadsheet of all the script project file IDs that are using the Picasa library (if we didn’t have this information we could iterate across all the script projects reading the manifest for each to check dependencies).

Kanshi provides instructions for setting up the ManifestApp library. Once you’ve done that here is some very basic code to iterate across project IDs and update the library version:

function updateLibraryVersion() {
  var pic_version = 22; // version we are updating to
  // Picasa library id
  var pic_id = '1RHjbqdEN-WvQxqSFr6hdtECfIqZlEEyCfrxvF4UKnKeVPnRX-dyg0dS7';
 
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getRange("A2:B3").getValues(); // Lazy way
  for (var i = 0; i <= data.length; i++){
    var id = data[i][0];
    var MANIFEST = ManifestsApp.setProjectId(id);
    var libs = MANIFEST.getLibraries();
    // if the library is already added remove first
    if (libExists_(libs, 'PicasaApp')) {
      MANIFEST.uninstallLibrary('PicasaApp');
    }
    // add library with the version you require
    MANIFEST.installLibrary('PicasaApp', pic_id, pic_version);
    data[i][1] = pic_version; // update data for spreadsheet
  }
  sheet.getRange("A2:B3").setValues(data); // Lazy way
}

// Helper function to test if library exists in manifest
function libExists_(lib, libName) {
  if (lib) {
    return lib.some(function(el) {
      return el.userSymbol === libName;
    });
  }
}

Summary

Congratulations on making it this far in the post. Hopefully this post has so far been useful in highlighting the main features of the Google Apps Script manifest file. For most scenarios it is likely that you’ll never need to touch the manifest as project settings and setup will be recorded through your usual interaction with the script editor but knowing what is possible is hopefully useful. I’m sure Google will continue developing Apps Script manifest files and it will be interesting to see how quickly this post becomes out of date.