Creating Packages
Build and publish reusable code. Create modules, configure scopes, and share with others.
Create a Package
Packages are reusable code libraries that can be published to the registry and installed by others.
Create the package
Edit gpm.json
Update the package metadata:
{
"name": "my-utils",
"version": "1.0.0",
"description": "Utility functions for Google Apps Script",
"keywords": ["utility", "helpers"],
"license": "MIT"
} Package naming
When published, your package name is automatically scoped to your verified domain or
email (e.g., @you.gmail.com/my-utils).
Write your code
Edit src/base/index.js with your functions:
/**
* Formats a number as currency
* @param {number} value - The value to format
* @returns {string} Formatted currency string
*/
export function formatCurrency(value) {
return '$' + value.toFixed(2);
}
/**
* Removes leading/trailing whitespace
* @param {string} str - The string to trim
* @returns {string} Trimmed string
*/
export function trimWhitespace(str) {
return str.trim();
}Build the package
Package Structure
A package contains one or more modules. Each module has its own namespace and exports.
Directory layout
my-package/
├── gpm.json # Package manifest
├── package.json # NPM config (optional)
├── README.md # Documentation
└── src/
├── base/ # Default module
│ └── index.js
├── utils/ # Additional module
│ └── index.js
└── helpers/ # Another module
└── index.js Adding modules
Add more modules to your package:
Module namespaces
Each module has a namespace that users will use to access its exports. Namespaces should be unique and descriptive:
| Module | Namespace | Usage |
|---|---|---|
| base | MY_UTILS_BASE | MY_UTILS_BASE.formatCurrency() |
| formatters | FORMATTERS | FORMATTERS.formatDate() |
Exports
Use ES module exports in your code:
// Named exports (recommended)
export function formatDate(date) {
return date.toLocaleDateString();
}
export function formatTime(date) {
return date.toLocaleTimeString();
}
// Export constants
export const DATE_FORMAT = 'YYYY-MM-DD';Scopes & Services
Configure OAuth scopes and Advanced Services that your package requires. These are automatically added to users' projects when they install your package.
Add scopes and services
Use the interactive gpm add command:
Select modules and toggle the scopes you need:
View configured scopes
Remove scopes
gpm remove Common OAuth scopes
| Scope | Description |
|---|---|
spreadsheets | Read and write Google Sheets |
drive | Full access to Google Drive |
drive.file | Access files created by this app |
gmail.send | Send email on behalf of user |
calendar | Full access to Google Calendar |
script.external_request | Make external HTTP requests (UrlFetchApp) |
Automatic configuration
When users install your package, scopes and services are automatically added to their appsscript.json. No manual setup required.
Module Dependencies
If your modules reference namespaces from other packages at runtime, declare those dependencies. This enables selective installation — consumers only download the specific modules they need.
Declare dependencies
Use gpm dep add to declare what each module depends on:
You can also resolve by namespace if you know the GAS global name:
gpm dep add fetch --dep-ns QUOTA_V1 Detect missing dependencies
Scan your compiled code to find namespace references you haven't declared:
Build with validation
Use --strict-deps to ensure all dependencies are properly declared:
gpm build --strict-deps Cross-package dependencies that can't be resolved locally (because the dependency package isn't installed in your package directory) are noted as informational — they'll be resolved by the registry when a consumer installs your package.
Dependency commands
| Command | Description |
|---|---|
gpm dep add <mod> --dep <ref> | Add dependency by canonical ref |
gpm dep add <mod> --dep-ns <ns> | Add dependency by namespace (registry lookup) |
gpm dep remove <mod> --dep <ref> | Remove a dependency |
gpm dep list <mod> | List dependencies for a module |
gpm dep detect <mod> | Scan code and suggest missing deps |
Learn more
See Core Concepts: Module Dependencies for how selective installation works end-to-end.
Linking
gpm link substitutes a live source tree for what would otherwise be a
registry-installed copy. When a consumer project depends on a linked package, every build in
the consumer picks up your latest edits directly — no publish, no install.
What linking is for
- Iterating on your own package. Edit your package source, run
gpm buildin the consumer, and the new code is in the bundle. No publish/install cycle between every change. - Pre-release validation. Try a candidate version against a real consumer before publishing to the Exchange — and avoid burning a version number on something you might revise.
- Monorepo cross-package work. If two of your packages reference each other and both live in your tree, link them so the build picks up local source.
- Working from a fork. Point a consumer at your fork of someone else's package instead of the registry version.
How it works
Two modes:
| Command | Where you run it | Effect |
|---|---|---|
gpm link | Inside your package directory | Creates a symlink at ~/.gpm/links/<name> pointing at your source. This
is a global registry of "packages I'm developing locally." |
gpm link @scope/pkg | Inside a consumer project | Symlinks gpm_modules/<scope>/<pkg> to the source registered
globally, and records "link:<path>" in the consumer's gpm.json so the link survives re-installs. |
Walkthrough
Register the package globally
From your package's directory:
Link it into a consumer
From the consumer project that should use the local source:
Edit, build, ship
Edit code in the package source. In the consumer, gpm build bundles the
latest version. Push the bundle to Apps Script with clasp push (or your
existing deploy flow) and exercise the change.
gpm build && clasp pushUnlink when done
Removes the symlink from gpm_modules/ and the link: entry from gpm.json. The consumer goes back to the registry version on next install.
Linking is about source, not runtime
Linking gets fresh source into the consumer's build. It does not run the code — Apps Script
services like DriveApp and SpreadsheetApp only exist on Google's
servers, so executing still means clasp push to a real Apps Script project
today.
A planned gpm init-local command will let you run linked (and installed)
packages from Node using gas-fakes, the community Apps Script emulation layer. Manual setup is possible today; first-class
integration is on the roadmap.
Layout on disk
~/development/
├── my-package/ # Source you're authoring
│ ├── src/
│ │ └── base/
│ │ └── index.js
│ └── gpm.json
│
└── test-project/ # Consumer that depends on it
├── src/
│ └── Code.js
├── gpm.json # records "@you.gmail.com/my-package": "link:..."
└── gpm_modules/
└── @you.gmail.com/
└── my-package → ~/development/my-package (linked)
~/.gpm/links/ # global "packages I'm developing" registry
└── @you.gmail.com-my-package → ~/development/my-packageLocal execution Planned
The companion to gpm link is gpm init-local — a one-time,
per-project setup that wires up the gas-fakes runtime so you can execute Apps Script code from Node, without clasp push on every iteration.
What gpm init-local will do
One-time, per consumer project:
- Install
@mcpher/gas-fakesas a devDependency. - Delegate to
npx gas-fakes initfor auth (Domain-Wide Delegation for Workspace, Application Default Credentials for consumer Gmail). We do not re-implement this flow. - Scaffold a
tests/local.mjsentry that imports gas-fakes and offers a sample call site for any package found ingpm_modules/(linked or installed). - Add
"local": "node tests/local.mjs"topackage.jsonscripts. - Add
.envto.gitignoreif not already present.
Two execution patterns to support
Reading existing community work (see references below), gpm init-local should
accommodate both styles. They serve different audiences:
- Library mode. Import gas-fakes once at the top of a Node entry, import your modules, call them directly. Best for automated tests, Vitest harnesses, and any multi-call flow. Default scaffold target.
- CLI mode. Wrap each module function as a small script using gas-fakes'
function myFunction(args)convention, invoke viagas-fakes -f tools/<name>.js -a '<JSON>'. Best for one-shots, shell-driven workflows, and exposing modules as MCP tools. A potentialgpm init-local --toolsmode.
Reference for implementers
Starting points for whoever picks this up:
- gas-fakes — the underlying Node.js emulation layer (MIT, Bruce McPherson). Relevant CLI flags:
-s "<code>"inline,-f <script.js>file mode,-xexecute,-a '<JSON>'args (received asargsinside the script),-w <docId>bound-document context. - Next-Level Google Apps Script Development — Kanshi Tanaike's worked example of an MCP server that wraps GAS scripts as tools via the
gas-fakes CLI. The pattern suggests an interesting follow-on: gpm modules as MCP tools, where each module function is exposed to AI
agents through a thin gas-fakes-CLI wrapper. Worth evaluating after
gpm init-localships.
gas-fakes hits real APIs
It's an emulation layer, not an offline simulator — calls to DriveApp, SpreadsheetApp, etc. hit real Google Workspace APIs against your account. Use
test resources you don't mind touching. .env credentials must be gitignored.
Final fidelity validation still happens on the live Apps Script runtime before publish.
Publishing
Share your package with the world or keep it private for your own projects.
Pre-publish checklist
gpm buildcompletes without errors- All exports work correctly in Apps Script
gpm verifypasses security checks- Version number is correct in
gpm.json
Dry run
Preview what will be published:
Publish
Access levels
| Flag | Description |
|---|---|
--access public | Anyone can install your package |
--access restricted | Only you can install (default) |
Version updates
Bump the module version before publishing updates. Package version is auto-calculated.
# Patch: 1.0.0 → 1.0.1 (bug fixes)
gpm bump base patch
# Minor: 1.0.0 → 1.1.0 (new features)
gpm bump base minor
# Major: 1.0.0 → 2.0.0 (breaking changes — namespace changes!)
gpm bump base major
# Then publish
gpm publish Versions are permanent
Once published, a version cannot be overwritten or deleted. Always test thoroughly before publishing.
Security scanning
All packages are automatically scanned for security issues before publishing. Packages with critical vulnerabilities (grade D or F) will be rejected.