11 min read

How to Make a Library

Deploying, versioning. Upgrading or Compatibility? Both!
How to Make a Library
Photo by Pickawood / Unsplash

We all have some code snippets we use again and again. Google Apps Script provides a convenient way to create, organize and maintain the reusable code: The Libraries.

Let's create a simple library that deals with a single sheet, it will help us when we need to refresh the sheet with new data. For now we will make it simple, two features only:

  • Clear the data but keep the formatting. Leave the header intact.
  • Remove a sheet filter if it exists.

Create GAS Standalone Project

Create a new Google Apps Script file on your Google Drive.

New Google Apps Script project

Then click on a name to rename it

Rename GAS project

Choose a name, for example "LibSheet". I've chosen "MrSheet" with my initials. There are probably several libraries named "LibSheet" out there, I want to make it more personal. It doesn't matter much, because user of the library is allowed to "rename" it, to give it an alias.

Now let's add some code:

function clearSheet(sheet, startRow) {
  const lastRow = sheet.getLastRow();

  if (lastRow >= startRow) {
    sheet
      .getRange(startRow, 1, lastRow - startRow + 1, sheet.getLastColumn())
      .clearContent();
  }
}

Function will accept sheet and startRow parameters from the user. startRow is where the data starts. Usually your header is in row 1, the user will pass startRow = 2. Sometimes the user has some data above the header (instructions, date selections...) so the header may end up in row 5. In that case the user will pass startRow = 6.

Deploy Project as a Library

Click on the "New Deployment" menu:

Select the "Library" deployment type:

Enter the description and click "Deploy":

That's it, it's deployed. You will get a window with the deployment ID, library URL, but the very important part to read is that you'll need to share the project with the library users:

We will discuss more on sharing later in this post.

Use the Library

I made another project, just for testing, where I have two sheets.

One with the table that has the header in row 1:

Header in row 1 (usual case)

And another sheet with the header in row 6:

Header in row 6 (custom case)

First I want to include MrSheet library in the project. I need a library ID. I'll go back to MrSheet project and:

  1. Click on "Project Settings" section
  2. Copy the Script ID

While I am here, I want to check the "Show appsscript.json" checkbox, I'll need that for later.

Now finally, add the Library to another project:

  1. Click on Library section add (plus) button
  2. Paste the library script ID
  3. Click "Look up" so Google can find the library
  4. Choose the version
  5. Change the library alias or just leave it as it is

We'll get back to details about this dialog, right now let's make it work.

This code:

function myFunction() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  
  const sheetUsual = ss.getSheetByName("Usual Table");
  MrSheet.clearSheet(sheetUsual, 2);

  const sheetCustom = ss.getSheetByName("Custom Table");
  MrSheet.clearSheet(sheetCustom, 7);
}

Works like a charm!

Library Identifier (Alias)

In case you want to use a library name different than "MrSheet", for example "SheetUtils", you can do it easily.

Changing the Library Alias
  1. Click on the Library to open the settings dialog
  2. Enter a new library alias
  3. Don't forget to change the library name in the code: "MrSheet.clearSheet" now should be replaced with "SheetUtils.clearSheet"

In most of the cases you will not change the library identifier. Especially when you are just trying to make things work, for example following a tutorial. Once you make it work, you can change the Library alias and all the occurrences in the code.

However it's great to have this mechanism. Thank you Google Developers! It decouples the library user from the library creator. Decoupling is always a good thing.

Junior Milan: When will I ever need this renaming feature?
Senior Milan: Let me show you.

Case 1: You simply don't like the library name. Just change it.

Case 2: You are using 2 different libraries from 2 authors but they both named their libraries the same: "UtilsLib".

Case 3: Library author changed library name from "MrSheet" to "MrSheetAdvanced". It will not affect the code already using the library. But if you want to start a new project and reuse some of the code from your old project, it's easier to have the same library identifier.

JSDoc

Now let's see how the library looks in the code completion:

Code Completion

Not very helpful, even confusing at some points. Unless you read the library documentation 😅, there will be some points where you will have to guess how it works.

💡
Code should work well, we all know that. But it's equally important to communicate well too.

Let's pus some JSDoc in our code. Google Apps Script supports only a subset of JSDoc notation, but it should be enough for our code.

/**
 * Clears the content in the provided sheet, leaving the formatting intact.
 * The clearing starts from the specified row number, all the columns all cleared.
 * 
 * @param {GoogleAppsScript.Spreadsheet.Sheet} sheet - The sheet to be cleared.
 * @param {number} startRow - The starting row number to begin clearing the content, one-indexed.
 */
function clearSheet(sheet, startRow) {
  const lastRow = sheet.getLastRow();

  if (lastRow >= startRow) {
    sheet
      .getRange(startRow, 1, lastRow - startRow + 1, sheet.getLastColumn())
      .clearContent();
  }
}
Library Code Updated

You will find JSDoc even more helpful when you work in the offline editor, for example Visual Studio Code.

If we try code completion now - nothing is changed 🤔. Oh yes, we need to deploy the new version of the library.

Upgrade the Library

First let's do some housekeeping:

Rename "Code" to "public"

Let's rename "Code.gs" to "public.gs". Right now it doesn't look necessary, but it will become very useful when your library grows.

For example you may have 10 public methods, as your library API/interface/endpoints. But you may have 5 or 20 private methods too. By the way, in GAS private methods end with the underscore by convention, myPrivateMethod_(). Private methods are at the lower level, they do some work for the public methods. It's best to keep them in separate file(s).

Now let's deploy version 2:

Deploying Version 2 of the Library

Now in our code that uses the library we can see another version:

Library Version 2 is available

Note: If you don't see the version 2, try refreshing your browser tab.

Let's select version 2 and try the code completion:

Code Completion for Version 2

Much better! Still not perfect, again, GAS supports only a subset of JSDoc notation, when you start using the offline editor, you will get much more benefits from JSDoc.

Add "removeFilter" Method

Users may apply filter on a sheet, when we change the underlying data it may lead to unexpected results.

Filter on a Sheet

So we want to remove filter before changing the data. One good side effect is that user (if watching the sheet data at the very moment we are refreshing the sheet) becomes aware that data has changed.

/**
 * Removes the filter from the provided sheet, if it is currently applied.
 *
 * @param {GoogleAppsScript.Spreadsheet.Sheet} sheet - The sheet from which to remove the filter.
 */
function removeFilter(sheet) {
  const filter = sheet.getFilter();

  if (filter) {
    filter.remove();
  }
}
Junior Milan: Isn't this method too small and too simple to be extracted into a library?
Senior Milan: Not at all, but that's a topic for another post.

Let's save and deploy Version 3:

Deploying Version 3 of the Library

Again, on the library user side, we will pick the latest version:

Picking Version 3

And we will see the "removeFilter" available to the user:

removeFilte Code Completion

The final library user code now looks like this:

function clearSheets() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  const sheetUsual = ss.getSheetByName("Usual Table");
  MrSheet.removeFilter(sheetUsual);
  MrSheet.clearSheet(sheetUsual, 2);

  const sheetCustom = ss.getSheetByName("Custom Table");
  MrSheet.removeFilter(sheetCustom);
  MrSheet.clearSheet(sheetCustom, 7);

}
Library Usage

Sharing

In order to use the Library, it must be shared with you. This allows you to use public libraries, but also to share your libraries as public, or your organization only, your account only, some user group... You will have the full control.

Libraries are shared in an usual way, just click the "Share" button:

Public Access, View Only

Here I chose to share it with everyone, to make it public. In most of the cases you don't want to grant Editor Access, you will choose the "Viewer".

You can try it yourself, the Script ID is:

"1-SHEwWu6BVkiagoSgRKPOjYKBzsg70igeHge82s4YxT0b-1t6lYeaR9A"

I am sharing the library publicly, so I need to put a disclaimer:

Disclaimer: This Library is provided as-is, and it is advised not to use it for business-sensitive applications. The author cannot be held responsible for any losses caused by using the Library, blah, blah blah. If the Library is causing a headache to the author, the author reserves the right to unpublish the Library in the future (it will be announced on the blog so you may create your own copy).

Hope: If you find the library useful, I may expand it and maintain it, maybe even upgrade it to a community project.

Library Versions Philosophy and Practice

Libraries are evolving. In some periods they may change daily, in other periods they may change once per year.

Library users are depending on the libraries, but library creators are depending too. You may create a library that wraps the communication with eBay and even if you promise that you'll never change it - eBay may change their API and you'll have to create a new version.

Junior Milan: Here we are dealing with Google Sheets only, nothing will change, right?
Senior Milan: Probably not in the near future. But let's be prepared anyway.

For example Google removed addFile method from the Folder class and offered more elegant moveTo method in the File class. It was announced in the release notes.

Conclusion: anything can change.

Library changes can be breaking or non-breaking, some examples:

  • If you add a new method - non-breaking change
  • Deleting a method - breaking change
  • Renaming the method - breaking change
  • Improving the performance - non-breaking change
  • Bug fixing - non-breaking change

Same goes for the parameters, you get the idea.

As in most of the Software development areas, the communication is very important for the Libraries.

What the Users Want

In short: bug-free code and non-breaking changes. They may have different approaches to new Library versions:

  • Latest-Greatest immediately. They will follow the announcements and upgrade to the latest version as soon as it comes out.
  • Latest-Greatest with a delay. They will wait for a week or month to check if anyone reported a problem with the latest version.
  • No upgrades. They used version 10, it works for them, no matter you published version 20 they want to use version 10 as long as it's available. They don't want to follow the announcements. They don't want to upgrade until the very last moment.
  • Not aware. They are not coders, they followed the tutorial, version 15 was used, they copied the code, it works, don't touch it. If the Library creator ever deprecates the version 15 they will have to troubleshoot the problem.

What the Creators Want

In short: minimal support tickets and non-breaking changes.

When the Library upgrades contain the non-breaking changes only (like the example Library from this post) - it's easy for both parties.

If the user needs only the clearSheet method, he may choose to stay on the version 1 no matter we added JSDoc for it in version 2 and removeFilter method in version 3. But user can also upgrade to the version 3 without any user code changes, non-breaking changes make the Library compatible.

The breaking changes - Google made it safe

Sometimes you as a Library creator have to make the breaking changes. For example you had 2 method parameters, then 3, then 6 - you want to pack them into a single "options" or "params" parameter.

Breaking changes are not a big deal, some users will upgrade to the latest version, they will modify the user code. Others will simply stay on the old version and don't change the user code.

This system is like having a code snapshots in different moments. Users have a freedom to stay on the old version and forget about it. Library may develop further, have the breaking changes, you simply don't care. You are connected to the old code, you don't have to modify your code. This versioning concept is not a new thing, Google just designed and applied it properly.

One more feature is the "HEAD (Development mode)" version. Users don't see it, they can't choose it, only the Library creator can use it. It means that you use the current code.

That enables the creator to change the code, test it, refactor it... in a "live" mode. Make a change, save the code, test it. The users are safe, they can't use this (potentially buggy) changes until the creator is finished and deploys the new version. That enables the creator to play with the code with the confidence, not to have to make the project copy and develop on it in order to protect the users.

Note that users could theoretically use this head version if the project is shared with them with the "Editor" access, but in most of the cases there is no reason for that. Libraries are usually shared with the "View only" access.

Remove the Old Versions - Archiving

If you open "Manage deployments" menu:

Manage deployments

You will see 3 active versions that we made, but there is also an option to archive the version.

If you made a new version, for example 13, deployed it and then: users are reporting it has a bug. You will start fixing the problem and communicate with the users:

  • Inform everybody about the bug
  • Tell them to fall back to previous version 12 until the problem is fixed
  • Fix the problem, release a new version 14, inform the users
  • Archive the version 13

On the other side, you may have 20 versions, all of them working properly, all of them used - but you may decide to slowly archive versions 1 to 5 one by one.

Junior Milan: Why would I want to take such a rude action? Some people may still be using version 1.
Senior Milan: Support and maintenance.

Having multiple versions is not always easy. You may improve your library, solve some performance issues, but people using the old versions may still report those problems: it will lead to unnecessary communication about the issues that are already solved.

In order to reduce the usage of the old code, developers may decide to have last N versions active and to archive the old ones. Here is one example email that communicates the version deprecation:

Notification Email About Version Deprecation

As you can see the communication is the most important feature of the Library.


Questions, Corrections and Suggestions are appreciated: contact me.