Google Sheets sync
Connect this dashboard directly to your master tracker. Two-way sync — read on load, write on save.
Setup steps
1
Open the master tracker in Google Sheets. Make sure the tab is named Q2 2026 and the header row matches the columns below.
2
From the menu: Extensions → Apps Script. Delete any starter code and paste the script below.
3
Click Deploy → New deployment → Web app. Set Execute as to Me and Who has access to Anyone. Copy the URL it gives you.
4
Paste the URL above and click Save & connect. Done.
Apps Script code
Copy this into your Apps Script editor. It exposes a read endpoint (GET) and a write endpoint (POST).
const SHEET_NAME = 'Q2 2026';
const KEY_COL = 'Name';
function doGet(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName(SHEET_NAME);
const values = sh.getDataRange().getValues();
const headers = values[0];
const rows = values.slice(1)
.filter(r => r[0] !== '' && r[0] !== 'Share')
.map(r => {
const o = {};
headers.forEach((h, i) => o[h] = r[i] === '' ? '' : r[i]);
return o;
});
return ContentService.createTextOutput(JSON.stringify({ ok: true, headers, rows }))
.setMimeType(ContentService.MimeType.JSON);
}
function doPost(e) {
try {
const body = JSON.parse(e.postData.contents);
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName(SHEET_NAME);
const values = sh.getDataRange().getValues();
const headers = values[0];
const keyIdx = headers.indexOf(KEY_COL);
if (body.action === 'update') {
const target = body.row[KEY_COL];
let rowNum = -1;
for (let i = 1; i < values.length; i++) {
if (String(values[i][keyIdx]) === String(target)) { rowNum = i + 1; break; }
}
if (rowNum === -1) {
// append new
const newRow = headers.map(h => body.row[h] !== undefined ? body.row[h] : '');
sh.appendRow(newRow);
} else {
headers.forEach((h, i) => {
if (body.row[h] !== undefined) sh.getRange(rowNum, i + 1).setValue(body.row[h]);
});
}
return ContentService.createTextOutput(JSON.stringify({ ok: true }))
.setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({ ok: false, error: 'unknown action' }))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({ ok: false, error: err.message }))
.setMimeType(ContentService.MimeType.JSON);
}
}
Sheet columns expected
Header row of the Q2 2026 tab must contain these names (order doesn't matter):
Name · Stage · Industry · Contact · Next Steps · Proposal Value · Deal Source · Open Date · Last Interaction Notes · Notes · Follow-up Date · Deal Description · Next Follow-up Date · Deal Age · Weighted Value
Notes
- Without a connected URL, edits stay in your browser only.
- Changes you make in Google Sheets directly will appear here when you click Refresh or reload.
- The Name column is the unique key — don't duplicate company names.
- Sign-in is access control, not encryption. Keep the link to this dashboard internal.