migrate / db push (Syncing Sheets)
npx gassma migrate and npx gassma db push are commands that generate a GAS function that syncs your spreadsheet's sheets and columns with the Prisma schema (the counterparts of Prisma's prisma migrate dev / prisma db push).
$ npx gassma migrate # generate and record a trail (migrations/)
$ npx gassma migrate --name add_tags # name the trail entry
$ npx gassma db push # generate without recording a trail
The only difference between the two commands is whether a trail (the migrations/ directory) is recorded. migrate leaves a migration.js under migrations/ every time the schema changes, while db push never touches migrations/. The generated runnable stub is identical for both. Use db push when you do not need a migration history.
The commands themselves do not access the spreadsheet. The sheets are synced the moment you run the generated gassmaMigrate function once on the Apps Script side. clasp push is not run automatically either (see "After Generating" below).
What Gets Generated
A runnable stub gassma-migration.js (plain JS) is generated in the output directory. Its content is a single gassmaMigrate function that calls the GASsma library's migrateSheets, with the sheet and column names extracted from the schema embedded in it.
model User {
id Int @id
name String
posts Post[]
}
model Post {
id Int @id
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
The schema above generates the following.
function gassmaMigrate() {
Gassma.migrateSheets({
spreadsheetId: "XXXXX",
models: [
{ name: "User", columns: ["id", "name"] },
{ name: "Post", columns: ["id", "title", "authorId"] }
]
});
}
- The spreadsheet ID is resolved from the
datasourceblock in the schema first, then fromdatasource.urlingassma.config.ts. If neither is set, it is not embedded and the spreadsheet bound to the script is targeted at runtime. - Running the function requires the GASsma library to be registered in the GAS project (an environment set up with bootstrap works as-is). The call symbol (the
Gassma.part in the example) is automatically resolved from theuserSymbolof the library registered inappsscript.jsonin the output directory (falling back toGassmawhen not found).
Output Directory Resolution
--output <dir>(highest priority)rootDirin.clasp.jsonin the current directory
If neither is available, a MigrateOutputDirError is raised.
Sheet and Column Extraction Rules
- One sheet per model. When
@@map/@mapare used, the mapped physical names are used. - Only scalar fields become columns. Relation fields (
posts/authorin the example above) do not become columns, while foreign key columns (authorId) do. - Fields and models with
@ignore/@@ignoreare also included as creation targets. As in Prisma, they are only excluded from the client and still exist physically in the spreadsheet. - Junction sheets for implicit Many-to-Many relations are also created (e.g.
_PostToTag, with columnspostId,tagIdin alphabetical order of the model names).
After Generating
Run the two Next steps shown on success, and the sheets get synced.
✅ Migration generated
Next steps:
1. Run "clasp push" (or "npm run push") to upload gassma-migration.js
2. In the Apps Script editor, run the "gassmaMigrate" function once
Push with clasp push directly (or npm run push, which only calls clasp push). A full clean build (such as npm run deploy generated by bootstrap) may wipe the output directory and delete gassma-migration.js before it is pushed.
Sync Rules
The sync performed by gassmaMigrate (Gassma.migrateSheets) is idempotent and safe to run any number of times.
- Sheets that are in the schema but not in the spreadsheet are created, with the headers written to row 1.
- On existing sheets, only the missing columns are appended to the right end of the header row. Existing columns are never reordered, and data rows are never written to.
- Columns and sheets that are not in the schema are not deleted by default; a warning is logged and they are left untouched.
Gassma.migrateSheets: column "legacy" on sheet "User" is not in the schema. It is left untouched.
Deleting Data (--accept-data-loss)
Pass --accept-data-loss to delete columns and sheets that are not in the schema. acceptDataLoss: true is embedded in the stub, and the deletion is performed when gassmaMigrate runs.
Columns and sheets that still contain data are deleted after a warning that reports how much is left (the number of non-empty cells for a column, the number of data rows for a sheet). Empty ones are deleted without a warning.
Gassma.migrateSheets: You are about to drop the column "legacy" on the sheet "User", which still contains 12 non-empty values.
Migration Trail (migrations/)
migrate creates <UTC timestamp>[_name]/migration.js under the migrations/ directory next to the schema. Its content is identical to the runnable stub.
gassma/
├── schema.prisma
└── migrations/
├── 20260801120000_init/
│ └── migration.js
└── 20260802093000_add_tags/
└── migration.js
- If the content is the same as the latest trail entry, no new entry is created (
Already in sync, no schema change or pending migration was found.is shown). The runnable stub itself is rewritten every time. --namenames the trail entry. The name is sanitized by splitting camelCase, lowercasing, and replacing runs of non-alphanumeric characters with_(e.g.--name "Add UserRole!"→20260801120000_add_user_role).
Options
migrate
| Option | Description |
|---|---|
--name <name> | Name for the new migration |
--output <dir> | Directory to write gassma-migration.js (defaults to rootDir in .clasp.json) |
--schema <path> | Path to a specific .prisma file to migrate from |
--config <path> | Custom path to your GASsma config file |
--accept-data-loss | Delete sheets and columns that are not in the schema |
db push
| Option | Description |
|---|---|
--output <dir> | Directory to write gassma-migration.js (defaults to rootDir in .clasp.json) |
--schema <path> | Path to a specific .prisma file to push from |
--config <path> | Custom path to your GASsma config file |
--accept-data-loss | Delete sheets and columns that are not in the schema |
Limitations
- The header row is assumed to be row 1 starting at column A on every sheet. Header positions moved via changeSettings are not supported.
- Since a spreadsheet must contain at least one sheet, the last remaining sheet is never deleted even with
--accept-data-loss; only a warning is logged.
Gassma.migrateSheets (Library API)
Gassma.migrateSheets, which the stub calls, is a public GASsma API. You can also call it directly with the sheets and columns you want to sync, without using the CLI.
Gassma.migrateSheets({
spreadsheetId: "SPREAD_SHEET_ID", // defaults to the bound spreadsheet
models: [{ name: "User", columns: ["id", "name"] }],
acceptDataLoss: false,
});
models is required; omitting it raises a GassmaMissingArgumentError. The sync rules and limitations are the same as when going through the CLI.