Build and publish a reusable library
Share Northwind code across many Apps Script projects — one source, many consumers.
Published Aug 24, 2025
Once you have written more than a couple of Apps Script projects, you start copying the same helper functions between them — a Slack poster, a date formatter, a retry wrapper. Copy-paste works until one copy gets a bug fix and the others do not, and now you have five slightly different versions of the same function drifting apart.
An Apps Script library solves this. You write the shared code once in a single project, publish it, and other projects import it by ID. Fix a bug in the library, publish a new version, and every consumer can pick it up. This guide covers building a library, consuming it, and the versioning discipline that keeps it from breaking the projects that depend on it.
Create the library
A library is just an ordinary Apps Script project whose code is meant to be called from outside.
- Create a new Apps Script project — this is the library’s single source.
- Write the functions you want to share. Only functions are exposed to
consumers; top-level variables and
_-prefixed names stay private.
// A shared helper. Any consuming project can call this once the library
// is imported, without copying the code or the webhook URL.
function slackPost(text) {
UrlFetchApp.fetch(
// The webhook lives in the LIBRARY's Script Properties, so each
// consumer does not need to configure it separately.
PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK'),
{
method: 'post',
contentType: 'application/json',
payload: JSON.stringify({ text }),
}
);
}
- Editor → Deploy → New deployment → choose Library as the type → deploy. Then copy the Script ID from Project Settings — that ID is how consumers find the library.
Use it
In any project that needs the shared code, import the library by its ID.
- Editor → Libraries (the
+next to Libraries) → Add a library. - Paste the Script ID and look it up.
- Pick a version — never leave it on the development head.
- Choose an identifier, the name you will call it by, for example
Lib.
// Calls run through the chosen identifier. The library's code and its
// Script Properties do the work; this project holds none of it.
Lib.slackPost('hello from a different script');
How properties and scopes behave
Two behaviours surprise people, so know them up front.
| Resource | Where it resolves |
|---|---|
| Script Properties | The library’s project, not the consumer’s |
| User Properties | The consumer’s active user |
| OAuth scopes | Added to the consumer’s consent screen |
That second column matters. When a library calls UrlFetchApp or GmailApp,
the consuming project inherits those scopes — importing a library can widen what
a consumer asks users to authorise.
Versioning
A library is shared infrastructure, so changing it can break every project that depends on it. Versioning is the discipline that prevents that.
- Always pin a version. The development head (
HEAD) changes every time you save the library — a consumer pinned to it can break with no warning. - Publish a new version on every release. In the deployment dialog, create a numbered version with a short description of what changed.
- Treat breaking changes as new versions, never edits. Renaming or removing a function breaks consumers still on the old call — leave the old version available and let them upgrade deliberately.
- Tell consumers when to upgrade. A new version does nothing until each consuming project edits its library reference to point at it.
When a library is the wrong tool
Libraries add a layer of indirection and a small performance cost on every call. They are not always the answer.
- For one project, a plain
.gsfile in that project is simpler. - For code shared publicly at scale, a published add-on may fit better.
- For a few constants, copying them is fine — reserve libraries for real logic.
Common mistakes
- Leaving a consumer pinned to
HEAD. It works today and breaks silently the next time you save the library. - Editing a published function’s behaviour in place instead of cutting a new version — every consumer changes at once with no chance to test.
- Expecting Script Properties to come from the consumer. They resolve in the library’s own project; use User Properties for per-consumer data.
- Forgetting that a library’s API calls add OAuth scopes to every consumer’s consent screen.
- Naming the identifier something generic like
Utilin every project, then losing track of which library it points to.