I am supporting CandidateX

CandidateX is a startup that focuses on creating inclusion-focused hiring solutions, designed to increase access to job opportunities for underestimated talent. Check them out if you have a few minutes to spare. They need visibility!

Those of you that write add-ons will know how to test them before deployment using

but sometimes this refuses to run. For example, if you have an add-on that includes an installable trigger, you’ll get this
ScriptError: The add-on attempted an action that is not permitted in Test as add-on mode. To use this action, you must deploy the add-on.
which seems pretty crazy since the whole point of testing before deployment is to avoid deploying. You can star this issue if it affects you too.


Using your add-on as a library

Of course you can do those all things in a container bound script, so it occurred to me that if I used my add-on as a library, and called it from a container bound script I would be able to test it to a deployable state.
It’s actually fairly simple. These are the steps.

  • create a container bound script, contents follow later on
  • save your add-on and reference it as a library in your container bound script
  • expose any functions in from your add-on you’ll be calling with google.script.run
  • create a function in in the add-on to serve the resolved htmlOutput that makes your add-on.


Container bound script.

Here’s an example – my add-on is published as a library “SheetEfxDemo” and referenced in this script.
function onOpen(e) {
      .createMenu("testing addons")
      .addItem('Sheets efx demo', 'showViz')
function showViz () {
  var ui = SheetEfxDemo.libGetUi();

Expose functions you’ll be calling from client side.

Let’s say you have a function in your add-on called xyz that you will be calling from google.script.run. Just reference it in the global space of your container script, like this
    var xyz = SheetEfxDemo.xyz;

Referencing the trigger

My add-on includes a reference to a function that exists in the add-on as the trigger to be loaded,
    // add the trigger
    Triggers.installChangeTrigger ("efxChanger");
but it doesn’t exist in the container bound script so we need a reference to that too
    var efxChanger = SheetEfxDemo.efxChanger;

Serving the htmlOutput

Instead of the Add-on servicing the htmlOutput, you have to get the container bound script to do it. This is just a matter of creating a function to return it from your add-on.  You’ll notice I referenced this in the showViz function earlier
function libGetUi() {
  return HtmlService.createTemplateFromFile('index.html')
      .setTitle("Sheets Efx demo");

Exposed namespaces

In my scripts I always use namespaces to bundle together functions, and use these namespaces to access them.  You don’t have to do any of this, but for complex add-ons it’s a good idea, and it actually helps with this problem.
On the server side (this will need to be copied into your container bound script)
* used to expose memebers of a namespace
* @param {string} namespace name
* @param {method} method name
function exposeRun(namespace, method, argArray) {
  var global = this;
  var func = namespace ? global[namespace][method] : global[method];
  if (argArray && argArray.length) {
    return func.apply(this, argArray);
  } else {
    return func();
On the client side use like this
    Provoke.run ('Server', 'init', someArgs, someMoreArgs)
    .then (function (keys) {
          // do something
    ['catch'](function(err) {
          // do something about an error
and include this namespace client side
var Provoke =(function (ns) {
  * run something asynchronously
  * @param {string} namespace the namespace (null for global)
  * @param {string} method the method or function to call
  * @param {[...]} the args
  * @return {Promise} a promise
  ns.run = function (namespace,method) {
    // the args to the server function
    var runArgs = Array.prototype.slice.call(arguments).slice(2);
    if (arguments.length<2) {
      throw new Error ('need at least a namespace and method');
    // this will return a promise
    return new Promise(function ( resolve , reject ) {
      .withFailureHandler (function(err) {
        reject (err);
      .withSuccessHandler (function(result) {
        resolve (result);
      .exposeRun (namespace,method,runArgs); 
  return ns;
})(Provoke || {});
Now in my container bound script I can expose all the methods in my Server namespace with
var Server = SheetEfxDemo.Server;


Putting it back together again

Actually, when you deploy your add-on there’s nothing that needs to be done.  You’ve been able to test it without needing to use “Test as add-on”