SpreadsheetApp.getActive() fails in onOpen trigger in Google Sheets addon - google-apps-script

I would like to read the Active spreadsheet and add menu items based on that. Below is the code in OnOpen(e) trigger:
function onOpen(e) {
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
//add menus based on values of sheet
}
I am getting below exception:
[Exception: You do not have permission to perform that action.]
I have gone through the documentation and found that the AuthMode is None in OnOpen trigger which does not allow access to any services that require authorization.
Please suggest if there is any other way to accomplish my task.
Thanks.

It's not possible to access the spreadsheet unless the owner or editor of the spreadsheet uses the add-on in that spreadsheet. As written in the official document,
However, because an editor add-on automatically runs its onOpen(e) function to add menu items when a document opens, the behavior above adds some complexity to Apps Script's authorization rules. After all, users wouldn't be comfortable with an add-on accessing personal data every time they open a document.
The add-on needs to be enabled for the document for the addon to get access to the document. When it's enabled, onOpen() runs in AuthMode.LIMITED, where you get access to the bound document. This "enabled" state is caused by
Getting an add-on from the store while using that document, or
Using a previously installed add-on in that document
In all other cases, add on runs in AuthMode.NONE. In this mode, You are only able to add menu items without access to any data including the bound spreadsheet. Add Menu items to request access to such data. Then, after getting AuthMode.FULL, the rest of the workflow can be done.
References:
The complete lifecycle

Related

How do you run function in Google Workspace Addon when Google Spreadsheet is opened?

I have some trouble understanding this. In Editor Addons it is easy to run a function for example to create custom menu items by using onOpen trigger. But I cannot figure out how I would achieve the same result with Google Workspace addons.
There are no simple triggers available so I am left with 2 options which do not satisfy my needs:
I use a manifest homepageTrigger
"sheets": {
"homepageTrigger": {
"runFunction": "initializeMenu"
}
}
The problem with this is that this function doesn't run until you click the Addon icon in the panel on the right.
I create an installable trigger
ScriptApp.newTrigger("initializeMenu")
.forSpreadsheet("XYZ")
.onOpen()
.create()
The problem with this one is that I need to provide a specific Spreadsheet ID. But I want my addon to work on EVERY Google Spreadsheet after it is installed.
Am I missing something here?
After checking out the documentation, I believe this cannot be done the way you want with a Google Workspace Add-on.
As you mentioned in your post, you can use onOpen() triggers with editor add-ons to add the menu on all spreadsheets, however, Workspace add-ons have a different set of triggers. Among the list, the most relevant to us would be these:
homepageTriggers are a global trigger type that runs when the user clicks the add-on icon.
onFileScopeGranted triggers are specific to the editor apps (which includes Sheets), and they fire when the add-on gains access to the current file's scope, but it also requires user input to run.
Those are the only triggers available in Sheets if you check out the manifest file definition. Essentially the add-on cannot act by itself in the background with just manifest triggers, it needs to use installable triggers to do this, and to generate installable triggers you need the user to at least open the card once.
It is possible to create installable triggers, but the problem is that you still need one of the regular add-on triggers to fire to create these installable triggers. For example, you can create an onOpen trigger when the user opens the add-on card within a Sheet by defining it within the homepageTrigger function. Something like dobleunary's answer works for this:
Manifest:
"sheets": {
"homepageTrigger": {
"runFunction": "onEditorsHomepage"
},
Sheets.gs
function onEditorsHomepage() {
//probably some logic to check for duplicates
ScriptApp.newTrigger("initializeMenu")
.forSpreadsheet(SpreadsheetApp.getActive())
.onOpen()
.create()
}
In this situation the user clicks the add-on menu, the trigger is created for the sheet, and on subsequent runs the trigger creates the menu without needing to open the add-on again. This works and I was able to create triggers for over 30 different sheets so the 20 triggers/user/script quota may apply in a different way or maybe it counts each sheet as a different script file. This may warrant some testing to figure out the real limit since the docs do not specify.
I would recommend to just build an editor add-on instead and opt for simple triggers. You probably don't want to burden users with triggers for each of their files and even Google's comparison between add-on types emphasizes editor add-ons as the main tools to create custom menus. If you really must build a Google Workspace add-on, then you may have to just live with needing to have the users use the add-on card rather than a menu at the top.
Sources:
Add-on types
Building editor add-ons
Apps Script quotas
Google Workspace add-ons triggers
Editor add-on triggers
Overall add-on manifest
General and Editors manifest definitions.
I need to provide a specific Spreadsheet ID
Instead of using a hard-coded spreadsheet ID, use the ID of the active spreadsheet, or a direct reference to the active spreadsheet object, like this:
function installOnOpenTrigger() {
const functionNameToTrigger = 'initializeMenu';
const ss = SpreadsheetApp.getActive();
if (ss) {
try {
ScriptApp.newTrigger(functionNameToTrigger)
.forSpreadsheet(ss)
.onOpen()
.create();
} catch (error) {
;
}
}
}
SpreadsheetApp.getActive() will return the current spreadsheet whenever there is an active spreadsheet.
From the documentation:
Each add-on can only have one trigger of each type, per user, per document. For instance, in a given spreadsheet, a given user can only have one edit trigger, although the user could also have a form-submit trigger or a time-driven trigger in the same spreadsheet. A different user with access to the same spreadsheet could have their own separate set of triggers.
Add-ons can only create triggers for the file in which the add-on is used. That is, an add-on that is used in Google Doc A cannot create a trigger to monitor when Google Doc B is opened.

Google Apps Script PropertiesService Permissions for AddOns

I'm currently developing an Editor Add-on for Google Sheets using Apps Script. There's an onOpen function that sets up the menu items.
I also have a CONFIG variable in the root (not in any function) like this:
const CONFIG = {}
function setProperties_(){
CONFIG.tmpSheetId = PropertiesService.getScriptProperties().getProperty('TMP_SHEET_ID');
}
setProperties_();
If I run any functions from within the script editor, everything runs fine. No issues. However, when I do a test deployment I get the error below from the moment the onOpen() runs:
You do not have permission to call getScriptProperties
I've tried adding various script scopes from here but nothing works.
When you first install the add-on (the entry for the add-on becomes visible in the sheet menu), it runs in AuthMode.NONE that allows you to inspect only the current user's locale. Please refer to the table here Google Add-On Authorization Lifecycle
As Cooper pointed out, calling setProperties_() in global scope occurs before your add-on is authorized by the user. Move the function call to a nested function and make sure you are through with the authorization flow first.

How to Test OnOpen() menu populate in Apps Script Sheets Add On?

For months I'm trying, without success, to submit my Google Sheets Add-On to be available at Google Workspace Marketplace. On the last tentatives I'm receiving this message:
The App doesn’t meet the publishing review criteria on the following:
Menu - Menu options not shown after App is installed. Please ensure that the add-on correctly uses onInstall() and onOpen() to populate its menu. The menu items populate when the add-on is first installed and when a different file is opened. See Editor add-on authorization.
My question is: How can I simulate the installation by myself in order to detect what function or variable is causing the problem?
I already try to avoid declaring Global variables, tried zillions of different ways to populate the menu, but it seems that nothing work. The documentation of Google Apps Script is too generic, the people at GWM doesn't show what error is happening. I really doesn't know what can I do.
How can I avoid a situation that I cannot replicate?
If I just have access to the my own add on GWM page to test it.
Sometimes I ask myself if Google is interested in have third party add on. It's so hard to have information.
Any help will be welcomed.
This is my initialization routine right now:
var ui = function(){
return SpreadsheetApp.getUi()}
/**
* #OnlyCurrentDoc
* ver: https://developers.google.com/apps-script/guides/services/authorization
*/
// The onOpen function is executed automatically every time a Spreadsheet is loaded
function onOpen(e) {
ui().createAddonMenu()
.addItem('Importar fontes', 'ExtrairComentarios')
.addSeparator()
.addItem('Relação de fatores', 'doFactors')
.addItem('Análise simples', 'doTable')
.addItem('Georreferenciamento', 'doMap')
.addSeparator()
.addItem('Gera matriz multimodal', 'GeraMatrizMM')
.addItem('Análise multimodal', 'doMultimodal')
.addItem('Gráfico multimodal', 'doGet')
.addItem('Gráfico multifatorial', 'doMultifatorial')
.addSeparator()
.addItem('Tutorial', 'openTutorial')
.addSeparator()
.addItem('Versão atual', 'DoVersaoAtual')
.addItem('Start!', 'DoStart')
.addToUi();
// Script external request
//eval(UrlFetchApp.fetch("https://cdnjs.cloudflare.com/ajax/libs/alasql/0.6.2/alasql.min.js").getContentText())
}
/**
* The event handler triggered when installing the add-on.
* #param {Event} e The onInstall event.
*/
function onInstall(e) {
onOpen(e);
}
P.S. Believed me, I already spend months trying to solve this.
Google recently released a way to test Editor add-ons and updated the docs. See https://developers.google.com/apps-script/add-ons/how-tos/testing-editor-addons
Now it's possible to create a test deployment for Editor add-ons. Please checkout the details on the previous link.
If you have a Google Workspace account, an alternative might be to publish the add-on for internal use only for testing purposes. Once you have it working you might make a copy to publish publicly, and keep the internal use only for testing future changes.
To prevent having problems use the simple onOpen trigger only to show the custom menu. If you want that the add-on does something else on open, do that using an installable trigger, this imply to add a way for the end-user to create this trigger, i.e. include a option on the custom menu for that.

Apps Script: Set triggers on a new form to show sidebar or menu items

I have a script that is creating a copy of a form. When the form is opened by the user for the first time I'd like it to display the sidebar or menus items. Is this possible before any authentication flow has occurred?
Adding an onOpen function the doesn't seemed to have worked as it's not automatically triggered unless you've already been through the authentication process.
The way add-ons work seems to be the behaviour I'd like but I'm unsure if you can create Add-ons for Forms yet.
Code samples would help please... Thanks
It is possible to create menu items for a Form via onOpen() prior to the user authorizing. For example, the following will create a single menu with a single menu item:
function onOpen(e) {
// Only creates menu item, and nothing else
FormApp.getUi()
.createMenu('My Menu')
.addItem('My Menu item','myFunction')
.addToUi();
}
function myFunction() {
// Functionality here; may include code that requires authorization
}
The thing to keep in mind is that onOpen should only be used to create the menus -- it should not contain any code that requires authorization. The reason is that Apps Script allows onOpen() to run without authorization, but will prevent its execution entirely if anything inside of it requires authorization. This is a user-safety mechanism -- onOpen() is run before the user interacts with the Form, and it should never automatically run things without getting the user's permission first. Therefore, if a script hasn't been authorized, Apps Script will only allow certain tasks (like menu creation) to be performed.
For the same reason, it is important not to use global variables which access Apps Script services. Global variables are read and processed as the script file is read, and thus execute before the user can be asked to authorize. If you use global variables, it's best practice to limit them to simple constants.
You can read more about the Apps Script Authorization Lifecycle. This documentation focuses on the authorization of Apps Script add-ons, but much of it is relevant to regular Apps Script scripts.
The other answer is correct about authorizations, the onOpen has limited functionalities because it runs without user explicit permission. You can use it to create a sidebar as well (not only menu items)
example below :
function onOpen() {
var userInterface = createUi();
var ui = formApp.getUi();
ui.showSidebar(userInterface)
}
function createUi(){
var app = UiApp.createApplication().setTitle('test sidebar');
var panel = app.createVerticalPanel();
panel.add(app.createLabel('This is a SideBar'));
return app.add(panel);
}
As mentioned above too (but I insist ;-) be careful if you use global service calls as these are executed when EVERY function is called (including the onOpen) and might prevent your script from working as expected.

Deploying and Using a Google App Script

I followed the app script guide to create a menu for my spreadsheet.
I was tested during development in the spreadsheet running it by going to:
Tools -> Script manager...
But now I want to permanently add it to the spreadsheet, but I don't know hot to deploy it.
I deployed it as a webapp, but when I click the test link i get:
Script function not found: doGet
I can not find documentation to complete that doGet function.
Any help on the last step to use my first Google App Script?
if the function that create your menu is called onOpen(e) it should automatically do the job for you when the document is opened, you don't need to publish the app or anything like that.
But in order to be executed the onOpen function must not call personnal data for the user this function onOpen (when triggered automatically, when you open the sheet) has limited rights:
in the doc https://developers.google.com/apps-script/guides/menus,
it say:
Warning: Note that the add-on menu sample above compares the
e.authMode property to values from the ScriptApp.AuthMode enum. This
is because an add-on that has not been used in the current document
runs its onOpen(e) function with severe restrictions, as explained in
the guide to the add-on authorization lifecycle. If your published
add-on fails to add its menu items, look in the browser's JavaScript
console to see if an error was thrown, then examine your script to see
whether the onOpen(e) function or global variables call services that
aren't allowed in AuthMode.NONE.