Create a Google Workspace Add-on file picker card with CardService that opens a Google Picker in an overlay window – Google Apps Script

Google Apps Script GWAO File Picker Title

Google Apps Script: Card Service, Google Workspace Add-on, Google Picker

So you have this awesome idea for a Google Workspace Add-on (GWAO), but you need to be able to select Google Drive files and folders as a part of your process.

Sure, you could just open up another tab and find the link and paste it in a text input or paragraph input in your Card Service build, but that is time-consuming and kinda defeats the purpose of the convenience of the sidebar.

Ideally, you would want a built-in File Picker class that would select the files and folders from the directories you need. Whelp… unfortunately, we don’t have that right now for Google Apps Script’s Card Service.

One approach might be to build out a file picker card selecting each parent’s files and folders and navigate through it like, say, a linked list. Now, I haven’t tried this approach, but looking at how slow and memory expensive it is to call your Google Drive through either Google Apps Script’s Drive App class or the Advanced Service Drive Class, I gave this a pass… for now… .

Instead, I decided to incorporate Googles File Picker API as a popup window from the sidebar, because, it’s kinda what it is designed for. Also, not gonna lie, the example at the bottom of the docs was a huge help (cough … copy and paste … cough)

Let’s take a look at what we are going to build.

Google Workplace Add-on File Picker

The homepage Card

In this tutorial, we will build a Google Workspace Add-on (GWAO) that opens a basic file picker card like this:

Google Workspace Add-on File Picker Homepage Card
Click to Expand!

As you can see, there is not much going on here. This card contains an information paragraph and then a placeholder where the Google Drive doc links will go. When you first load the card you get the italicised, ‘Please select your file and folder’. Once you have selected some files and/or folders, this paragraph section will update to a hyperlinked list of files/folders in your Google Drive.

Finally, we have the “Select Docs” and “Clear All” buttons. All pretty self-explanatory.

Hire me for our next Google Workspace project.

The Picker

Clicking “Select Docs” opens a window overlay of a Google Apps Script WebApp with our file picker inside (1).

Google Workspace Add-on File Picker Web App Overlay
Click to Expand!

You can see the delightfully helpful warning that this web app was created by not the Google.

via GIPHY

 

Now you can go ahead and navigate through the directory and select one or many files and folders in a parent folder. When you are done, click Select and those files and/or folders will be recorded and added to your homepage as hyperlinks and the web app window will close.

Note, that while you are picking your documents the homepage is locked with a loading button to prevent any other tasks to occur (2). It’s for your safety…promise. 

The homepage now displays your selection

Back to your homepage, you can now see that it is displaying your selection of files and folders as hyperlinks.

Google Workspace Add-on File Picker Homepage Card with selections
Click to Expand!

In the background, your files and folders are being stored in a PropertiesService.getUserProperty()“files” object property, for you to actually do something with them. The object looks a little like this:

[{id, name, url},{id, name, url},...]
If you need to select more documents from different folders, you can click the “Select Docs” button again >> Navigate to your desired folder >> Select your docs >> hit Select and they will be added to your homepage.
Note that duplicate selection is also handled based on the ID of the document.
If you really made a mess of things you can clear your selection which will remove the “files” property from Properties Service and refresh the homepage.
The intent of this tutorial is to give you the cleanest possible code example for you to then work out how to apply it to your own project.

Setup

The Setup comes with a big bad warning. If you are thinking about adding the File Picker to your project it is a very good idea to manually pair your Google Apps Script project to a Standard Google Cloud Platform project before your start building your project. 

Why? Because sometimes, depending on what other APIs and Services you have added to your project you may not be able to convert it afterwards. Also if you were trying to manage your Google Apps Scripts in your Google Drive directory for, neatness then you may just find – like I did – that your project won’t convert to a Standard Google Cloud Project from the default one (A glitch perhaps). 

Now if you are sitting here wondering what on earth I am yammering on about with default and standard Google Cloud Platform (GCP) projects, don’t worry. I will walk you through it in a moment.

🐐You can support me for free by using this Amazon affiliate link in your next tech purchase :Computers & Stuff! 🐐

Let’s get started.

Create your project

The first thing we need to do is to set up our Google Cloud Project. Yeah. I know we have to enter that scary work of a million buttons and options that go down a myriad of rabbit holes.

This is generally the part where most tutorials gloss over because it is time consuming and boring to produce. Being one of the most boring goats I know, this is right down my alley.

Let’s get cracking…

Create your Google Apps Script Project

Create your script from the Google Apps Script homepage ( https://script.google.com/home ). Click the New Project button, top-right. Then rename your project to whatever you would like to name it.

Navigate to your Project Settings:

Apps Script Project Settings for GWAO Google Picker
Click to Expand!

Scroll to the bottom and you will see the Google Cloud Platform (GCP) Project header.

Out of the box, your Google Apps Script project comes with a Default Google Cloud Project setup that is essentially hidden. It does all the setup and heavy lifting for you when you create a project and you can just work on writing your code. However, this Default setup doesn’t always work with every API and library, and sometimes you need to use the similarly named Standard GCP instead. Like when we need to use the File Picker.

Click on the Change project button and you will get some instructions.

Google Workspace Add-on File Picker Change to GCP Stantard select here
Click to Expand!

Click on the link where it says, here. Or… well … here, to get to  https://console.cloud.google.com/home/ .

This will open the big bad scary Google Cloud Platform console.

Creating a Google Cloud Console Project

In the blue bar at the top, select the project dropdown.

Google Workspace Add-on File Picker Change to GCP Stantard select project
Click to Expand!

Weirdly, you will get a popup window with a list of any projects that you have instead of a dropdown menu. In the top-right, select ‘NEW PROJECT’.

Google Workspace Add-on File Picker Change to GCP Standard select NEW project
Click to Expand!

This will send you to a new project builder. Here, you can enter the name of your project (1). It might be a good idea to name it the same as your Apps Script one for easy reference.

Once you are done, hit, Create (2)

Google Workspace Add-on File Picker Change to GCP Standard name project

This will build your project and return you to your last project. You will notice in the top right that a notification will appear to let you know your project is built.

Google Workspace Add-on File Picker GCP Standard select built project

If you don’t see this, select from the project ‘dropdown’ again and select your new project from the list.

You will be navigated to the dashboard of your newly created project.

Google Workspace Add-on File Picker GCP New project dashboard page
Click to Expand!

Now your sharp eyes might have found the Project ID and you are eager to get this job done. Unfortunately, we are not quite there yet and if you input the project id into your Apps Script Project IDE right now you are likely to get an error like this:

Google Apps Script GCP OAuth Consent screen warning
Click to Expand!

How do I know? Well, let’s just say I thoroughly research my tutorials 🤣.

We’ll need to configure our OAuth consent screen

Open up the side-bar menu of your project and select Apis and services > OAuth consent screen.

Google Workspace Add-on File Picker GCP nav to OAuth Consent Screen
Click to Expand!
User Type

Your first task is to choose between your user type.

Internal will be available if you are running a Google Workspace (paid) account. This will give your app access to users within your domain.

The External option will launch you into the test environment as part of the process of publishing an external app.

I usually click External (1) here and stay within the test environment and then either change my user type to Internal when I am ready to publish within a Google Workspace environment or if I really want to punish myself, work my way through an external publishing process to publish it on the Google Marketplace.

Once you have made your decision, click Create (2). 

Google Workspace Add-on File Picker GCP nav to OAuth Consent Screen Choose User Type
Click to Expand!
OAuth Consent Screen Info

Here you are setting up your consent screen information that your users will see the first time they run the app. You can see an example of this on the right-hand side of the screen:

OAuth Consent Screen example
Click to Expand!

First, you will need to enter:

  1. The app name
  2. Support Email (This will likely be you)
  3. The app logo – Yeah. It is a bit much if you are just building this internally, but it is what it is. If you want to do it quickly, just add the example image below.
Main Image by Vecteezy

This is what your screen should look like:

Google Workspace Add-on File Picker OAuth consent screen App Information
Click to Expand!

You should be able to skip the App Domain info for now, but if you intend on publishing the app you will need to update this later.

Finally, add your email in the Developer contact information and select Save and continue. 

Google Workspace Add-on File Picker OAuth consent screen App Information Developer contact
Click to Expand!
Scopes

You will now be directed to the Scopes page. Here you add the scopes for your project.

Apps Script Project Settings for GWAO GCP Scope adding
Click to Expand!

Normally these are added for you when you run your project for the first time or when you update your project. You can find your scopes in two places in your Google Apps Script editor:

  1. At the bottom of the Overview screen of your project.
  2. In your appsscript.json manifest file.

Unlike a normal project, your scopes will also need to be registered in your Google Cloud Console.

For our project, we will need the following scopes.

It’s often hard to know what scopes you are going to use for your project. So it is likely that you will need to come back to this point and add them again.

Click on ADD OR REMOVE SCOPES. A right-sidebar will appear. You can search through and find the scopes from the list above or simply copy them and paste them in under the Manually Add Scopes header (1). Make sure each scope is on a new line.

Then click Add to Table (2).

Apps Script Project Settings for GWAO GCP Scope adding 2
Click to Expand!
You will see your scopes added to the scope list above in the sidebar (1).
Once you are happy, click UPDATE (2).
Apps Script Project Settings for GWAO GCP Scope adding 3
Click to Expand!

Your main Scopes panel will update identifying what type of scopes you have.

Click SAVE AND CONTINUE (2).

Apps Script Project Settings for GWAO GCP Scope adding 4
Click to Expand!
Test Users

Next, you can add in any test users to your project. Simply add a Gmail or Google Workspace add-on here to give the user permissions to test your GWAO.

Apps Script Project Settings for GWAO GCP Test Users
Click to Expand!

Note! While you are testing, you will also need to add these users as editors to your project. More on this later. 

Finally, you will get a summary of your Authentication inputs. Scroll to the bottom and click > BACK TO DASHBOARD.

Add the APIs you will need

Next in the sidebar of APIs and Services select Library.

Apps Script Project Settings for GWAO GCP API Library
Click to Expand!

You will be navigated to the API library.

Apps Script Project Settings for GWAO GCP API Library2
Click to Expand!

Here, search for “Google Picker”.

Apps Script Project Settings for GWAO GCP API Library Google Picker Search
Click to Expand!
Apps Script Project Settings for GWAO GCP API Library Google Picker Enable
Click to Expand!
 This will take you to the dashboard for Google Picker. There is nothing for you to do here, but we do need to grab one more API.
In the Navigation panel select APIs and services > Library.
Apps Script Project Settings for GWAO Drive API 1
Click to Expand!
In the search menu, type ‘Drive API’ and select the first item.
Once you have been navigated to the Google Drive API screen select Enable.
Apps Script Project Settings for GWAO Drive API 2
Click to Expand!
You are all set. There is nothing to do on this screen so click on the Navigation panel and select Home.
Apps Script Project Settings for GWAO GCP home
Click to expand!

Connecting your Google Apps Script project to your Google Cloud project

Back in your home screen, find the Project Info card and grab the Project number. Select > Copy.

Find the project number for a GCP Google Cloud Project
Click to Expand!

Note! Don’t worry. I’ve long since deleted the project so all the IDs and Keys won’t work anymore. 

Next, head back to your Google Apps Script project tab. Keep your cloud project open in a separate tab. You will be accessing it again (I know, I know. I can feel the groans).

Anyway, back to the Safety of our Google Apps Script editor and our project. In the menu bar select, Project settings. Then scroll to the bottom.

Click Change ProjectAnd then paste in the Google Cloud project ID. Then click the Set project button.

Apps Script Project Settings for GWAO add GCP Project ID to Apps Script Project
Click to Expand!

Your project number will be added.

Now we can go make a coffee, come back and finally write some code.

The Code

We’re going to set up our code into three parts:

Code.gs – The backend handler for the Card Service for the Google Workspace Add-on (GWAO) and storage of file data.

WebApp.gs – Initialisation of the web app and client-server call functions.

FilePicker.html – Essentially the basic index page layout with links to CSS, our Javascript for our file picker and some simple HTML to create a loading page.

JS.html – This page drives the Google Picker loading and closing process. It accesses the developer API key and the OAuth token.

appsscript.json – Google Workspace Add-ons require some manual grunt work inside this JSON file that Google Apps Script usually handles for you in other circumstances.

I’ll provide the code below and then discuss how to test and run it. If you are interested in the code breakdown, there will be a large section after that for you to follow along with.

appsscript.json

Your appsscript.json file handles the configuration of your Google Apps Script project. Card Service relies heavily on this file to set it up.

To access this file go to Project Settings and check the Show “appsscript.json” manifest file in editor box. When you return to the editor, you will see the file in your file manager side panel.

Apps Script Project Settings for GWAO show appsscript JSON
Click to Expand!

Click on the appsscript.json file and then paste in this data:

You will need to change the timezone details to your own timezone.

Code.gs

You will need to change the pickerButtonWidget.setURL() value to your own web app URL in the buildSection() function. More on this in the Quick Use Guide below.

WebApp.gs

Again, you will update your project API key here. More on this soon.

FilePicker.html

JS.html

Quick use guide

There are a number of steps to get you up and running. Let’s go through each one now.

Add the Developer API Key

In your Google Apps Script editor, navigate to the WebApp.gs page under the pickerConfig() function on line 46 you will need to get the Developer API Key.

Where do you find that? Back in your Google Cloud Project.

via GIPHY

If you have your Google Cloud project open in a separate tab, make sure you are still in the connected project.

Next in the sidebar head to Apis and services > Credentials.

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 1
Click to Expand!

Then click on + Create Credentials > API Key.

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 2
Click to Expand!

Your API key will be generated in a popup window.

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 3
Click to Expand!

Keep this API secret. Anyone can use this key to run Google processes on your dime.

Fortunately, there are a few ways to restrict your API key. For us, we only want the key to be used for the Google Picker and when using our Web App URL. We can do this by clicking the RESTRICT KEY button on the bottom right of the window.

API restrictions

The restrictions window also give us an opportunity to rename our API key should we have more for our project. We’ll call ours, Picker API.

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 4
Click to Expand!

Under the Applications Restrictions, we can limit the URLs that the API can be called from. Click on HTTP referrers (websites) (1) radio button. You will notice that your options have expanded to allow you to add in your own URL or URL pattern. You will also find instructions on creating these patterns on the right side of the screen. 

For us, our pattern will be the URL of the Web App that we generate from either our test or development deployment. Well… kinda. The URL pattern is a little different. For now, let’s just restrict it to this path (2): 

*script.googleusercontent.com/*

Then select, Done (3).

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 5
Click to Expand!

We can further restrict the use of the API key by deciding what it will be used for. In the next section, select Restrict key and then use the selection menu to find the Google Picker API and check the box.

Finally hit Save.

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 6
Click to Expand!

You will then be returned to the Credentials page. Now, go ahead and copy the API key you created.

Apps Script Project Settings for GWAO Create API Key in Google Cloud Console 7
Click to Expand!

Head back over to your Google Apps Script editor and paste the key into line 46 of the WebApp.gs page.

Deploy the Card Service and Web App

Our next task is to deploy the Card Service. We will do this in a test deployment environment for this tutorial.

In your Google Apps Script editor select the Deploy button (Top right) > New Deployment.

You will get a popup window. In the top-left select the settings cog (1) and ensure that both Web app and Add-on have been checked (2).

Apps Script Project Settings for GWAO New Deployment
Click to Expand!

Next, we need to update our details. First, add a description (1). Then change the Execute as to User accessing the web app (2). Next change the Who has access to either Only myself if you are testing alone or Anyone with a Google Account if you have others testing (3). Finally, hit, Deploy.

Apps Script Project Settings for GWAO New Deployment 2
Click to expand!

You will then be navigated to a summary page. Go ahead and close the window.

We will run our script using the test deployment. To do this, click on the Deploy button again > Test deployments.

Here, we want to copy the Web App test deployment URL (1) and install a test GWAO sidebar in Gmail (2). Then you can click done (3).

Apps Script Project Settings for GWAO New Deployment 3
Click to Expand!

Head to your Code.gs file and update line 87 by pasting in the test URL for the web app.

 

Test the GWAO

Head over to your Gmail account and refresh your page. You should notice that your new Google Workspace Add-on is now available. Give it a click. On the first run through you will need to Authorise Access for your add on.

Apps Script Project Settings for GWAO Test in gmail and authorise access
Click to Expand!

Go ahead and follow the prompts.

Up and Running

You should, by now have your very own file picker up and running for testing and playing around with. Go ahead and give it a try.

The script is pretty well documented but if you want a more detailed discussion on how everything works or just want to skim to points of interest, read on.

Create and Publish a Google Workspace Add-on with Apps Script Course

Need help with Google Workspace development?

Go something to solve bigger than Chat GPT?

I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.

Schedule a free consultation today to discuss your needs and get started or learn more about our services here.


Code Breakdown

There is a lot of interconnectivity going on in this project. So I thought it would be a good opportunity to build my first developer flow chart. I hope it makes things easier to understand where things go (If you are a pro at building dev flowcharts, I would love some feedback).

CardService Google Picker Apps Script
Click to Expand!

I have spit the flowchart into 3 sections:

  • Add-on UI client-side – This is basically all the sidebar interactions that the user has.
  • Apps Script Server-side – This is all the backend construction of the GWAO cards, the web app and storages of data.
  • Web-app UI  client-side – These are web-app interactions from the user. Essentially the web app is just a container for the file picker so all interactions occur in the File Picker, while sending the data to the server-side occurs in the JS.html file.

I’ll go into greater detail on this as we explore each page and function, but first, we need to set up our appsscript.json file.

appsscript.json

Timezone

Line 2

"timeZone": "Australia/Sydney",

This is part of the standard template for this file. You can change the timezone to your own country. I have a discussion on changing the timezone here.

Dependencies

Lines 3-11

When you enable advanced services, you will find that this part of your JSON file is updated with the appropriate dependency. Because you copied and pasted in my appsscript.json file, the Drive API advanced service has been added.

Have a go a cutting this out of the JSON file and saving it. You will notice that the Drive item in the file manager sidebar disappears.

Make sure you paste it back and save it again.

Note that because you are now connected to the Standard Google Cloud Project, you have also enabled the Drive API manually inside the GCP console.

oAuth Scopes

Lines 13-18

Normally, you can just run your code and accept all the permissions when the warning comes up and your scopes will be added to your appsscript.json file.

However, for this project, we have two circumstances that prevent us from doing this:

  1. Google Workspace Add-on Card Service: It is a good idea to explicitly add the add-on scopes to this file. This gives you greater control over the minimum scopes that you need to effectively run your project and when it comes to publishing your project to the marketplace Google will be expecting this.
  2. Standard Google Cloud Project: Your project is connected more explicitly to your Google Cloud project. Remember we added in our scopes into our GCP console when we set up. If you need to add more scopes as your project progresses, then you will need to add them in on your GCP console too.

Add-ons

Lines 20-31

As I have mentioned in the tutorial previously, there is a lot of formatting and setting up you can do to create your Google Workspace Add-on (GWAO) project from the appsscript.json file.

I have kept things very bare-bones here, so we can focus on our File Picker implementation. You can find out more here.

First, we have our ‘common’ key parent object. This is all the information for the add-on that is shared in all accessible locations like, Gmail, Drive, Slides, Docs, Sheets  etc. 

For our project, we chose the following details, which are essentially the minimum requirements for a GWAO. Let’s take a look:

  • name: This is the name of your project. It will appear at the top of your project when you open it or when you hover over your project’s icon.
  • logoUrl: This is the link to our little icon I shared earlier. You can add your own icon here. It is usually good practice to use the same logo in your Authorization page.
  • useLocaleFromApp: If you set this to true you will get a bunch of the user’s locale information that might be helpful for your project. This will be returned as an event (e) object for your homepage function. Incidentally, this is why we needed the script.locale scope. Here is what my event info looks like:

  • homepageTrigger: This object assigns the function you will use to run when your GWAO is selected by your user. For us this the onhomepage function.

In addition to our ‘common’ property, we need to assign all locations that you want the app to be displayed in. For us, this is just ‘gmail’. If you have location-specific information or initialisation functions that you need to add, you can also add this information here as you did with the ‘common’ object.

Code.gs

onhomepage(e)

This function is initialised when the Google Workspace Add-on (GWAO) is selected from the sidebar. We enabled this function to be triggered back in the appsscript.json file addOns > common > homepageTrigger > runFunction.  This function takes an event (e) parameter of locale information, that we don’t use in this project, but you might find useful in your own project.

Finally, the function returns the createSelectionCard(e) function.

You will need to return this function so that the Card Service build returns to the onHompage function for it to be read properly by Apps Script. All Card Service build scripts need to be returned to the first level function.

createSelectionCard(e)

The createSelectionCard(e) function is called from the onHomepage() function. It creates a newCardBuilder environment and once all elements of that card are added, it builds the card and returns it to onHomepage().

We add one section to this card which is compiled in the buildSection() function. Line 11

rebuildSelectionCard(e)

The rebuildSelectionCard(e) function is similar to the createSelectionCard(e)function, however, it is used when the homepage card needs to display new data or be refreshed.

The function’s first act is to set a PropertiesService property, “filePick” to false. This will be used in the getFilesAndFoldersDataWidget() function to inform it that the text should be just instructions on what to do and clear out the list of files and folders from the PropertiesService.

Note on line 17, we invoke the newNavigation() method that allows us to call updateCard() to change the nature of the homepage card.

buildSection()

ThebuildSection() function builds the section of the main card. In our little example, we only have one section that we call the detailsSection (Line 49).

Here we set our header to “Front Page” and then add three widgets that are contained in the method above inside this function:

  1. textWidet() (lines 10-14): This is a pararaphWidget  with instructions on what the user needs to do.
  2. getFilesAndFoldersDataWidget() (separate Function): This is another paragraph widget. However, this one is updated dynamically. On initial load, the widget provides further instructions to the user, but when we start to add files and folders, it will populate with a list of hyperlinked files and folders. More on this in a bit.
  3. buttonSet() (Lines 39-44): This function calls the ButtonSet class which is essentially a placeholder for you to add a bunch of buttons. This class allows us to generate our two buttons:
    1. buttonPickerWidget() (Lines 16-27): This function will call the button class. First, we will give it a title of “Select Docs” and then use the setOpenLink() method to first direct our button click to open our web app with setUrl(). Then we can determine how we want to open our window; either as a separate tab in your browser or as an overlay. We don’t want to navigate our user away from their current tab so an overlay, or pop-up window, is preferable. Also selecting this option allows us to set a loading image that locks the sidebar until the overlay window is closed. Here we need to use the onClose() method set to ‘reload’ when the overlay is closed. This will also allow us to update the card with our new data.
    2. buttonFileRemoveWidget() (Lines 31-36): This button handler sets the “Clear All” title and then runs the rebuildSelectionCard(e) function.

Once the details section has been built, it will be returned back to either createSelectionCard(e) or rebuildSelectionCard(e) functions from which it was called.

getFilesAndFoldersDataWidget()

The getFilesAndFoldersDataWidget() function is called from the buildSection() function.

Set Variables

In our user Properties Service, we have stored a property called ‘filePick’ that is set to either true or false (Line 10). When it is set to false it means that the user has clicked the CLEAR ALL button and rebuildSelectionCard(e) has been run. If it is set to true it means that files have been stored and need to be displayed in the GWAO side-bar.

On line 11,  we reach into the user’s Property Service again and check the ‘files’ property. These files are stored as a stringified array of objects containing two properties:

[{url: , name: }, {url: , name: }, ...]

Our final variable is the paragraph placeholder (Line 12) this will be returned with either a list of hyperlinks or the preset instructions.

No files or clear all files

If there are no files or the user has selected CLEAR ALL as indicated by a false filePick variable, then the initial instructions text is assigned to the paragraph.

Next, the clearFilesFromPropServ() function is called which deletes the ‘files’ property in the Properties Service (Line 17). I’ve separated this out to another function because I often run some other processes as a part of clearing files, like changing some indicators. For example, perhaps you want to reset the ‘filePick’ property to true.

Else

If we have files in our ‘files’ property and ‘filePick’ is set to true, then we want to assign our links that we selected from the Google Picker to our paragraph variable.

We use a forEach loop to iterate through each stored file or folder and generate a hyperlink and a line break that we will concatenate into one large string.

Last bit

Finally, we reset our ‘filePick’ to false and then add our paragraph to our paragraph widget that will be returned to buildSection().

WebApp.gs

This server-side page initialises the web-app with a mandatory doGet() function and then handles calls from the clientside Javascript.

doGet()

For a Google Apps Script to run, you need to either have a doGet() or doPost() function within your project.

In this function, we call on the HtmlService to generate our HTML file for our file picker window. We’ll use the createTemplateFromFile() method so that we can use scriptlets inside our main FilePicker.html file to include our JS.html file. You can find out more about this here:

https://yagisanatode.com/2018/04/15/google-apps-script-how-to-create-javascript-and-css-files-for-a-sidebar-project-in-google-apps-script/

We reference our FilePicker.html as our main run file and then evaluate or let HtmlService interpret all the scriptlets. Finally, we will set the title for the page (Line 11) before returning the built Html Service.

include(filename)

The include function ( I believe I acquired from one of the Google Docs many years ago), in conjunction with the use of the createTemplateFromFile method in the previous function, allows us to insert or import other files into each other. For us, we want to import our JS.html file into our FilePicker.html. Keep an eye out for this scriptlet in the FilePicker.html code:

<?!= include("JS"); ?>

pickerConfig()

The pickerConfig() function is called from the onApiLoad() function in the JS.html file as the File Picker is being initialised. It collects our oauthToken which we glean from the ScriptApp.getOAuthToken() call. It also collects the developer key that we pasted in from our Google Cloud project during setup.

Client-side this object will be stored as the config variable.

Keeping this key server-side is a safer way of storing the key so that a nefarious user can’t grab it and go on a Google Services spending spree on your API key. Just remember to set the config variable to null once you have used it so it isn’t stored client-side. Also, don’t forget that we also set some URL and type restrictions to the developer API key back in the Google Cloud console as an extra line of defence so that it can’t be used for other things.

storeDriveSelections(fileId)

Inputs and variables

The storeDriveSelections(filed) function is called from the client-side pickerCallback function on google.script.run. It will store a list of objects containing the doc or folder ID, the name of the doc or folder and the associated URL of each selected doc.

This is an important function for when you want to convert this tutorial to your own project.

The returned results look like this:

[{id, name, url},{id, name, url},...]

Our main aim here is to combine any currently stored link values with the ones currently being added from the user’s selection.

First, we will grab our current list of links from the ‘files’ property in Properties Service. We’ll store this in the storedDocs variable. (Lines 12-13).

Combining our links with our stored links

Next, we want to combine our parameter input fileId with the storedDocs. However, we also want to ensure that there are no duplicates. I found a neat way of doing this from V.Sambor over in StackOverflow.

Inside our updateArray arrow function, we return a new array of objects. Inside this array, we use the spread syntax and new Map() constructor to create an object of key-value pairs.

To create these pairs we will create our array using the spread syntax in an array again to join our fileId and storedDocs. Next, we map this array creating an array with a key which will be our item.id and our item. Because all items in this quasi object need to be unique, it will only display each object with the same item.id only once, capturing the last iteration of that id. This then returns only unique items based on the id of each object of the combined data.

Checking we have stored links

let docsAll = (storedDocs === null)? fileId : updateArray();

Here we use a ternary operator to check if ‘files’ in Properties Service does not exist. If this is the case the storedDocs variable will report null and we just have to add the fileId to our docsAll variable. However, if ‘files’ exists we will call the updateArray() method we created above. Line 22

Storing our link data

Next, we need to store our link data back into our Properties Service ‘files’ property. (Lines 25-26)

We should also set our ‘filePick’ property to true to indicate to the getFilesAndFoldersDataWidget() function in Code.gs that we have documents or folders that we want to display. (Lines 29-30)

clearFilesFromPropServ()

In this final function, we clear the ‘files’ property of the properties service.

This is invoked from getFilesAndFoldersDataWidget() if the ‘filePick’ property is set to false.

FilePicker.html

The FilePicker.html page is essentially the index for our web app. Visually, all it does is create a black background with a red text indicating that the file picker is loading or an error message should an error occur.

Two important things to note here are:

  • The HTML Sevice template scriptlet that I used to call the include function in the WebApp.gs file to import the JS.html file. (Line 8)
  • The script to load our file picker. Note the onload=onApiLoad function call that will be called in our JS.html file. This will run our code as soon as the page is loaded and have the Google Picker come up straight away.

JS.html

This file is the main driving force that builds and interacts with the Google Picker. I have taken much of this from the Docs and modified what I needed. No need to reinvent the wheel, right 😉?

Let’s go through it to give you a bit more clarity and hopefully help you avoid the gotchas that I got stuck with when I first tried to build this.

onApiLoad()

gapi.load()

The var pickerApiLoaded variable indicates the state of Picker API service indicating whether it has loaded or not. This initially set to false.

When the FilePicker.html file loads this Google API loader script is called:

 

As you can see the onApiLoad() function is called (calledback?) from this script allowing us to access the gapi class. Here we call the load method on gapi to get the library that we need (Line 12) . The library name is put in the first argument of this method. For us, this is ‘picker’ (Line 12).

The second argument is any actions or calls that we want to make to the library. From what I understand here, we call the ‘callback’ method of the picker library to run our callback function and reset our pickerApiLoaded to true to acknowledge that we are now running the Google Picker. Lines 13-14

Grabbing our OAuth token and Developer Key.

Getting this out of the way straight up if you are having trouble with the developer key you might also try the keywords ‘API key’ when searching for help. It got me stuck for a little while.

On lines 20-23, we use the Client-side access API google.script.run to access our OAuth token and developer key from the pickerConfig() function inside WebApp.gs.

If there is an error in our server-side code it will throw an error to our errorMessage() function, alternatively, if all goes well createPicker() will be run with our OAuth token and developer API key as the config parameter.

More on the client-side access API here:

https://yagisanatode.com/2020/11/11/google-apps-script-how-to-create-a-basic-interactive-interface-with-web-apps/

CreatePicker(config)

The createPicker(config) function is a callback function that is run after the server-side pickerConfig() function from the WebApp.gs file is successfully executed. The config parameter is the returned results of the pickerConfig() file and contains the oauthToken and developerKey objects.

Our first task is to check to see that we have everything we need to run the Google Picker. On line 9, we confirm that pickerApiLoaded is set to true and we have a value in config.oauthToken.

Setting up how Google Picker looks

We will need to set up how our Google Picker is displayed and then call on these items when we build the picker.

First, we set the dimensions of the File Picker window. I tried to match these dimensions to fit neatly inside our web-app window when it pops up as a dialogue box. Line 11

Next, we set up the picker UI preferences.

Now you might be wondering, where the google.picker.selectedMethod is coming from. The google call is derived from when we accessed the API library earlier. From  what I can see, you call your selected API by doing google.yourAPI.selecteMethod.

Back to our view variable setup on lines 14-18 we first call the DocsView subclass to set up our view. We want this view to include all folders to be traversed with setIncludeFolders(true) and we also want to be able to select a folder with setSelectFolderEnabled(true).

The next task is to set the label or title of the picker, which we set to ‘My Drive’. Line 17

Apps Script Google Picker label
Click to Expand!

Finally, we set what directory this view will start from. For simplicity sake we will set our directory to ‘root’. However, if you want it to be set from somewhere else you can add the folder ID here. Line 18

Note: You can create multiple views that will be accessed at the top of your picker. This can be done by adding a new instance of new google.picker.DocsView() and filling in the required information then adding another setView() to the main picker builder. However, be warned. This label header bar seems to be a bit of a blind spot for most users; they don’t notice it is there. I’ve had to resort to providing buttons on a main page to different directory locations and an instance of the file picker is loaded for each instead. 

Building the picker

To build the Google Picker we call a new instance of new google.picker.PickerBuilder(). Let’s look at what we included in the build:

  • .enableFeature(google.picker.Feature.MULTISELECT_ENABLED): This lets the user select more than one file or folder in the same director by holding ctrl + clicking selected items or by clicking and items and holding shift + selecting the end range of files or folders, or by simply holding down the left-mouse button and dragging across a range. Why and I mentioning this? Because it is a very good idea to point out to your users or they won’t know.
  • .hideTitleBar(): Hiding the title bar give us a little extra space to display our picker.
  • .setOAuthToken(config.oauthToken): Required: We grab our OAuth token from our config file.
  • .addView(view): Required: This is where we add our view configuration. As I mentioned earlier, you can add more than one and they will come up as labels in the header of the Picker.
  • .setDeveloperKey(config.developerKey): Required: We grab our Developer (API) key token from our config file.
  • .setOrigin(google.script.host.origin): Required: This needs to be set because we are calling the picker from our Web-App.
  • .setSize(DIALOG_DIMENSIONS.width, DIALOG_DIMENSIONS.height): self-explanatory.
  • .setCallback(pickerCallback): Required: What happens when either the ‘Select’ or ‘Cancel’ buttons are clicked. The pickerCallback() will send our data to WebApp.gs to be store or cleared respectively.
  • .build();: Required: This builds in all the above details.

Once our picker is built we set the picker to visible. Line 34

Finally, we clear our config file so no one can see our developer key. Line 37

pickerCallback(data)

The pickerCallback(data) function is run when the user either submits or closes the Google Picker.

The data parameter contains an object of properties about the picker and your selections. With two selections your data object would look similar to this:

For our purposes, we just want the docs property information that is an array of each selection of files or folders as an object.

If ‘select’ button is clicked

If the ‘Select’ button is clicked then the google.picker.Action state is ‘PICKED’. We check the action state by checking data.actionLine 1 of the snippet above

Probably the most interesting bits of each selection for us is going to be the file or folders, URL, name, id and type properties. So we have selected them to be returned server-side. We then iterate through the date.docs object and grab these details and put them in the filesAndFolders variable. Lines 5-10

Finally, we use google.script.run again in order to attempt to send our data to our server to be stored using the storeDriveSelections() function before calling our client-side closeWebAppWindow() function to close our picker and popup window in one fell swoop. Lines 16-19

If ‘Cancel’ button is clicked

If cancel is clicked then we call the closeWebAppWindow() function and that’s it.

Create and Publish Google Workspace Add-ons with Apps Script Course 300px

closeWebAppWindow()

The closeWebAppWindow() function closes not only the picker but the entire pop-out window.

errorMessage(e)

The errorMessage(e) function is called from the withFailureHandler(e) and will report error messages to the body of the popout window. You could also use this function to report other errors that might be caught in a try-catch.

Conclusion

As you can see, it is quite a process to get a File Picker popup window up and running in a Google Workspace Add-on. Perhaps as the CardService class grows and is developed by the Google team, a simpler implementation might be provided for us, but that is a bit of wishful thinking. Until then, this is what we have and I am grateful that something like this exists.

I really wanted to get into the weeds on this process. Firstly, I could not find a tutorial on providing the file picker specifically for a Google Workspace Add-on. Secondly, the picker tutorials seem to miss a lot of the setup information; particularly when setting up the Google Cloud Project side of things. I understand why, it’s a lot of work to get all the screenshots together.

So how have I used this. In a recent project for a client, I have modified this script so that I have a popup window that the user can choose from a bunch of directory locations (including shared drives) and they will be saved and stored for processing. Here, I disabled the autoload of the file picker and let it appear when the user picks a file directory from a button set. Like I said earlier, the labels at the top of the picker seem to be a bit of a blind spot for users.

Anyways, I really hope you found all or part of this tutorial useful. I would love to hear how you have applied the code to your own project. And if you did find it helpful, please consider buying me a beer or a coffee by hitting my donate button. It makes the full week I put into building, testing,  writing and editing this tutorial worthwhile (top-right sidebar).

~Yagi

14 thoughts on “Create a Google Workspace Add-on file picker card with CardService that opens a Google Picker in an overlay window – Google Apps Script”

  1. I am very excited to see this tutorial as I have tried to and failed to setup the file picker before. Are there earlier tutorials on add-ons, card service or Standard Google Cloud Platform which I should do first. I have been using Tanaike’s code as it does not require all that fancy stuff.
    https://gist.github.com/tanaikech/96166a32e7781fee22da9e498b2289d0
    Thank you as always – I will see how far I can get but really think I need some pre-lessons.

    1. Hi L.Klein,

      Tanaike is a legend. He really has some great stuff out there.

      It would be a little tricky to go his approach with Card Service, because you would need to refresh the card each time. Definitely not impossible, but just a pain.

      There isn’t a lot out there about Card Services at the moment, but stay tuned. I hope to get a little series going on this. Just recently, however, I did stumble across a post from Steve Bazyl ( Boss man in charge of the Google Apps Script team) who came up with a cool post on The Card Builder Tool and creating a chatbot with Card Service.

      I think you should be at a pretty good level to tackle this tutorial. It will just take a little time to work through.

      Best of luck.

      ~Yagi

  2. Wow Yagi! Your tutorials are the BEST. I always learn a lot from you.

    1. Hi JD,

      Thanks for the kind words.

      ~Yagi

  3. Hi Yagi,

    Thanks for this tutorial, it has helped me to achieve what I wanted to do. Also all your ressources are immensly helpfull to me to learn google app script !

    I am now trying to improve on what you did here, by dynamicaly refreshing my add on card when the file is selected. This works well in your case because you use .setOnClose(CardService.OnClose.RELOAD)); but you are on the “main” card so it does reload the main card without problem.
    In my case, my file picker is in an other card on top of the main one, so when the picker reloads the add on, it goes back to the main card. Do you see any way to either prevent the reload to close my opened card or to pass a parameter to the reload action in order to reopen the card that was on top ?

    Thank you

    1. Hi Quentin,

      Yes, it will reload the main card. My approach is to generally store a card indicator in Properties Service, cardStatus: pickerupdate for example and rebuild the card and check for the card indicator each time. Then onHomepage() check the cardStatus and navigate to the appropriate build for the card.

      1. Hi Yagi,

        Thank you so much for your answer, I did not think of that before ! So now that you gave me this amazing idea, I tried all day to make it work but impossible. Actually, I am blocked because it seems that the homepage function can only return a built card. But what I actually want to do is build the main card and add a navigation to push the second card I actually want to show (which is the one where the picker is called and that should be refreshed with the picker selection).

        Just so that it’s perfectly clear :
        There is card1 acting like a menu
        card1 is build on the homepage function
        A button on card1 creates and builds card2
        The file picker is on card2
        Once a file is selected, the picker reloads the add on so if I don’t do anything special, it goes back to card1
        I would like that on reload, it loads card1 and card2 on top of it (so that I have the back button in the header to return to card1)

        I can make a conditionnal call to the card2 build when I am in the reload case, but it is not what I want as in this case I don’t have the back button in the header to go back to card1.

        Unfortunatly, when I try to return something like that on the homepage function, it doesn’t work (I get a strange “content not available for this message” error and the refresh of the add on is not possible anymore) :
        var card1 = createMainMenuCard();
        var card2 = createFilePickerCard();
        var nav = CardService.newNavigation().pushCard(card1).pushCard(card2);
        return CardService.newActionResponseBuilder().setNavigation(nav).build();

        Any idea ?

  4. Yagi, you are always an inspiration. are there any news about CardService that could make all this stuff easier and ‘more native’? thank you lot!

    1. Hey Dani,
      Thanks for the support and kind words!

      I don’t think the general structure of CardService for Apps Script will ever change. However, they are making some minor ‘improvements’. In a recent update they now provide: ‘multi-select’ and ‘columns’.
      You can learn more here: https://developers.google.com/apps-script/docs/release-notes#July_25_2024

      I am planning on providing a small tutorial on these two updates in my Card Service course.

      CardService and other Apps Script APIs are generally very intuitive wrappers for object building.

      A more traditional REST API approach is to use the HTTPS runtime (But a lot of unnecessary setup work): https://developers.google.com/workspace/add-ons/reference/rpc/google.apps.card.v1#selectioninput

      Cheers,

      Yagi

      1. I’d like your opinion, if you can. I have split up your code in order to build a library, so that the code can be called from different addons to build a filepicker section. all the web app sections, plus a part of code.gs are now into the library. I moved builder functions in the calling addon code. I have rewritten some functions so that they return a card section, instead of building the whole card. the card itself is built by the calling addon code. (when it will be complete, I’d like to share it with you, if you want to). I am building a more complex card, containing further widgets, in order to ask to users additional information, together with the filepicker widget. coming to my the question: when the filepicker overlay window is closed, the addon in loaded again (so the whole card is built from zero). selected files are retrieved from user properties. how can I preserve the additional values already entered by the user (I mean, in the other widgets)? should I use properties to store and retrieve all of them? is there a smarter way on your opinion? thank you anyway, cheers

        1. You certainly can use Property Service or Cache Service to store the data. Also, take a look at what data the event (e) parameter provides in terms of your input data.

          Looking forward to see your end result.

          ~ Yagi

  5. Hello Yagi, thank you for your contribution your knowledge! I have tested my addon it works from side addon menu, but after installation card menu from extension to launch add-on does not support addon card from displaying. can you give some information to make custom menu for gwao cardservice to start? appreciate your feedback in advance

Leave a Reply