Fetching table details and setting up tabletop...
Fetching table details and setting up tabletop...
Paste the full public edit link of your Google Sheet. We will parse the ID and sheet tab (gid) automatically.
Allows syncing toggles for favorites and play status directly back to the Google Sheet. Without this, these toggles will be disabled in the UI (read-only mode).
If you configured a secret token in your Google Apps Script, enter it here to authorize writes.
// Tabletop Game Organizer Sync Script - Version 1.4.1
var SECURITY_TOKEN = "YOUR_SECRET_TOKEN_HERE"; // Change to a secret password if desired
function doPost(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var data = JSON.parse(e.postData.contents);
console.log("Sync request received for game: '" + data.gameName + "', action: '" + data.action + "'");
console.log("Token verification: expected: '" + SECURITY_TOKEN + "', received: '" + data.token + "'");
// Security token validation
if (SECURITY_TOKEN && SECURITY_TOKEN !== "YOUR_SECRET_TOKEN_HERE") {
if (data.token !== SECURITY_TOKEN) {
console.log("Verification failed: Tokens do not match.");
return ContentService.createTextOutput(JSON.stringify({
status: "error",
message: "Unauthorized: Invalid Security Token."
})).setMimeType(ContentService.MimeType.JSON);
}
}
var gameName = data.gameName;
var action = data.action; // 'updateGame', 'toggleFavorite', or 'togglePlayed'
var rows = sheet.getDataRange().getValues();
var headers = rows[0];
var gameColIndex = headers.indexOf("Game");
if (gameColIndex === -1) {
return ContentService.createTextOutput(JSON.stringify({
status: "error",
message: "Required column 'Game' not found."
})).setMimeType(ContentService.MimeType.JSON);
}
// Find matching row
var cleanGameName = gameName.toString().trim().toLowerCase();
var targetRowIndex = -1;
for (var i = 1; i < rows.length; i++) {
var cellValue = rows[i][gameColIndex].toString().trim().toLowerCase();
if (cellValue === cleanGameName) {
targetRowIndex = i + 1; // 1-indexed for Sheets API
break;
}
}
if (targetRowIndex === -1) {
console.log("Error: Game name '" + gameName + "' not found in sheet.");
return ContentService.createTextOutput(JSON.stringify({
status: "error",
message: "Game '" + gameName + "' not found in sheet."
})).setMimeType(ContentService.MimeType.JSON);
}
// 1. Unified Multi-Attribute Update Action
if (action === "updateGame") {
var attributes = data.attributes || {};
var updatedCols = [];
for (var colName in attributes) {
var colIdx = headers.indexOf(colName);
if (colIdx !== -1) {
var rawVal = attributes[colName];
var writeVal = rawVal;
// Normalize booleans to TRUE/FALSE string
if (typeof rawVal === "boolean") {
writeVal = rawVal ? "TRUE" : "FALSE";
} else if (rawVal === null || rawVal === undefined) {
writeVal = "";
}
sheet.getRange(targetRowIndex, colIdx + 1).setValue(writeVal);
updatedCols.push(colName);
}
}
console.log("Successfully updated " + updatedCols.length + " columns for row " + targetRowIndex);
return ContentService.createTextOutput(JSON.stringify({
status: "success",
game: gameName,
updatedColumns: updatedCols
})).setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({
status: "error",
message: "Unknown action '" + action + "'."
})).setMimeType(ContentService.MimeType.JSON);
}
}
Local Cache Status: No local cache found.
The app caches sheet data locally in your browser for instant startup and offline capability. It refreshes automatically in the background on load.
Based on your filters, we recommend: