Thank you for being patient! We're working hard on resolving the issue
Sheets and rows follow a lifecycle from creation through deletion. Understanding this lifecycle is important for building features that persist data correctly.
Sheets are the top-level container. A sheet owns rows via the spreadsheet_to_rows
junction table.
| Operation | Effect |
|---|---|
| Create | Inserts into spreadsheets, generates ID |
| Soft delete | Sets deleted_at, sheet hidden from listings |
| Hard delete | Removes sheet AND cleans up orphaned rows |
Hard delete triggers an orphan cleanup that removes rows not linked to any sheet (see Row Cleanup below).
Rows exist in two modes: sheet-linked and standalone.
Created with a sheetId. The system inserts the row into spreadsheet_rows and
creates a link in spreadsheet_to_rows.
const row = await sheet.rows.add({
type: "number",
label: "Weight",
});
When a sheet-linked row is unlinked from its last sheet (and has no lookupKey),
it is automatically soft-deleted.
Created without a sheetId but with a required lookupKey. These rows are not
linked to any sheet and exist independently.
await sdk.rows.create({
type: "scheduling-link",
label: "Coffee Chat",
lookupKey: ":bookings:coffee-chat",
attributes: { slug: "coffee-chat", durationMinutes: 30, ... },
});
Standalone rows are used for:
lookupKey: ":bookings:{slug}"lookupKey: ":whoop:sleep", etc.The lookupKey serves as both a stable identifier and a protection mechanism:
rows with a lookupKey are never automatically deleted.
When a sheet is hard-deleted, the system runs orphan cleanup:
DELETE FROM spreadsheet_rows
WHERE id NOT IN (SELECT row_id FROM spreadsheet_to_rows)
AND lookup_key IS NULL
AND (metadata->>'superseded')::boolean IS NOT TRUE;
This removes rows that are:
lookupKey (not intentionally standalone)Rows with a lookupKey always survive cleanup. Superseded rows (see below) are
also preserved.
When a standalone row is created with a lookupKey that already exists, the old
row is superseded rather than blocking creation:
lookupKey is saved in metadata.previousLookupKeylookupKey is cleared (freeing it for the new row)deleted_at is set)superseded: trueThe superseded row is preserved for investigation or recovery. It is not affected
by orphan cleanup. The original lookupKey is always recoverable from metadata.
Old row: { id: "abc", lookupKey: ":bookings:chat", ... }
↓ supersede
Old row: { id: "abc", lookupKey: null, deleted_at: "...",
metadata: { superseded: true, previousLookupKey: ":bookings:chat" } }
New row: { id: "xyz", lookupKey: ":bookings:chat", ... }
| Soft Delete | Hard Delete | |
|---|---|---|
| Mechanism | deleted_at = NOW() | DELETE FROM |
| Reversible | Yes (restore) | No |
| When | Unlink last reference | Admin sheet delete |
| Affects | Single row | Orphaned rows without lookupKey |
| lookupKey | Cleared | N/A (row removed) |
lookupKey) persist indefinitelylookupKeylookupKeylookupKey for data that should outlive any particular sheet