Create a Package

Packages are reusable code libraries that can be published to the registry and installed by others.

1

Create the package

$ gpm create package my-utils
Creating package my-utils...
Package created
Next steps:
cd my-utils
# Edit src/base/index.js
gpm build
2

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).

3

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();
}
4

Build the package

$ gpm build
Building package my-utils with 1 module(s)...
src/base/index.js → dist/base.js...
created dist/base.js in 28ms
Build complete - ready for publishing

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:

$ gpm create module formatters --namespace FORMATTERS
Created module formatters
Namespace: FORMATTERS
Entry: src/formatters/index.js

Module namespaces

Each module has a namespace that users will use to access its exports. Namespaces should be unique and descriptive:

ModuleNamespaceUsage
baseMY_UTILS_BASEMY_UTILS_BASE.formatCurrency()
formattersFORMATTERSFORMATTERS.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:

$ gpm add
? What do you want to add? (Use arrow keys)
❯ OAuth Scope
Advanced Service

Select modules and toggle the scopes you need:

? Toggle OAuth scopes (checked = enabled):
◉ spreadsheets - Read and write Google Sheets
◯ spreadsheets.readonly - Read-only access to Google Sheets
◉ drive.file - Access files created by this app
◯ gmail.send - Send email on behalf of user
(Move up and down to reveal more choices)

View configured scopes

$ gpm scopes
📦 OAuth Scopes and Advanced Services
Module: base
────────────────────────────────────────
OAuth Scopes:
• spreadsheets
• drive.file
Advanced Services:
• Sheets (v4)

Remove scopes

gpm remove

Common OAuth scopes

ScopeDescription
spreadsheetsRead and write Google Sheets
driveFull access to Google Drive
drive.fileAccess files created by this app
gmail.sendSend email on behalf of user
calendarFull access to Google Calendar
script.external_requestMake 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:

$ gpm dep add fetch --dep @company.com/quota/tracker
Added dependency '@company.com/quota/tracker' to module 'fetch'

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:

$ gpm dep detect fetch
Detected dependencies:
QUOTA_V1 → @company.com/quota/tracker
⚠️ LOGGER_V1 → @company.com/logger/logger (not declared)
To add missing dependencies:
gpm dep add fetch --dep @company.com/logger/logger

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

CommandDescription
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.

Local 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:

  1. Install @mcpher/gas-fakes as a devDependency.
  2. Delegate to npx gas-fakes init for auth (Domain-Wide Delegation for Workspace, Application Default Credentials for consumer Gmail). We do not re-implement this flow.
  3. Scaffold a tests/local.mjs entry that imports gas-fakes and offers a sample call site for any package found in gpm_modules/ (linked or installed).
  4. Add "local": "node tests/local.mjs" to package.json scripts.
  5. Add .env to .gitignore if 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 via gas-fakes -f tools/<name>.js -a '<JSON>'. Best for one-shots, shell-driven workflows, and exposing modules as MCP tools. A potential gpm init-local --tools mode.

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, -x execute, -a '<JSON>' args (received as args inside 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-local ships.

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 build completes without errors
  • All exports work correctly in Apps Script
  • gpm verify passes security checks
  • Version number is correct in gpm.json

Dry run

Preview what will be published:

$ gpm publish --dry-run
Would publish:
Package: @you.gmail.com/my-utils
Version: 1.0.0
Modules: base, formatters
Files: 4

Publish

$ gpm publish --access public
Published @you.gmail.com/my-utils@1.0.0 as public
📦 Package is now available for installation

Access levels

FlagDescription
--access publicAnyone can install your package
--access restrictedOnly 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.