Technical

A Looker Studio ROAS dashboard from a Google Sheet: fixing "Data Set Configuration Error"

Our server logs every booking to a Sheet, and Looker Studio turns it into a live dashboard. Here's the setup, and the errors that cost us hours.

Hamza El Asri Hamza El AsriFounder & lead strategist, RM Click PublishedNovember 10, 2026 Reading time8 min read AudienceMarketers & developers

If your Looker Studio report shows "Data Set Configuration Error" on charts that worked yesterday, it's usually not your data. It's how the report reads it.

Short answer: avoid blending your Google Sheet with Google Ads for the core numbers. Use standalone scorecards, or join the data inside the Sheet. After adding columns, open the data source and click Refresh fields. On the logging side, use LockService and widen the grid, or rows disappear without an error.

Our server logs every booking from a US salon's Boulevard tracking pipeline to a Google Sheet, and a Looker Studio report reads it live. The pipeline itself is in our complete server-side guide.

The setup

Server (Cloudflare Worker) ──POST row──▶ Apps Script web app ──▶ Google Sheet ──▶ Looker Studio
  1. Every conversion (booking or completed visit) becomes one row: time, appointment, event type, value, channel, UTMs, click IDs, match tier, and whether each ad platform accepted it.
  2. A small Apps Script web app (doPost) receives the row and appends it.
  3. Looker Studio connects to the Sheet: total bookings, bookings by channel, new clients, revenue and a table of recent bookings.

We also keep every row in the server's own storage for 90 days, so if the Sheet ever fails, nothing is lost.

The Apps Script logger, and the rows we lost

A logger looks trivial. Ours dropped rows twice before we fixed it.

// Apps Script: append one row per conversion, safely
var SHEET_NAME = '';            // '' = first sheet
var COLUMNS = ['ts', 'appointment_id', 'event_type', 'event_id', 'value', 'currency',
               'channel', 'utm_source', 'utm_campaign', 'fbclid', 'gclid', 'ttclid',
               'match_tier', 'capi_meta_ok', 'capi_google_ok', 'capi_tiktok_ok'];

function doPost(e) {
  var lock = LockService.getScriptLock();
  lock.waitLock(20000);                                   // serialize concurrent writes
  try {
    var ss = SpreadsheetApp.getActiveSpreadsheet();
    var sh = SHEET_NAME ? ss.getSheetByName(SHEET_NAME) : ss.getSheets()[0];   // never getActiveSheet()
    if (sh.getMaxColumns() < COLUMNS.length) {            // default grid is 26 columns wide
      sh.insertColumnsAfter(sh.getMaxColumns(), COLUMNS.length - sh.getMaxColumns());
    }
    if (sh.getLastRow() === 0) sh.appendRow(COLUMNS);
    var data = JSON.parse(e.postData.contents);
    sh.appendRow(COLUMNS.map(function (k) { return data[k] !== undefined ? data[k] : ''; }));
    return ContentService.createTextOutput('ok');
  } finally {
    lock.releaseLock();
  }
}

What each line protects against:

  • LockService: bookings and their webhooks arrive seconds apart. Without a lock, two writes can collide.
  • Widening the grid: a new sheet is 26 columns wide. When we added a 27th field, reading or writing that column threw an error and the row was silently dropped.
  • An explicit sheet instead of getActiveSheet(), which isn't reliable when the script runs as a web app with nobody looking at it.
  • Mapping by column name, and adding new columns at the end, so old rows stay aligned with the header.

Redeploying without breaking the URL: in Apps Script, use Deploy → Manage deployments → edit the existing deployment → New version. That keeps the same web app URL. Creating a new deployment gives a new URL, and your server keeps posting to the old one.

"Data Set Configuration Error": what fixed it for us

Blends of the Sheet and Google Ads

We wanted ROAS on one chart: revenue from the Sheet, cost from Google Ads, blended by date. Those blends repeatedly showed Data Set Configuration Error, even when each source worked fine on its own.

What worked instead:

  • Standalone scorecards. One scorecard with Google Ads cost (from the Google Ads connector), one with revenue (from the Sheet), side by side. It's less elegant and much more reliable.
  • Join in the Sheet. Put weekly spend in its own tab and calculate ROAS there. Looker Studio then reads one clean source.

New columns break the data source

Looker Studio stores the list of fields when you connect the Sheet. After you add a column:

  1. Open Resource → Manage added data sources → Edit.
  2. Click Refresh fields.
  3. Check the new field's type (number vs text) before using it.

Until you do, charts that touch the changed source can fail.

Other causes worth checking

  • A metric stored as text. If value was ever written as "12.50" text, aggregation fails. Set the field type to Number in the data source.
  • Authorization. If the account that connected the data source lost access to the Sheet, every chart on it fails.

Getting ad spend in: the honest version

  • Google Ads has a free native Looker Studio connector.
  • Meta and TikTok don't. Either type weekly spend into a tab in the Sheet (five minutes a week), or pay for a connector.

For one business, the manual tab is fine and removes another integration that can break.

The columns that make the dashboard trustworthy

Beyond bookings and revenue, three columns turned our dashboard from a pretty chart into something we could act on:

  • match_tier: how each booking was matched to an ad click. The share of deterministic matches vs guesses is visible. We explain why in our match-by-IP case study.
  • capi_*_ok per platform: a column of false for TikTok or Google means a silent failure, spotted the same day.
  • is_new_client: new clients from ads vs returning ones are very different results.

Common errors

  • Data Set Configuration Error on blended charts: replace the blend with standalone scorecards, or compute ROAS in the Sheet.
  • Charts broken after a Sheet change: Refresh fields in the data source.
  • Totals wrong or missing: a number field typed as text.
  • Rows missing in the Sheet: no LockService, or the 26-column limit.
  • Server posting but no rows: you redeployed as a new deployment and the URL changed.

Testing checklist

  • A test function appends a clearly named test row (then delete it).
  • Two test bookings close together both appear.
  • Every column, including the last one, is filled.
  • The data source fields are refreshed and typed correctly.
  • Spend and revenue scorecards match the platforms and the Sheet.

Related reading: Google Ads offline click conversions: gbraid errors and silent partial failures.

Hamza El Asri
Written by Hamza El Asri

Media buyer and tracking specialist, founder of RM Click. Top Rated on Fiverr with 413 reviews. We built the pipeline this article describes. LinkedIn · Fiverr

FAQ

Frequently asked
questions

Common causes are a data source whose fields changed, a field type that no longer matches its data (for example a metric stored as text), a broken authorization, or a blend that can't be queried. In our case it was mostly blends between a Google Sheet and Google Ads.

Looker Studio keeps a snapshot of the Sheet's fields. New columns don't appear and can break charts until you edit the data source and click Refresh fields.

Not natively. Google Ads has a free native connector. For Meta and TikTok spend, enter it manually in the Sheet each week or use a paid connector.

Two causes we hit: concurrent requests writing at the same moment, fixed with LockService, and writing past the sheet's default 26 columns, which threw an error and dropped the row.

Free ad audit

Need this set up
for your salon?