Mark records as deleted without permanently removing them from the database
Soft deletes mark records as deleted without actually removing them from your database. This is useful for maintaining data integrity, audit trails, and allowing record restoration.
class Post extends Model {}Post._table = "Posts";Post._softDeletes = true;Post._deletedAt = "deleted_at"; // Column name (default)Post._timestamps = true; // Recommended with soft deletesPost.use(db);
Your sheet must have a deleted_at column (or whatever you set in _deletedAt). ServiceSQL uses null for active records and an ISO 8601 timestamp for deleted records.
When you call delete(), the record is marked as deleted:
const post = Post.find(1);post.delete();// In the database:// deleted_at: "2025-01-15T10:30:00.000Z" (was null)// Record still exists in the sheet, just marked as deleted
// Get only deleted postsconst deletedPosts = Post.onlyTrashed().get();deletedPosts.each(post => { Logger.log(`Deleted on ${post.deleted_at}: ${post.title}`);});
class TrashManager { // Get all deleted records from a model static getTrash(ModelClass) { return ModelClass.onlyTrashed().get(); } // Empty trash (permanently delete old records) static emptyTrash(ModelClass, daysOld = 30) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysOld); const oldDeleted = ModelClass.onlyTrashed() .where("deleted_at", "<=", cutoffDate.toISOString()) .get(); Logger.log(`Found ${oldDeleted.count()} records to permanently delete`); // Note: This would require a force delete method // For now, you could manually delete these from the sheet } // Restore all deleted records static restoreAll(ModelClass) { const deleted = ModelClass.onlyTrashed().get(); deleted.each(record => { record.deleted_at = null; record.save(); }); Logger.log(`Restored ${deleted.count()} records`); }}// Usageconst trash = TrashManager.getTrash(Post);Logger.log(`Posts in trash: ${trash.count()}`);TrashManager.emptyTrash(Post, 30); // Delete posts in trash > 30 daysTrashManager.restoreAll(Post); // Restore all deleted posts
If you need to permanently delete a record with soft deletes enabled, you’ll need to access the database directly:
// This still soft deletesconst post = Post.find(1);post.delete();// For hard delete, you'd need to implement a custom methodclass Post extends Model { static forceDelete(id) { // Bypass soft delete filter return this._db.table(this._table) .where(this._primaryKey, id) .delete(); }}
Soft deletes increase database size over time - implement cleanup strategies
Unique constraints may conflict with soft-deleted records
Related records need careful handling when parent is soft-deleted
Consider privacy regulations (GDPR) which may require actual deletion
Soft deletes are perfect for user-facing features where “undo” functionality is important. They also provide an excellent audit trail and help prevent accidental data loss.