Every RevOps team has been there: quota tracking lives in a tangle of spreadsheets with broken formulas, outdated rep lists, and five different versions floating around in email threads. This structured Google Sheets setup replaces that chaos with a single source of truth that updates itself.

Sheet Structure

The tracker uses four tabs that work together:

Tab Purpose Key Columns
Reps Master rep roster Name, Team, Manager, Start Date, Ramp Status
Quotas Quarterly quota assignments Rep, Quarter, Quota Amount, Ramp Percentage
Actuals Closed-won bookings Rep, Quarter, Month, Booked Amount
Dashboard Auto-calculated summary Rep, Quota, Actual, Attainment %, Gap

The separation matters. When quotas change, you update one tab. When a rep moves teams, you update another. The dashboard tab calculates everything from the source tabs.

Setting Up the Dashboard Formulas

The Dashboard tab uses structured references to pull data from the other tabs. For each rep, the attainment calculation looks like this:

=IFERROR(
  SUMIFS(Actuals!D:D, Actuals!A:A, A2, Actuals!B:B, $B$1) /
  VLOOKUP(A2, Quotas!A:D, 3, FALSE),
  0
)

For ramp-adjusted quotas, multiply the base quota by the ramp percentage:

=VLOOKUP(A2, Quotas!A:D, 3, FALSE) * VLOOKUP(A2, Quotas!A:D, 4, FALSE)

This ensures new hires on a 50% ramp show accurate attainment against their adjusted target, not the full quota.

Automating the Summary With Apps Script

Manual updates defeat the purpose. This Apps Script function recalculates the dashboard and highlights reps who are off track:

function updateDashboard() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dashboard = ss.getSheetByName("Dashboard");
  const quotas = ss.getSheetByName("Quotas");
  const actuals = ss.getSheetByName("Actuals");

  const currentQuarter = getCurrentQuarter();
  const repData = getRepSummary(quotas, actuals, currentQuarter);

  // Clear and rebuild dashboard
  dashboard.getRange("A2:F100").clearContent();

  repData.forEach((rep, i) => {
    const row = i + 2;
    const attainment = rep.actual / rep.adjustedQuota;
    dashboard.getRange(row, 1, 1, 5).setValues([[
      rep.name, rep.adjustedQuota, rep.actual,
      attainment, rep.adjustedQuota - rep.actual
    ]]);

    // Red highlight if below 60% attainment at mid-quarter
    if (attainment < 0.6 && isPastMidQuarter()) {
      dashboard.getRange(row, 1, 1, 5)
        .setBackground("#f4cccc");
    }
  });
}

function getCurrentQuarter() {
  const now = new Date();
  const q = Math.ceil((now.getMonth() + 1) / 3);
  return `${now.getFullYear()}-Q${q}`;
}

Adding a Weekly Email Digest

Set a time-driven trigger to email leadership a snapshot every Monday:

function sendWeeklyDigest() {
  updateDashboard();
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dashboard = ss.getSheetByName("Dashboard");
  const data = dashboard.getDataRange().getValues();

  let html = "<h2>Quota Attainment Summary</h2><table border='1'>";
  html += "<tr><th>Rep</th><th>Quota</th><th>Actual</th><th>Attainment</th></tr>";
  data.slice(1).forEach(row => {
    const pct = (row[3] * 100).toFixed(1);
    html += `<tr><td>${row[0]}</td><td>$${row[1].toLocaleString()}</td>`;
    html += `<td>$${row[2].toLocaleString()}</td><td>${pct}%</td></tr>`;
  });
  html += "</table>";

  MailApp.sendEmail({
    to: "[email protected]",
    subject: `Weekly Quota Snapshot - ${getCurrentQuarter()}`,
    htmlBody: html,
  });
}

Install the trigger from Apps Script by navigating to Triggers > Add Trigger and setting sendWeeklyDigest to run every Monday at 8 AM.

Key Takeaways

  • A structured four-tab Google Sheet with clear data separation eliminates the version-control chaos of ad hoc quota spreadsheets
  • Apps Script automates dashboard recalculation and conditional formatting so you never publish stale numbers
  • Ramp-adjusted quotas should be calculated automatically to avoid inflating or deflating attainment figures for new hires
  • A weekly email digest keeps leadership informed without requiring them to open the spreadsheet