In Pseudo binding in HTML service I showed a way of looking for changes on the server from client side htmlservice apps. I’ve taken that a bit further and created a very straightforward structure to build in client-side to react to changes in data (or indeed any property) as well as changes in selection, active sheet and so on.

This also works on Docs – see Watching docs for changes

Here’s an example of how you would use it.

  • create a watcher
  • Use it. It will call you every time there are any changes of data or selections in that active sheet.
It really is that simple, and this will allow your add-on to react to changes that are happening server-side right away. Of course, there’s a lot going on behind the scenes that I’ll cover later, but first, let’s look at what gets passed to you.

Callback arguments

Your callback passed to the .watch() method would typically react to server side changes – perhaps visualizing the updated data or using the active range is some way. You receive 3 arguments each time a change happens in the sheet.
This is the current status of the data in the sheet server side. Its contents depends on what you’ve asked to be watched – we’ll look at how to do that later. Assuming you’ve asked to monitor data and selection changes, it will look like this.

 Property  if monitoring has been requested, can contain –
 active  information on the active selection
 data  The [[]] array for the property being monitored (eg Values, Backgrounds etc)
 fiddler  A fiddler object with the data structured (if requested). Will only exist for the Values property.
 sheets  An array of sheet names


This is not normally required, but the pack object contains info on what has actually changed. For example, if you are monitoring both data changes and active changes you can test and to see which (maybe both) has changed to provoke your function being called. If you are monitoring sheets, then packed.change.sheet will indicate there has been a change in the number of sheets or their names.


This is the watcher object and is passed for convenience. You can use it to get more information on the polling process. For example, watcher.getStatus() will provide information on how many polling requests there have been, hot many resulted in changes, and even how much time its spent waiting – a good measure for checking the latency of client/server requests. Just divide the wait time by status.serial and you’ll get the average latency.

Some useful methods available via the watcher object

 Method  Purpose
 getWatching  What is currently being watched. Can be used to change monitoring behavior midflight.
 poke  Reset all the checksums to refresh all data being watched. Recommended if you change properties being watched midflight
 watch  start watching
 stop  stop watching
 restart  restart watching after a stop
 getCurrent  Get all current values. This is the same as the ‘current’ argument passed to your callback
 getStatus  Get status information on the polling process


You can try all this out using the demo. I’m using my usual layout for HTMLservice so I have more files than you would need to for such a simple demo, but you should find it a useful starting pattern for more complex apps as described in More client server code sharing

Setting up a watcher.

These are the default values – this will monitor for data and selection changes in the activesheet.

You can override these changes by passing an options object when you add the watcher. For example, here is one that watches for changes in background colors, but does not monitor for changes in active selection.

Running multiple watchers

You can create and have multiple watchers going at the same time. For example you might have one that watches for just data changes, another for selection changes, one for background color changes and another for changes in font color.

Stopping polling

You can use watcher.stop() to finish polling (it will stop on completion of the currently active poll) and watcher.restart().watch(callback) to start it up again. Removing a watcher ClientWatcher.remove (watcher), will stop it, then remove it.

Watching parts of sheets – scope
By default the entire active sheet is watched for changes. But you can watch just the active selection, or even some other sheet or range. Here’s a few examples

Watch for data changes in the active selection

Watch for data changes in a specific sheet

Watch for data changes in a specific sheet and a specific range

Watch for data changes in a specific range in the active sheet

Changing watching without stopping polling

You can change what your watching midstream. This example stops watching the sheet but starts watching an active selection. This makes it really easy to implement a radio button to swap between watching a whole sheet and the active range

Polling frequency

Apps Script does not have a binding capability, so Watcher simulates this by polling the server. The pollingFrequency property is how often the client asks the server (in ms) to look to see what’s changed. If you have multiple watcher, they can each have different polling intervals. You should set this from the default dependinding on how urgently changes need to be reflected in the client.


In A functional approach to fiddling with sheet data I covered how to play around with sheet data in a structured way. You can instruct the watcher to organize your data values like this too if you want. It will make it a lot easier to manipulate and access than the usual values array. By default, a fiddler is created, and you can access it from the callback. So to get your data in structured format, use

If your data is not suitable for fiddling, or not required, you can turn that off when you set up your watcher with domain.fiddler = false; I highly recommend you spend some time reading up on A functional approach to fiddling with sheet data and using that to manipulate data returned from the sheet.

Setting up

I have created a shared test add-on to demonstrate what gets returned from the watcher. Play around with that and see how it works. There are a number of modules required, and I use my own style for writing modules that will run both server and client side. If you do something else, you’ll need to tweak the code – which you’ll find on github or here.

Here’s what they are all for

 Script  runs on   purpose
 Include  Server  Pulls in server side code to run on the client
 Provoke  Client  Manages conversation with server side
 Utils  Client/Server  Various Utilities
 DomUtils  Client  Dom manipulation stuff – just required for demo
 Fiddler  Client/Server  Manage data for structure access. I’m running this on client, but could be server also if manipulation required before sending
 ClientWatcher  Client  The watcher namespace
 ServerWatcher  Server  The co-operating server namespace
 main.js.html  Client  Just for demo
 addon  Server  Just for demo


I use promises throughout these scripts, so if you need to support very old browser, you may need a promise polyfill client side. You can get one by including this in your script.


One problem about polling is that you might be using up bandwidth or quota polling for changes that aren’t happening. To minimize unnecessary polling, ClientWatcher is able to detect whether the document is actually visible (in other words the user is on the browser tab that hosts it). If it’s not visible, it suspends polling until the user switches back to it. So no polling is done unless the user is looking at it.

It uses this library to do that.

You can change to poll all the time with one of the clientwatcher options


The demo on github just shows changes in the sidebar. You can try it here. It looks like this

For more like this see Google Apps Scripts Snippets
Why not join our community , follow the blog or follow me on Twitter